#!/usr/bin/env python """The BuildInitSugar class for building sugar coordinates given base and C1' coordinates""" # Copyright 2010 Kevin Keating # # Licensed under the Educational Community License, Version 2.0 (the # "License"); you may not use this file except in compliance with the # License. You may obtain a copy of the License at # # http://www.osedu.org/licenses/ECL-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an "AS IS" # BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express # or implied. See the License for the specific language governing # permissions and limitations under the License. from strucCalc import plus, minus, torsion, rotateAtoms, crossProd, magnitude STARTING_CHI = -150 #anti SYN_CHI = 70 HIGH_ANTI_CHI = -85 class BuildInitSugar: """Given base and C1' coordinates, build a ribose sugar in an anti configuration""" def __init__(self, c3pStruc, c2pStruc): """Initialize a BuildInitSugar object. ARGUMENTS: c3pstruc - the filename for a PDB file containing a C3'-endo sugar c2pstruc - the filename for a PDB file containing a C2'-endo sugar RETURNS: an initialized BuildInitSugar object """ self.c3pStrucFilename = c3pStruc self.c2pStrucFilename = c2pStruc self.__c3pAtoms = readSugarPDB(c3pStruc) self.__c2pAtoms = readSugarPDB(c2pStruc) #translate the sugars so that the C1' atom is at the origin (so we don't have to do it every time we align a sugar) for curSugar in (self.__c3pAtoms, self.__c2pAtoms): c1p = curSugar["C1'"] del curSugar["C1'"] #delete the C1' atom, since it's going to be zeroed anyway #(and we don't want to return it since it doesn't need to be added to the pseudoMolecule object) for (curAtom, curCoords) in curSugar.iteritems(): curSugar[curAtom] = minus(curCoords, c1p) def buildSugar(self, baseAtoms, pucker): """Build a sugar of the specified pucker onto a base ARGUMENTS: baseAtoms - a dictionary of base atoms in the form atomName:[x, y, z] Note that this dictionary MUST contain the C1' atom pucker - the pucker of the sugar to be built (passed as an integer, either 2 or 3) RETURNS: coordinates for a sugar of the specified pucker in anti configuration with the base """ #fetch the appropriate sugar structure if pucker == 3: sugarAtoms = self.__c3pAtoms elif pucker == 2: sugarAtoms = self.__c2pAtoms else: raise "BuildInitSugar called with unrecognized pucker: " + str(pucker) #I don't have to worry about accidentally modifying the original atom dictionaries, since #rotateAtoms effectively makes a deep copy #figure out which base atoms to use for alignment if baseAtoms.has_key("N9"): Natom = "N9" Catom = "C4" else: Natom = "N1" Catom = "C2" #rotate the sugar so the glycosidic bond is at the appropriate angle #first, calculate an axis for this rotation translatedBaseN = minus(baseAtoms[Natom], baseAtoms["C1'"]) sugarN = sugarAtoms[Natom] axis = crossProd(sugarN, translatedBaseN) angle = torsion(translatedBaseN, axis, (0,0,0), sugarN) #if either angle or magnitude(axis) is 0, then the glycosidic bond is already oriented appropriately if not(angle == 0 or magnitude(axis) == 0): sugarAtoms = rotateAtoms(sugarAtoms, axis, angle) #next, rotate the sugar so that chi is appropriate translatedBaseC = minus(baseAtoms[Catom], baseAtoms["C1'"]) curChi = torsion(translatedBaseC, translatedBaseN, [0,0,0], sugarAtoms["O4'"]) sugarAtoms = rotateAtoms(sugarAtoms, translatedBaseN, curChi - STARTING_CHI) #remove the unnecessary atoms from the sugarAtoms dict del sugarAtoms["N1"] del sugarAtoms["N9"] #translate the sugar to the C1' atom of the base sugarAtoms = dict([(atom, plus(coords, baseAtoms["C1'"])) for (atom, coords) in sugarAtoms.iteritems()]) return sugarAtoms def rotateSugar(antiSugarCoords, atoms, newChi = "syn"): """Given a sugar in anti configuration, rotate it to a new configuration (such as syn or high-anti). ARGUMENTS: antiSugarCoords - a dictionary containing a sugar in anti configuration in the format atomName: [x, y, z] typically, this dictionary is generated by a BuildInitSugar object atoms - a dictionary containing base coordinates in the format atomName: [x, y, z] OPTIONAL ARGUMENTS: newChi - the chi value to rotate the sugar to may be "syn", "high-anti", or a number (in degrees) defaults to "syn" RETURNS: synSugarCoords - a dictionary containing a sugar in syn configuration in the format atomName: [x, y, z] """ if newChi == "syn": newChi = SYN_CHI elif newChi == "high-anti": newChi = HIGH_ANTI_CHI #translate the sugar coordinates to the origin synSugarCoords = dict([(atom, minus(coords, atoms["C1'"])) for (atom, coords) in antiSugarCoords.iteritems()]) #rotate the sugar if atoms.has_key("N9"): baseN = "N9" else: baseN = "N1" axis = minus(atoms[baseN], atoms["C1'"]) synSugarCoords = rotateAtoms(synSugarCoords, axis, newChi - STARTING_CHI, atoms["C1'"]) return synSugarCoords def readSugarPDB(filename): """Read in a simple PDB containing the atoms of a sugar ring. ARGUMENTS: filename - the name of the PDB file RETURNS: a dictionary of atomName: [x, y, z] """ input = open(filename, 'r') #a dictionary of atoms names -> coordinates struc = {} for curline in input: if curline[0:6].strip() == "ATOM": atomName = curline[12:17].strip() x = float(curline[30:39]) y = float(curline[38:47]) z = float(curline[46:55]) #if necessary convert the atom names to PDB version 3.0 atomName.replace('*', "'") struc[atomName] = (x, y, z) input.close() return struc