Source code for MMTK.ChemicalObjects

# This module implements classes that represent atoms, molecules, and
# complexes. They are made as copies from blueprints in the database.
#
# Written by Konrad Hinsen
#

"""
Atoms, groups, molecules and similar objects
"""

__docformat__ = 'restructuredtext'

from MMTK import Bonds, Collections, Database, \
                 Units, Utility, Visualization
from Scientific.Geometry import Vector
from Scientific.Geometry import Objects3D
from Scientific import N
import copy

#
# The base class for all chemical objects.
#
[docs]class ChemicalObject(Collections.GroupOfAtoms, Visualization.Viewable): """ General chemical object This is an abstract base class that implements methods which are applicable to any chemical object (atom, molecule, etc.). """ def __init__(self, blueprint, memo): if isinstance(blueprint, str): blueprint = self.blueprintclass(blueprint) self.type = blueprint.type if hasattr(blueprint, 'name'): self.name = blueprint.name if memo is None: memo = {} memo[id(blueprint)] = self for attr in blueprint.instance: setattr(self, attr, Database.instantiate(getattr(blueprint, attr), memo)) is_chemical_object = True is_incomplete = False is_modified = False __safe_for_unpickling__ = True __had_initargs__ = True def __hash__(self): return id(self) def __getattr__(self, attr): if attr[:1] == '_' or attr[:3] == 'is_': raise AttributeError else: return getattr(self.type, attr) def isSubsetModel(self): return False def addProperties(self, properties): if properties: for item in properties.items(): if hasattr(self, item[0]) and item[0] != 'name': raise TypeError('attribute '+item[0]+' already defined') setattr(self, item[0], item[1]) def binaryProperty(self, properties, name, default): value = default try: value = properties[name] del properties[name] except KeyError: pass return value
[docs] def topLevelChemicalObject(self): """Returns the highest-level chemical object of which the current object is a part.""" if self.parent is None or not isChemicalObject(self.parent): return self else: return self.parent.topLevelChemicalObject()
[docs] def universe(self): """ :returns: the universe to which the object belongs, or None if the object does not belong to any universe :rtype: :class:`~MMTK.Universe.Universe` """ if self.parent is None: return None else: return self.parent.universe()
[docs] def bondedUnits(self): """ :returns: the largest subobjects which can contain bonds. There are no bonds between any of the subobjects in the list. :rtype: list """ return [self]
[docs] def atomList(self): """ :returns: a list containing all atoms in the object :rtype: list """ pass
[docs] def atomIterator(self): """ :returns: an iterator over all atoms in the object :rtype: iterator """ pass
[docs] def fullName(self): """ :returns: the full name of the object. The full name consists of the proper name of the object preceded by the full name of its parent separated by a dot. :rtype: str """ if self.parent is None or not isChemicalObject(self.parent): return self.name else: return self.parent.fullName() + '.' + self.name
[docs] def degreesOfFreedom(self): """ :returns: the number of degrees of freedom of the object :rtype: int """ return Collections.GroupOfAtoms.degreesOfFreedom(self) \ - self.numberOfDistanceConstraints()
[docs] def distanceConstraintList(self): """ :returns: the distance constraints of the object :rtype: list """ return []
def _distanceConstraintList(self): return [] def traverseBondTree(self, function = None): return []
[docs] def numberOfDistanceConstraints(self): """ :returns: the number of distance constraints of the object :rtype: int """ return 0
[docs] def setBondConstraints(self, universe=None): """ Sets distance constraints for all bonds. """ pass
[docs] def removeDistanceConstraints(self, universe=None): """ Removes all distance constraints. """ pass
[docs] def setRigidBodyConstraints(self, universe = None): """ Sets distance constraints that make the object fully rigid. """ if universe is None: universe = self.universe() if universe is None: import Universe universe = Universe.InfiniteUniverse() atoms = self.atomList() if len(atoms) > 1: self.addDistanceConstraint(atoms[0], atoms[1], universe.distance(atoms[0], atoms[1])) if len(atoms) > 2: self.addDistanceConstraint(atoms[0], atoms[2], universe.distance(atoms[0], atoms[2])) self.addDistanceConstraint(atoms[1], atoms[2], universe.distance(atoms[1], atoms[2])) if len(atoms) > 3: for a in atoms[3:]: self.addDistanceConstraint(atoms[0], a, universe.distance(atoms[0], a)) self.addDistanceConstraint(atoms[1], a, universe.distance(atoms[1], a)) self.addDistanceConstraint(atoms[2], a, universe.distance(atoms[2], a))
[docs] def getAtomProperty(self, atom, property): """ Retrieve atom properties from the chemical database. Note: the property is first looked up in the database entry for the object on which the method is called. If the lookup fails, the complete hierarchy from the atom to the top-level object is constructed and traversed starting from the top-level object until the property is found. This permits database entries for higher-level objects to override property definitions in its constituents. At the atom level, the property is retrieved from an attribute with the same name. This means that properties at the atom level can be defined both in the chemical database and for each atom individually by assignment to the attribute. :returns: the value of the specified property for the given atom from the chemical database. """
def writeXML(self, file, memo, toplevel=1): if self.type is None: name = 'm' + `memo['counter']` memo['counter'] = memo['counter'] + 1 memo[id(self)] = name atoms = copy.copy(self.atoms) bonds = copy.copy(self.bonds) for group in self.groups: group.writeXML(file, memo, 0) for atom in group.atoms: atoms.remove(atom) for bond in group.bonds: bonds.remove(bond) file.write('<molecule id="%s">\n' % name) for group in self.groups: file.write(' <molecule ref="%s" title="%s"/>\n' % (memo[id(group.type)], group.name)) if atoms: file.write(' <atomArray>\n') for atom in atoms: file.write(' <atom title="%s" elementType="%s"/>\n' % (atom.name, atom.type.symbol)) file.write(' </atomArray>\n') if bonds: file.write(' <bondArray>\n') for bond in bonds: a1n = self.relativeName(bond.a1) a2n = self.relativeName(bond.a2) file.write(' <bond atomRefs2="%s %s"/>\n' % (a1n, a2n)) file.write(' </bondArray>\n') file.write('</molecule>\n') else: name = self.name self.type.writeXML(file, memo) atom_names = self.type.getXMLAtomOrder() if toplevel: return ['<molecule ref="%s"/>' % name] else: return None def getXMLAtomOrder(self): if self.type is None: atoms = [] for group in self.groups: atoms.extend(group.getXMLAtomOrder()) for atom in self.atoms: if atom not in atoms: atoms.append(atom) else: atom_names = self.type.getXMLAtomOrder() atoms = [] for name in atom_names: parts = name.split(':') object = self for attr in parts: object = getattr(object, attr) atoms.append(object) return atoms def relativeName(self, object): name = '' while object is not None and object != self: name = object.name + ':' + name object = object.parent return name[:-1] def relativeName_unused(self, object): name = '' while object is not None and object != self: for attr, value in object.parent.__dict__.items(): if value is object: name = attr + ':' + name break object = object.parent return name[:-1] def description(self, index_map = None): tag = Utility.uniqueAttribute() s = self._description(tag, index_map, 1) for a in self.atomList(): delattr(a, tag) return s def __repr__(self): return self.__class__.__name__ + ' ' + self.fullName() __str__ = __repr__ def __copy__(self): return copy.deepcopy(self, {id(self.parent): None}) # Type check
[docs]def isChemicalObject(object): """ :returns: True if object is a chemical object """ return hasattr(object, 'is_chemical_object') # # The second base class for all composite chemical objects. #
[docs]class CompositeChemicalObject(object): """ Chemical object containing subobjects This is an abstract base class that implements methods which can be used with any composite chemical object, i.e. any chemical object that is not an atom. """ def __init__(self, properties): if properties.has_key('configuration'): conf = properties['configuration'] self.configurations[conf].applyTo(self) del properties['configuration'] elif hasattr(self, 'configurations') and \ self.configurations.has_key('default'): self.configurations['default'].applyTo(self) if properties.has_key('position'): self.translateTo(properties['position']) del properties['position'] self.addProperties(properties) def atomList(self): return self.atoms def atomIterator(self): return iter(self.atoms) def setPosition(self, atom, position): if atom.__class__ is Database.AtomReference: atom = self.atoms[atom.number] atom.setPosition(position) def setIndex(self, atom, index): if atom.__class__ is Database.AtomReference: atom = self.atoms[atom.number] atom.setIndex(index) def getAtom(self, atom): if atom.__class__ is Database.AtomReference: atom = self.atoms[atom.number] return atom def getReference(self, atom): if atom.__class__ is Database.AtomReference: return atom return Database.AtomReference(self.atoms.index(atom)) def getAtomProperty(self, atom, property, levels = None): try: return atom.__dict__[property] except KeyError: try: return getattr(self, property)[self.getReference(atom)] except (AttributeError, KeyError): if levels is None: object = atom levels = [] while object != self: levels.append(object) object = object.parent if not levels: raise KeyError('Property ' + property + ' not defined for ', `atom`) return levels[-1].getAtomProperty(atom, property, levels[:-1]) def deleteUndefinedAtoms(self): delete = [a for a in self.atoms if a.position() is None] for a in delete: a.delete() def _deleteAtom(self, atom): self.atoms.remove(atom) self.is_modified = True self.type = None if self.parent is not None: self.parent._deleteAtom(atom) def distanceConstraintList(self): dc = self._distanceConstraintList() for o in self._subunits(): dc = dc + o._distanceConstraintList() return dc def _distanceConstraintList(self): try: return self.distance_constraints except AttributeError: return [] def numberOfDistanceConstraints(self): n = len(self._distanceConstraintList()) + \ sum(len(o._distanceConstraintList()) for o in self._subunits()) return n def setBondConstraints(self, universe=None): if universe is None: universe = self.universe() bond_database = universe.bondLengthDatabase() for o in self.bondedUnits(): o._setBondConstraints(universe, bond_database) def _setBondConstraints(self, universe, bond_database): self.distance_constraints = [] for bond in self.bonds: d = bond_database.bondLength(bond) if d is None: d = universe.distance(bond.a1, bond.a2) self.distance_constraints.append((bond.a1, bond.a2, d)) def addDistanceConstraint(self, atom1, atom2, distance): try: self.distance_constraints.append((atom1, atom2, distance)) except AttributeError: self.distance_constraints = [(atom1, atom2, distance)] def removeDistanceConstraints(self, universe=None): try: del self.distance_constraints except AttributeError: pass for o in self._subunits(): o.removeDistanceConstraints() def traverseBondTree(self, function = None): self.setBondAttributes() todo = [self.atoms[0]] done = {todo[0]: True} bonds = [] while todo: next_todo = [] for atom in todo: bonded = atom.bondedTo() for other in bonded: if not done.get(other, False): if function is None: bonds.append((atom, other)) else: bonds.append((function(atom), function(other))) next_todo.append(other) done[other] = True todo = next_todo self.clearBondAttributes() return bonds def _description(self, tag, index_map, toplevel): letter, kwargs = self._descriptionSpec() s = [letter, '(', `self.name`, ',['] s.extend([o._description(tag, index_map, 0) + ',' for o in self._subunits()]) s.extend([a._description(tag, index_map, 0) + ',' for a in self.atoms if not hasattr(a, tag)]) s.append(']') if toplevel: s.extend([',', `self._typeName()`]) if kwargs is not None: s.extend([',', kwargs]) constraints = self._distanceConstraintList() if constraints: s.append(',dc=[') if index_map is None: s.extend(['(%d,%d,%f),' % (c[0].index, c[1].index, c[2]) for c in constraints]) else: s.extend(['(%d,%d,%f),' % (index_map[c[0].index], index_map[c[1].index], c[2]) for c in constraints]) s.append(']') s.append(')') return ''.join(s) def _typeName(self): return self.type.name def _graphics(self, conf, distance_fn, model, module, options): glist = [] for bu in self.bondedUnits(): for a in bu.atomList(): glist.extend(a._graphics(conf, distance_fn, model, module, options)) if hasattr(bu, 'bonds'): for b in bu.bonds: glist.extend(b._graphics(conf, distance_fn, model, module, options)) return glist # # The classes for atoms, groups, molecules, and complexes. #
[docs]class Atom(ChemicalObject): """ Atom """ def __init__(self, atom_spec, _memo = None, **properties): """ :param atom_spec: a string (not case sensitive) specifying the chemical element :type atom_spec: str :keyword position: the position of the atom :type position: Scientific.Geometry.Vector :keyword name: a name given to the atom :type name: str """ Utility.uniqueID.registerObject(self) ChemicalObject.__init__(self, atom_spec, _memo) self._mass = self.type.average_mass self.array = None self.index = None if properties.has_key('position'): self.setPosition(properties['position']) del properties['position'] self.addProperties(properties) blueprintclass = Database.BlueprintAtom def __getstate__(self): state = copy.copy(self.__dict__) if self.array is not None: state['array'] = None state['pos'] = Vector(self.array[self.index,:]) return state def atomList(self): return [self] def atomIterator(self): yield self
[docs] def setPosition(self, position): """ Changes the position of the atom. :param position: the new position :type position: Scientific.Geometry.Vector """ if position is None: if self.array is None: try: del self.pos except AttributeError: pass else: self.array[self.index,0] = Utility.undefined self.array[self.index,1] = Utility.undefined self.array[self.index,2] = Utility.undefined else: if self.array is None: self.pos = position else: self.array[self.index,0] = position[0] self.array[self.index,1] = position[1] self.array[self.index,2] = position[2]
translateTo = setPosition
[docs] def position(self, conf = None): """ :returns: the position in configuration conf. If conf is 'None', use the current configuration. If the atom has not been assigned a position, the return value is None. """ if conf is None: if self.array is None: try: return self.pos except AttributeError: return None else: if N.logical_or.reduce( N.greater(self.array[self.index, :], Utility.undefined_limit)): return None else: return Vector(self.array[self.index,:]) else: return conf[self]
centerOfMass = position
[docs] def setMass(self, mass): """ Changes the mass of the atom. :param mass: the mass :type mass: float """ self._mass = mass universe = self.universe() if universe is not None: universe._changed(True)
def getAtom(self, atom): return self def translateBy(self, vector): if self.array is None: self.pos = self.pos + vector else: self.array[self.index,0] = self.array[self.index,0] + vector[0] self.array[self.index,1] = self.array[self.index,1] + vector[1] self.array[self.index,2] = self.array[self.index,2] + vector[2] def numberOfPoints(self): return 1 numberOfCartesianCoordinates = numberOfPoints def setIndex(self, index): if self.index is not None and self.index != index: raise ValueError('Wrong atom index') self.index = index def setArray(self, index): self.index = index self.array = None def getArray(self): return self.array def unsetArray(self): self.pos = self.position() self.array = None def setBondAttribute(self, atom): try: self.bonded_to__.append(atom) except AttributeError: self.bonded_to__ = [atom] def clearBondAttribute(self): try: del self.bonded_to__ except AttributeError: pass
[docs] def bondedTo(self): """ :returns: a list of all atoms to which a chemical bond exists. :rtype: list """ try: return self.bonded_to__ except AttributeError: if self.parent is None or not isChemicalObject(self.parent): return [] else: return self.parent.bondedTo(self)
def delete(self): if self.parent is not None: self.parent._deleteAtom(self) def getAtomProperty(self, atom, property, levels = None): if self != atom: raise ValueError("Wrong atom") return getattr(self, property) def _description(self, tag, index_map, toplevel): setattr(self, tag, None) if index_map is None: index = self.index else: index = index_map[self.index] if toplevel: return 'A(' + `self.name` + ',' + `index` + ',' + \ `self.symbol` + ')' else: return 'A(' + `self.name` + ',' + `index` + ')' def _graphics(self, conf, distance_fn, model, module, options): #PJC change: if model == 'ball_and_stick': color = self._atomColor(self, options) material = module.DiffuseMaterial(color) radius = options.get('ball_radius', 0.03) return [module.Sphere(self.position(), radius, material=material)] elif model == 'vdw' or model == 'vdw_and_stick': color = self._atomColor(self, options) material = module.DiffuseMaterial(color) try: radius = self.vdW_radius except: radius = options.get('ball_radius', 0.03) return [module.Sphere(self.position(), radius, material=material)] else: return [] if model != 'ball_and_stick': return [] color = self._atomColor(self, options) material = module.DiffuseMaterial(color) radius = options.get('ball_radius', 0.03) return [module.Sphere(self.position(), radius, material=material)] def writeXML(self, file, memo, toplevel=1): return ['<atom title="%s" elementType="%s"/>' % (self.name, self.type.symbol)] def getXMLAtomOrder(self): return [self]
[docs]class Group(CompositeChemicalObject, ChemicalObject): """ Group of bonded atoms Groups can contain atoms and other groups, and link them by chemical bonds. They are used to represent functional groups or any other part of a molecule that has a well-defined identity. Groups cannot be created in application programs, but only in database definitions for molecules or through a MoleculeFactory. """ def __init__(self, group_spec, _memo = None, **properties): """ :param group_spec: a string (not case sensitive) that specifies the group name in the chemical database :type group_spec: str :keyword position: the position of the center of mass of the group :type position: Scientific.Geometry.Vector :keyword name: a name given to the group :type name: str """ if group_spec is not None: # group_spec is None when called from MoleculeFactory ChemicalObject.__init__(self, group_spec, _memo) self.addProperties(properties) blueprintclass = Database.BlueprintGroup is_incomplete = True def bondedTo(self, atom): if self.parent is None or not isChemicalObject(self.parent): return [] else: return self.parent.bondedTo(atom) def setBondAttributes(self): pass def clearBondAttributes(self): pass def _subunits(self): return self.groups def _descriptionSpec(self): return "G", None
[docs]class Molecule(CompositeChemicalObject, ChemicalObject): """Molecule Molecules consist of atoms and groups linked by bonds. """ def __init__(self, molecule_spec, _memo = None, **properties): """ :param molecule_spec: a string (not case sensitive) that specifies the molecule name in the chemical database :type molecule_spec: str :keyword position: the position of the center of mass of the molecule :type position: Scientific.Geometry.Vector :keyword name: a name given to the molecule :type name: str :keyword configuration: the name of a configuration listed in the database definition of the molecule, which is used to initialize the atom positions. If no configuration is specified, the configuration named "default" will be used, if it exists. Otherwise the atom positions are undefined. :type configuration: str """ if molecule_spec is not None: # molecule_spec is None when called from MoleculeFactory ChemicalObject.__init__(self, molecule_spec, _memo) properties = copy.copy(properties) CompositeChemicalObject.__init__(self, properties) self.bonds = Bonds.BondList(self.bonds) blueprintclass = Database.BlueprintMolecule def bondedTo(self, atom): return self.bonds.bondedTo(atom) def setBondAttributes(self): self.bonds.setBondAttributes() def clearBondAttributes(self): for a in self.atoms: a.clearBondAttribute() def _subunits(self): return self.groups def _descriptionSpec(self): return "M", None def addGroup(self, group, bond_atom_pairs): for a1, a2 in bond_atom_pairs: o1 = a1.topLevelChemicalObject() o2 = a2.topLevelChemicalObject() if set([o1, o2]) != set([self, group]): raise ValueError("bond %s-%s outside object" % (str(a1), str(a2))) self.groups.append(group) self.atoms = self.atoms + group.atoms group.parent = self self.clearBondAttributes() for a1, a2 in bond_atom_pairs: self.bonds.append(Bonds.Bond((a1, a2))) for b in group.bonds: self.bonds.append(b) # construct positions of missing hydrogens
[docs] def findHydrogenPositions(self): """ Find reasonable positions for hydrogen atoms that have no position assigned. This method uses a heuristic approach based on standard geometry data. It was developed for proteins and DNA and may not give good results for other molecules. It raises an exception if presented with a topology it cannot handle. """ self.setBondAttributes() try: unknown = {} for a in self.atoms: if a.position() is None: if a.symbol != 'H': raise ValueError('position of ' + a.fullName() + \ ' is undefined') bonded = a.bondedTo()[0] unknown.setdefault(bonded, []).append(a) for a, list in unknown.items(): bonded = a.bondedTo() n = len(bonded) known = [b for b in bonded if b.position() is not None] nb = len(list) try: method = self._h_methods[a.symbol][n][nb] except KeyError: raise ValueError("Can't handle this yet: " + a.symbol + ' with ' + `n` + ' bonds (' + a.fullName() + ').') method(self, a, known, list) finally: self.clearBondAttributes() # default C-H bond length and X-C-H angle
_ch_bond = 1.09*Units.Ang _hch_angle = N.arccos(-1./3.)*Units.rad _nh_bond = 1.03*Units.Ang _hnh_angle = 120.*Units.deg _oh_bond = 0.95*Units.Ang _coh_angle = 114.9*Units.deg _sh_bond = 1.007*Units.Ang _csh_angle = 96.5*Units.deg def _C4oneH(self, atom, known, unknown): r = atom.position() n0 = (known[0].position()-r).normal() n1 = (known[1].position()-r).normal() n2 = (known[2].position()-r).normal() n3 = (n0 + n1 + n2).normal() unknown[0].setPosition(r-self._ch_bond*n3) def _C4twoH(self, atom, known, unknown): r = atom.position() r1 = known[0].position() r2 = known[1].position() plane = Objects3D.Plane(r, r1, r2) axis = -((r1-r)+(r2-r)).normal() plane = plane.rotate(Objects3D.Line(r, axis), 90.*Units.deg) cone = Objects3D.Cone(r, axis, 0.5*self._hch_angle) sphere = Objects3D.Sphere(r, self._ch_bond) circle = sphere.intersectWith(cone) points = circle.intersectWith(plane) unknown[0].setPosition(points[0]) unknown[1].setPosition(points[1]) def _C4threeH(self, atom, known, unknown): self._tetrahedralH(atom, known, unknown, self._ch_bond) def _C3oneH(self, atom, known, unknown): r = atom.position() n1 = (known[0].position()-r).normal() n2 = (known[1].position()-r).normal() n3 = -(n1 + n2).normal() unknown[0].setPosition(r+self._ch_bond*n3) def _C3twoH(self, atom, known, unknown): r = atom.position() r1 = known[0].position() others = filter(lambda a: a.symbol != 'H', known[0].bondedTo()) r2 = others[0].position() try: plane = Objects3D.Plane(r, r1, r2) except ZeroDivisionError: # We get here if all three points are colinear. # Add a small random displacement as a fix. from MMTK.Random import randomPointInSphere plane = Objects3D.Plane(r, r1, r2 + randomPointInSphere(0.001)) axis = (r-r1).normal() cone = Objects3D.Cone(r, axis, 0.5*self._hch_angle) sphere = Objects3D.Sphere(r, self._ch_bond) circle = sphere.intersectWith(cone) points = circle.intersectWith(plane) unknown[0].setPosition(points[0]) unknown[1].setPosition(points[1]) def _C2oneH(self, atom, known, unknown): r = atom.position() r1 = known[0].position() x = r + self._ch_bond * (r - r1).normal() unknown[0].setPosition(x) def _N2oneH(self, atom, known, unknown): r = atom.position() r1 = known[0].position() others = filter(lambda a: a.symbol != 'H', known[0].bondedTo()) r2 = others[0].position() try: plane = Objects3D.Plane(r, r1, r2) except ZeroDivisionError: # We get here when all three points are colinear. # Add a small random displacement as a fix. from MMTK.Random import randomPointInSphere plane = Objects3D.Plane(r, r1, r2 + randomPointInSphere(0.001)) axis = (r-r1).normal() cone = Objects3D.Cone(r, axis, 0.5*self._hch_angle) sphere = Objects3D.Sphere(r, self._nh_bond) circle = sphere.intersectWith(cone) points = circle.intersectWith(plane) unknown[0].setPosition(points[0]) def _N3oneH(self, atom, known, unknown): r = atom.position() n1 = (known[0].position()-r).normal() n2 = (known[1].position()-r).normal() n3 = -(n1 + n2).normal() unknown[0].setPosition(r+self._nh_bond*n3) def _N3twoH(self, atom, known, unknown): r = atom.position() r1 = known[0].position() others = filter(lambda a: a.symbol != 'H', known[0].bondedTo()) r2 = others[0].position() plane = Objects3D.Plane(r, r1, r2) axis = (r-r1).normal() cone = Objects3D.Cone(r, axis, 0.5*self._hnh_angle) sphere = Objects3D.Sphere(r, self._nh_bond) circle = sphere.intersectWith(cone) points = circle.intersectWith(plane) unknown[0].setPosition(points[0]) unknown[1].setPosition(points[1]) def _N4threeH(self, atom, known, unknown): self._tetrahedralH(atom, known, unknown, self._nh_bond) def _N4twoH(self, atom, known, unknown): r = atom.position() r1 = known[0].position() r2 = known[1].position() plane = Objects3D.Plane(r, r1, r2) axis = -((r1-r)+(r2-r)).normal() plane = plane.rotate(Objects3D.Line(r, axis), 90.*Units.deg) cone = Objects3D.Cone(r, axis, 0.5*self._hnh_angle) sphere = Objects3D.Sphere(r, self._nh_bond) circle = sphere.intersectWith(cone) points = circle.intersectWith(plane) unknown[0].setPosition(points[0]) unknown[1].setPosition(points[1]) def _N4oneH(self, atom, known, unknown): r = atom.position() n0 = (known[0].position()-r).normal() n1 = (known[1].position()-r).normal() n2 = (known[2].position()-r).normal() n3 = (n0 + n1 + n2).normal() unknown[0].setPosition(r-self._nh_bond*n3) def _O2(self, atom, known, unknown): others = known[0].bondedTo() for a in others: r = a.position() if a != atom and r is not None: break dihedral = 180.*Units.deg self._findPosition(unknown[0], atom.position(), known[0].position(), r, self._oh_bond, self._coh_angle, dihedral) def _S2(self, atom, known, unknown): c2 = filter(lambda a: a.symbol == 'C', known[0].bondedTo())[0] self._findPosition(unknown[0], atom.position(), known[0].position(), c2.position(), self._sh_bond, self._csh_angle, 180.*Units.deg) def _tetrahedralH(self, atom, known, unknown, bond): r = atom.position() n = (known[0].position()-r).normal() cone = Objects3D.Cone(r, n, N.arccos(-1./3.)) sphere = Objects3D.Sphere(r, bond) circle = sphere.intersectWith(cone) others = filter(lambda a: a.symbol != 'H', known[0].bondedTo()) others.remove(atom) other = others[0] ref = (Objects3D.Plane(circle.center, circle.normal) \ .projectionOf(other.position())-circle.center).normal() p0 = circle.center + ref*circle.radius p0 = Objects3D.rotatePoint(p0, Objects3D.Line(circle.center, circle.normal), 60.*Units.deg) p1 = Objects3D.rotatePoint(p0, Objects3D.Line(circle.center, circle.normal), 120.*Units.deg) p2 = Objects3D.rotatePoint(p1, Objects3D.Line(circle.center, circle.normal), 120.*Units.deg) unknown[0].setPosition(p0) unknown[1].setPosition(p1) unknown[2].setPosition(p2) def _findPosition(self, unknown, a1, a2, a3, bond, angle, dihedral): sphere = Objects3D.Sphere(a1, bond) cone = Objects3D.Cone(a1, a2-a1, angle) plane = Objects3D.Plane(a3, a2, a1) plane = plane.rotate(Objects3D.Line(a1, a2-a1), dihedral) points = sphere.intersectWith(cone).intersectWith(plane) for p in points: if (a1-a2).cross(p-a1)*(plane.normal) > 0: unknown.setPosition(p) break _h_methods = {'C': {4: {3: _C4threeH, 2: _C4twoH, 1: _C4oneH}, 3: {2: _C3twoH, 1: _C3oneH}, 2: {1: _C2oneH}}, 'N': {4: {3: _N4threeH, 2: _N4twoH, 1: _N4oneH}, 3: {2: _N3twoH, 1: _N3oneH}, 2: {1: _N2oneH}}, 'O': {2: {1: _O2}}, 'S': {2: {1: _S2}}, }
[docs]class ChainMolecule(Molecule): """ ChainMolecule ChainMolecules are generated by a MoleculeFactory from templates that have a sequence attribute. """ def __len__(self): return len(self.sequence) def __getitem__(self, item): return getattr(self, self.sequence[item])
class Crystal(CompositeChemicalObject, ChemicalObject): def __init__(self, blueprint, _memo = None, **properties): ChemicalObject.__init__(self, blueprint, _memo) properties = copy.copy(properties) CompositeChemicalObject.__init__(self, properties) self.bonds = Bonds.BondList(self.bonds) blueprintclass = Database.BlueprintCrystal def _subunits(self): return self.groups def _descriptionSpec(self): return "X", None
[docs]class Complex(CompositeChemicalObject, ChemicalObject): """ Complex A complex is an assembly of molecules that are not connected by chemical bonds. """ def __init__(self, complex_spec, _memo = None, **properties): """ :param complex_spec: a string (not case sensitive) that specifies the complex name in the chemical database :type complex_spec: str :keyword position: the position of the center of mass of the complex :type position: Scientific.Geometry.Vector :keyword name: a name given to the complex :type name: str :keyword configuration: the name of a configuration listed in the database definition of the complex, which is used to initialize the atom positions. If no configuration is specified, the configuration named "default" will be used, if it exists. Otherwise the atom positions are undefined. :type configuration: str """ ChemicalObject.__init__(self, complex_spec, _memo) properties = copy.copy(properties) CompositeChemicalObject.__init__(self, properties) blueprintclass = Database.BlueprintComplex def recreateAtomList(self): self.atoms = [] for m in self.molecules: self.atoms.extend(m.atoms) def bondedUnits(self): return self.molecules def setBondAttributes(self): for m in self.molecules: m.setBondAttributes() def clearBondAttributes(self): for m in self.molecules: m.clearBondAttributes() def _subunits(self): return self.molecules def _descriptionSpec(self): return "C", None def writeXML(self, file, memo, toplevel=1): if self.type is None: name = 'm' + `memo['counter']` memo['counter'] = memo['counter'] + 1 memo[id(self)] = name for molecule in self.molecules: molecule.writeXML(file, memo, 0) file.write('<molecule id="%s">\n' % name) for molecule in self.molecules: file.write(' <molecule ref="%s"/>\n' % memo[id(molecule)]) file.write('</molecule>\n') else: ChemicalObject.writeXML(self, file, memo, toplevel) if toplevel: return ['<molecule ref="%s"/>' % name] else: return None def getXMLAtomOrder(self): if self.type is None: atoms = [] for molecule in self.molecules: atoms.extend(molecule.getXMLAtomOrder()) return atoms else: return ChemicalObject.getXMLAtomOrder(self)
Database.registerInstanceClass(Atom.blueprintclass, Atom) Database.registerInstanceClass(Group.blueprintclass, Group) Database.registerInstanceClass(Molecule.blueprintclass, Molecule) Database.registerInstanceClass(Crystal.blueprintclass, Crystal) Database.registerInstanceClass(Complex.blueprintclass, Complex)
[docs]class AtomCluster(CompositeChemicalObject, ChemicalObject): """ An agglomeration of atoms An atom cluster acts like a molecule without any bonds or atom properties. It can be used to represent a group of atoms that are known to form a chemical unit but whose chemical properties are not sufficiently known to define a molecule. """ def __init__(self, atoms, **properties): """ :param atoms: a list of atoms in the cluster :type atoms: list :keyword position: the position of the center of mass of the cluster :type position: Scientific.Geometry.Vector :keyword name: a name given to the cluster :type name: str """ self.atoms = list(atoms) self.parent = None self.name = '' self.type = None for a in self.atoms: if a.parent is not None: raise ValueError(repr(a)+' is part of ' + repr(a.parent)) a.parent = self if a.name != '': setattr(self, a.name, a) properties = copy.copy(properties) CompositeChemicalObject.__init__(self, properties) self.bonds = Bonds.BondList([]) def bondedTo(self, atom): return [] def setBondAttributes(self): pass def clearBondAttributes(self): pass def _subunits(self): return [] def _description(self, tag, index_map, toplevel): s = 'AC(' + `self.name` + ',[' for a in self.atoms: s = s + a._description(tag, index_map, 1) + ',' s = s + ']' constraints = self._distanceConstraintList() if constraints: s = s + ',dc=[' if index_map is None: for c in constraints: s = s + '(%d,%d,%f),' % (c[0].index, c[1].index, c[2]) else: for c in constraints: s = s + '(%d,%d,%f),' % (index_map[c[0].index], index_map[c[1].index], c[2]) s = s + ']' return s + ')'