Source code for MMTK.InternalCoordinates

# Manipulation of internal coordinates
#
# Written by Konrad Hinsen
#

"""
Manipulation of molecular configurations in terms of internal coordinates
"""

__docformat__ = 'restructuredtext'

import MMTK
from Scientific import N

#
# The abstract base class
#
class InternalCoordinate:

    def __init__(self, atoms):
        self.atoms = atoms
        self.molecule = None
        for m in atoms[0].topLevelChemicalObject().bondedUnits():
            if atoms[0] in m.atomList():
                self.molecule = m
                break
        if self.molecule is None:
            raise ValueError("inconsistent data structure")
        for a in atoms[1:]:
            if a not in self.molecule.atomList():
                raise ValueError("atoms not in the same molecule")
        self.universe = self.molecule.universe()
        if self.universe is None:
            self.universe = MMTK.InfiniteUniverse()

    def bondTest(self):
        for i in range(len(self.atoms)-1):
            if not self.atoms[i] in self.atoms[i+1].bondedTo():
                raise ValueError("no bond between %s and %s"
                                  % (self.atoms[i], self.atoms[i+1]))

    def findFragments(self, spec1, spec2, excluded_first_only=False):
        self.fragment1 = MMTK.Collection()
        self.fragment2 = MMTK.Collection()
        self.molecule.setBondAttributes()
        try:
            self.bondTest()
            for fragment, (start, excluded, error_check) \
                    in [(self.fragment1, spec1), (self.fragment2, spec2)]:
                atoms = set([start])
                first = True
                new_atoms = set([start])
                while new_atoms:
                    check = new_atoms
                    new_atoms = set()
                    for a in check:
                        for na in a.bondedTo():
                            if excluded_first_only:
                                if first:
                                    add = na not in excluded
                                else:
                                    add = True
                            else:
                                add = na not in excluded
                            if add and na not in atoms:
                                atoms.add(na)
                                new_atoms.add(na)
                    first = False
                if error_check in atoms:
                    raise ValueError("cyclic bond structure")
                for a in atoms:
                    fragment.addObject(a)
        finally:
            self.molecule.clearBondAttributes()

