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 theta(self): """Calculate the theta pseudotorsion for the current nucleotide ARGUMENTS: None RETURNS: The value of theta if it is defined, None otherwise. NOTE: Calculating theta requires the phosphate and sugar atom of the next nucleotide. As a result, behaviour of this function is undefined if this nucleotide is not part of a chain. (Currently, it raises an error.) """ if (self.connectedToNext() and self.hasAtom("P") and self.hasAtom(SUGARATOM) and self.nextNuc().hasAtom("P") and self.nextNuc().hasAtom(SUGARATOM)): return torsion(self.atoms["P"], self.atoms[SUGARATOM], self.nextNuc().atoms["P"], self.nextNuc().atoms[SUGARATOM]) else: return None
def delta(self): """Calculate the delta torsion ARGUMENTS: None RETURNS: The value of delta if it is defined, None otherwise. """ if (self.hasAtom("C5'") and self.hasAtom("C4'") and self.hasAtom("C3'") and self.hasAtom("O3'")): return torsion(self.atoms["C5'"], self.atoms["C4'"], self.atoms["C3'"], self.atoms["O3'"]) else: return None
def zeta(self): """Calculate the zeta torsion ARGUMENTS: None RETURNS: The value of zeta if it is defined, None otherwise. """ if (self.connectedToNext() and self.hasAtom("C3'") and self.hasAtom("O3'") and self.nextNuc().hasAtom("P") and self.nextNuc().hasAtom("O5'")): return torsion(self.atoms["C3'"], self.atoms["O3'"], self.nextNuc().atoms["P"], self.nextNuc().atoms["O5'"]) else: return None
def alpha(self): """Calculate the alpha torsion ARGUMENTS: None RETURNS: The value of alpha if it is defined, None otherwise. """ if (self.connectedToPrev() and self.prevNuc().hasAtom("O3'") and self.hasAtom("P") and self.hasAtom("O5'") and self.hasAtom("C5'")): return torsion(self.atoms["C5'"], self.atoms["O5'"], self.atoms["P"], self.prevNuc().atoms["O3'"]) else: return None
def findBase(self, mapNum, sugar, phos5, phos3, baseType, direction = 3): """Rotate the sugar center by 360 degrees in ROTATE_SUGAR_INTERVAL increments ARGUMENTS: mapNum - the molecule number of the Coot map to use sugar - the coordinates of the C1' atom phos5 - the coordinates of the 5' phosphate phos3 - the coordinates of the 3' phosphate baseType - the base type (A, C, G, or U) OPTIONAL ARGUMENTS: direction - which direction are we tracing the chain if it is 5 (i.e. 3'->5'), then phos5 and phos3 will be flipped all other values will be ignored defaults to 3 (i.e. 5'->3') RETURNS: baseObj - a list of [baseType, baseCoordinates] """ if direction == 5: (phos5, phos3) = (phos3, phos5) #calculate the bisector of the phos-sugar-phos angle #first, calculate a normal to the phos-sugar-phos plane sugarPhos5Vec = minus(phos5, sugar) sugarPhos3Vec = minus(phos3, sugar) normal = crossProd(sugarPhos5Vec, sugarPhos3Vec) normal = scalarProd(normal, 1.0/magnitude(normal)) phosSugarPhosAngle = angle(phos5, sugar, phos3) bisector = rotate(sugarPhos5Vec, normal, phosSugarPhosAngle/2.0) #flip the bisector around (so it points away from the phosphates) and scale its length to 5 A startingBasePos = scalarProd(bisector, -1/magnitude(bisector)) #rotate the base baton by 10 degree increments about half of a sphere rotations = [startingBasePos] #a list of coordinates for all of the rotations for curTheta in range(-90, -1, 10) + range(10, 91, 10): curRotation = rotate(startingBasePos, normal, curTheta) rotations.append(curRotation) #here's where the phi=0 rotation is accounted for for curPhi in range(-90, -1, 10) + range(10, 91, 10): rotations.append(rotate(curRotation, startingBasePos, curPhi)) #test electron density along all base batons for curBaton in rotations: curDensityTotal = 0 densityList = [] for i in range(1, 9): (x, y, z) = plus(sugar, scalarProd(i/2.0, curBaton)) curPointDensity = density_at_point(mapNum, x, y, z) curDensityTotal += curPointDensity densityList.append(curPointDensity) curBaton.append(curDensityTotal) #the sum of the density (equivalent to the mean for ordering purposes) curBaton.append(median(densityList)) #the median of the density curBaton.append(min(densityList)) #the minimum of the density #find the baton with the max density (as measured using the median) #Note that we ignore the sum and minimum of the density. Those calculations could be commented out, # but they may be useful at some point in the future. When we look at higher resolutions maybe? # Besides, they're fast calculations.) baseDir = max(rotations, key = lambda x: x[4]) #rotate the stock base+sugar structure to align with the base baton rotationAngle = angle(self.__baseStrucs["C"]["C4"], [0,0,0], baseDir) axis = crossProd(self.__baseStrucs["C"]["C4"], baseDir[0:3]) orientedBase = rotateAtoms(self.__baseStrucs["C"], axis, rotationAngle) #rotate the base about chi to find the best fit to density bestFitBase = None maxDensity = -999999 for curAngle in range(0,360,5): rotatedBase = rotateAtoms(orientedBase, orientedBase["C4"], curAngle, sugar) curDensity = 0 for curAtom in ["N1", "C2", "N3", "C4", "C5", "C6"]: curDensity += density_at_point(mapNum, rotatedBase[curAtom][0], rotatedBase[curAtom][1], rotatedBase[curAtom][2]) #this is "pseudoChi" because it uses the 5' phosphate in place of the O4' atom pseudoChi = torsion(phos5, sugar, rotatedBase["N1"], rotatedBase["N3"]) curDensity *= self.__pseudoChiInterp.interp(pseudoChi) if curDensity > maxDensity: maxDensity = curDensity bestFitBase = rotatedBase baseObj = ["C", bestFitBase] #mutate the base to the appropriate type if baseType != "C": baseObj = self.mutateBase(baseObj, baseType) return baseObj