#
# Bond length
#
[docs]class BondLength(InternalCoordinate): """ Bond length coordinate A BondLength object permits calculating and modifying the length of a bond in a molecule, under the condition that the bond is not part of a circular bond structure (but it is not a problem to have a circular bond structure elsewhere in the molecule). Modifying the bond length moves the parts of the molecule on both sides of the bond along the bond direction in such a way that the center of mass of the molecule does not change. The initial construction of the BondLength object can be expensive (the bond structure of the molecule must be analyzed). It is therefore advisable to keep the object rather than recreate it frequently. Note that if you only want to calculate bond lengths (no modification), the method Universe.distance is simpler and faster. """ def __init__(self, atom1, atom2): """ :param atom1: the first atom that defines the bond :type atom1: :class:`~MMTK.ChemicalObjects.Atom` :param atom2: the second atom that defines the bond :type atom2: :class:`~MMTK.ChemicalObjects.Atom` """ InternalCoordinate.__init__(self, [atom1, atom2]) self.findFragments((atom1, set([atom2]), atom2), (atom2, set([atom1]), atom1), True)
[docs] def getValue(self, conf = None): """ :param conf: a configuration (defaults to the current configuration) :type conf: :class:`~MMTK.ParticleProperties.Configuration` :returns: the length of the bond in the configuration conf :rtype: float """ return self.universe.distance(self.atoms[0], self.atoms[1], conf)
[docs] def setValue(self, value): """ Sets the length of the bond :param value: the desired length of the bond :type value: float """ v = self.universe.distanceVector(self.atoms[0], self.atoms[1]) axis = v.normal() distance = value - v.length() m1 = self.fragment1.mass() m2 = self.fragment2.mass() d1 = -m2*distance/(m1+m2) d2 = m1*distance/(m1+m2) self.fragment1.translateBy(d1*axis) self.fragment2.translateBy(d2*axis) # # Bond angles #
[docs]class BondAngle(InternalCoordinate): """ Bond angle A BondAngle object permits calculating and modifying the angle between two bonds in a molecule, under the condition that the bonds are not part of a circular bond structure (but it is not a problem to have a circular bond structure elsewhere in the molecule). Modifying the bond angle rotates the parts of the molecule on both sides of the central atom around an axis passing through the central atom and perpendicular to the plane defined by the two bonds in such a way that there is no overall rotation of the molecule. The central atom and any other atoms bonded to it do not move. The initial construction of the BondAngle object can be expensive (the bond structure of the molecule must be analyzed). It is therefore advisable to keep the object rather than recreate it frequently. Note that if you only want to calculate bond angles (no modification), the method Universe.angle is simpler and faster. """ def __init__(self, atom1, atom2, atom3): """ :param atom1: the first atom that defines the angle :type atom1: :class:`~MMTK.ChemicalObjects.Atom` :param atom2: the second and central atom that defines the bond :type atom2: :class:`~MMTK.ChemicalObjects.Atom` :param atom3: the third atom that defines the bond :type atom3: :class:`~MMTK.ChemicalObjects.Atom` """ InternalCoordinate.__init__(self, [atom1, atom2, atom3]) excluded = set([atom2]) self.findFragments((atom1, excluded, atom3), (atom3, excluded, atom1))
[docs] def getValue(self, conf = None): """ :param conf: a configuration (defaults to the current configuration) :type conf: :class:`~MMTK.ParticleProperties.Configuration` :returns: the size of the angle in the configuration conf :rtype: float """ return self.universe.angle(self.atoms[0], self.atoms[1], self.atoms[2], conf)
[docs] def setValue(self, value): """ Sets the size of the angle :param value: the desired angle :type value: float """ from Scientific.Geometry import delta v1 = self.universe.distanceVector(self.atoms[1], self.atoms[0]) v2 = self.universe.distanceVector(self.atoms[1], self.atoms[2]) angle = v1.angle(v2) if N.fabs(angle - N.pi) < 1.e-4: raise ValueError("angle too close to pi") axis = v1.cross(v2).normal() d = angle-value cm1, th1 = self.fragment1.centerAndMomentOfInertia() r1 = self.universe.distanceVector(self.atoms[1], cm1) th1 -= self.fragment1.mass()*((r1*r1)*delta-r1.dyadicProduct(r1)) i1 = axis*(th1*axis) cm2, th2 = self.fragment2.centerAndMomentOfInertia() r2 = self.universe.distanceVector(self.atoms[1], cm2) th2 -= self.fragment2.mass()*((r2*r2)*delta-r2.dyadicProduct(r2)) i2 = axis*(th2*axis) d1 = i2*d/(i1+i2) d2 = d1-d self.fragment1.rotateAroundAxis(self.atoms[1].position(), self.atoms[1].position()+axis, d1) self.fragment2.rotateAroundAxis(self.atoms[1].position(), self.atoms[1].position()+axis, d2) # # Dihedral angles #
[docs]class DihedralAngle(InternalCoordinate): """ Dihedral angle A DihedralAngle object permits calculating and modifying the dihedral angle defined by three consecutive bonds in a molecule, under the condition that the central bond is not part of a circular bond structure (but it is not a problem to have a circular bond structure elsewhere in the molecule). Modifying the dihedral angle rotates the parts of the molecule on both sides of the central bond around this central bond in such a way that there is no overall rotation of the molecule. The initial construction of the DihedralAngle object can be expensive (the bond structure of the molecule must be analyzed). It is therefore advisable to keep the object rather than recreate it frequently. Note that if you only want to calculate bond angles (no modification), the method Universe.dihedral is simpler and faster. """ def __init__(self, atom1, atom2, atom3, atom4): """ :param atom1: the first atom that defines the dihedral :type atom1: :class:`~MMTK.ChemicalObjects.Atom` :param atom2: the second atom that defines the dihedral, must be on the central bond :type atom2: :class:`~MMTK.ChemicalObjects.Atom` :param atom3: the third atom that defines the dihedral, must be on the central bond :type atom3: :class:`~MMTK.ChemicalObjects.Atom` :param atom4: the fourth atom that defines the dihedral :type atom4: :class:`~MMTK.ChemicalObjects.Atom` """ InternalCoordinate.__init__(self, [atom1, atom2, atom3, atom4]) excluded = set([atom2, atom3]) self.findFragments((atom1, excluded, atom4), (atom4, excluded, atom1))
[docs] def getValue(self, conf = None): """ :param conf: a configuration (defaults to the current configuration) :type conf: :class:`~MMTK.ParticleProperties.Configuration` :returns: the size of the dihedral angle in the configuration conf :rtype: float """ return self.universe.dihedral(self.atoms[0], self.atoms[1], self.atoms[2], self.atoms[3], conf)
[docs] def setValue(self, value): """ Sets the size of the dihedral :param value: the desired dihedral angle :type value: float """ from Scientific.Geometry import delta angle = self.universe.dihedral(self.atoms[0], self.atoms[1], self.atoms[2], self.atoms[3]) v = self.universe.distanceVector(self.atoms[1], self.atoms[2]) axis = v.normal() d = N.fmod(angle-value, 2.*N.pi) cm1, th1 = self.fragment1.centerAndMomentOfInertia() r1 = self.universe.distanceVector(self.atoms[1], cm1) th1 -= self.fragment1.mass()*((r1*r1)*delta-r1.dyadicProduct(r1)) i1 = axis*(th1*axis) cm2, th2 = self.fragment2.centerAndMomentOfInertia() r2 = self.universe.distanceVector(self.atoms[2], cm2) th2 -= self.fragment2.mass()*((r2*r2)*delta-r2.dyadicProduct(r2)) i2 = axis*(th2*axis) d1 = i2*d/(i1+i2) d2 = d1-d self.fragment1.rotateAroundAxis(self.atoms[1].position(), self.atoms[1].position()+axis, d1) self.fragment2.rotateAroundAxis(self.atoms[2].position(), self.atoms[2].position()+axis, d2)