def secondPhos(self, mapNum, curPhos, direction = 3): """find the second phosphate in a chain using phosphate distance data ARGUMENTS: mapNum - the molecule number of the Coot map to use curPhos - the coordinates of the first phosphate OPTIONAL ARGUMENTS: direction - which direction to trace the chain: 3 implies 5'->3' 5 implies 3'->5' defaults to 3 (5'->3') RETURNS: peakList - a list of potential phosphate peaks sugarList - a list of potential C1' locations for each phosphate peak """ peaks = self.getPeaks(mapNum, curPhos) #calculate the score for each peak peakScores = [] for curPeak in peaks: #exclude any peaks that are within an Angstom of the current phosphate position #this will exclude the peak corresponding to the current phosphate position if dist(curPhos, curPeak) >= 1: #do a sugar search for the current phosphate if direction == 3: sugarLocs = self.findSugar(mapNum, curPhos, curPeak) else: #direction == 5: sugarLocs = self.findSugar(mapNum, curPeak, curPhos) densityScore = sugarLocs[0][3] + curPeak[3] score = ln(self.__phosDistInterp.interp(dist(curPhos, curPeak))) + (SECOND_PHOS_DENSITY_WEIGHT * ln(densityScore)) peakScores.append((curPeak, score, sugarLocs)) #sort the peaks according to score peakScores.sort(key = lambda x: x[1], reverse = 1) #return only the coordinates, not the scores peakList = [x[0] for x in peakScores] sugarList = [x[2] for x in peakScores] return (peakList, sugarList)
def firstPhos(self, mapNum, curPos): """find the first phosphate in a chain ARGUMENTS: mapNum - the molecule number of the Coot map to use curPos - where to start the search (i.e. the current screen center coordinates) RETURNS: peaks - a list of potential phosphate peaks """ peaks = self.getPeaks(mapNum, curPos) #sort the peaks based on how close they are to curPos peaks.sort(key=lambda x: dist(curPos, x)) return peaks
def startingSugarDist(self): """Calculate the sugar-sugar distance for the starting suite of this nucleotide ARGUMENTS: None RETURNS: the distance from the sugar atom (i.e. C1') of the previous nucleotide to the sugar atom (C1') of this nucleotide None if there is no previous nucleotide NOTE: Behavior of this function is undefined if this nucleotide is not part of a chain. (Currently, it returns None.) """ if self.connectedToPrev() and self.hasAtom(SUGARATOM) and self.prevNuc().hasAtom(SUGARATOM): return dist(self.atoms[SUGARATOM], self.prevNuc().atoms[SUGARATOM]) else: return None
def phosDist(self): """Calculate the phosphate-phosphate distance ARGUMENTS: None RETURNS: The distance from the phosphate of this nucleotide to the phosphate of the next nucleotide None if no next phosphate if present NOTE: Behavior 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.nextNuc().hasAtom("P"): return dist(self.atoms["P"], self.nextNuc().atoms["P"]) else: return None
def findSugar(self, mapNum, phos5, phos3): """find potential C1' locations between the given 5' and 3' phosphate coordinates ARGUMENTS: mapNum - the molecule number of the Coot map to use phos5 - the coordinates of the 5' phosphate phos3 - the coordinates of the 3' phosphate RETURNS: sugarMaxima - a list of potential C1' locations, each listed as [x, y, z, score] """ #calculate the distance between the two phosphatse phosPhosDist = dist(phos5, phos3) #calculate a potential spot for the sugar based on the phosphate-phosphate distance #projDist is how far along the 3'P-5'P vector the sugar center should be (measured from the 3'P) #perpDist is how far off from the 3'P-5'P vector the sugar center should be #these functions are for the sugar center, which is what we try to find here #since it will be more in the center of the density blob perpDist = -0.185842*phosPhosDist**2 + 1.62296*phosPhosDist - 0.124146 projDist = 0.440092*phosPhosDist + 0.909732 #if we wanted to find the C1' instead of the sugar center, we'd use these functions #however, finding the C1' directly causes our density scores to be less accurate #so we instead use the functions above to find the sugar center and later adjust our #coordinates to get the C1' location #perpDist = -0.124615*phosPhosDist**2 + 0.955624*phosPhosDist + 2.772573 #projDist = 0.466938*phosPhosDist + 0.649833 #calculate the normal to the plane defined by 3'P, 5'P, and a dummy point normal = crossProd([10,0,0], minus(phos3, phos5)) #make sure the magnitude of the normal is not zero (or almost zero) #if it is zero, that means that our dummy point was co-linear with the 3'P-5'P vector #and we haven't calculated a normal #if the magnitude is almost zero, then the dummy point was almost co-linear and we have to worry about rounding error #in either of those cases, just use a different dummy point #they should both be incredibly rare cases, but it doesn't hurt to be safe if magnitude(normal) < 0.001: #print "Recalculating normal" normal = crossProd([0,10,0], minus(phos5, phos3)) #scale the normal to the length of perpDist perpVector = scalarProd(normal, perpDist/magnitude(normal)) #calculate the 3'P-5'P vector and scale it to the length of projDist projVector = minus(phos3, phos5) projVector = scalarProd(projVector, projDist/magnitude(projVector)) #calculate a possible sugar location sugarLoc = plus(phos5, projVector) sugarLoc = plus(sugarLoc, perpVector) #rotate the potential sugar location around the 3'P-5'P vector to generate a list of potential sugar locations sugarRotationPoints = self.__rotateSugarCenter(phos5, phos3, sugarLoc) #test each potential sugar locations to find the one with the best electron density for curSugarLocFull in sugarRotationPoints: curSugarLoc = curSugarLocFull[0:3] #the rotation angle is stored as curSugarLocFull[4], so we trim that off for curSugarLoc curDensityTotal = 0 #densityList = [] #if desired, this could be used to generate additional statistics on the density (such as the median or quartiles) #check density along the 5'P-sugar vector phosSugarVector = minus(curSugarLoc, phos5) phosSugarVector = scalarProd(phosSugarVector, 1.0/(DENSITY_CHECK_POINTS+1)) for i in range(1, DENSITY_CHECK_POINTS+1): (x, y, z) = plus(phos5, scalarProd(i, phosSugarVector)) curPointDensity = density_at_point(mapNum, x, y, z) curDensityTotal += curPointDensity #densityList.append(curPointDensity) #check at the sugar center (x, y, z) = curSugarLoc curPointDensity = density_at_point(mapNum, x, y, z) curDensityTotal += curPointDensity #densityList.append(curPointDensity) #check along the sugar-3'P vector sugarPhosVector = minus(phos3, curSugarLoc) sugarPhosVector = scalarProd(sugarPhosVector, 1.0/(DENSITY_CHECK_POINTS+1)) for i in range(1, DENSITY_CHECK_POINTS+1): (x, y, z) = plus(curSugarLoc, scalarProd(i, sugarPhosVector)) curPointDensity = density_at_point(mapNum, x, y, z) curDensityTotal += curPointDensity #densityList.append(curPointDensity) curSugarLocFull.append(curDensityTotal) #curSugarLocFull.extend([curDensityTotal, median(densityList), lowerQuartile(densityList), min(densityList)])#, pointList]) #find all the local maxima sugarMaxima = [] curPeakHeight = sugarRotationPoints[-1][4] nextPeakHeight = sugarRotationPoints[0][4] sugarRotationPoints.append(sugarRotationPoints[0]) #copy the first point to the end so that we can properly check the last point for i in range(0, len(sugarRotationPoints)-1): prevPeakHeight = curPeakHeight curPeakHeight = nextPeakHeight nextPeakHeight = sugarRotationPoints[i+1][4] if prevPeakHeight < curPeakHeight and curPeakHeight >= nextPeakHeight: sugarMaxima.append(sugarRotationPoints[i]) #sort the local maxima by their density score sugarMaxima.sort(key = lambda x: x[4], reverse = True) #adjust all the sugar center coordinates so that they represent the corresponding C1' coordinates for i in range(0, len(sugarMaxima)): curSugar = sugarMaxima[i][0:3] #rotate a vector 148 degrees from the phosphate bisector phosAngle = angle(phos5, curSugar, phos3) phos5vector = minus(phos5, curSugar) axis = crossProd(minus(phos3, curSugar), phos5vector) axis = scalarProd(axis, 1/magnitude(axis)) c1vec = rotate(phos5vector, axis, 148.539123-phosAngle/2) #scale the vector to the appropriate length c1vec = scalarProd(c1vec, 1.235367/magnitude(c1vec)) #rotate the vector about the phosphate bisector phosBisectorAxis = rotate(phos5vector, axis, -phosAngle/2) phosBisectorAxis = scalarProd(phosBisectorAxis, 1/magnitude(phosBisectorAxis)) c1vec = rotate(c1vec, phosBisectorAxis, -71.409162) sugarMaxima[i][0:3] = plus(c1vec, curSugar) return sugarMaxima
def nextPhos(self, mapNum, curPhos, prevPhos, prevSugar, direction = 3): """Find the next phosphate in a chain using phosphate distance and angle data ARGUMENTS: mapNum - the molecule number of the Coot map to use curPhos - the coordinates of the current phosphate prevPhos - the coordinates of the previous phosphate prevSugar - the coordinates of the previous C1' atom OPTIONAL ARGUMENTS: direction - which direction to trace the chain: 3 implies 5'->3' 5 implies 3'->5' defaults to 3 (5'->3') RETURNS: peakList - a list of potential phosphate peaks sugarList - a list of potential C1' locations for each phosphate peak """ peaks = self.getPeaks(mapNum, curPhos) #calculate the score for each peak peakScores = [] for curPeak in peaks: #exclude any peaks that are within an Angstom of the current phosphate position #this will exclude the peak corresponding to the current phosphate position if dist(curPhos, curPeak) >= 1: #do a sugar search for the current phosphate if direction == 3: sugarLocs = self.findSugar(mapNum, curPhos, curPeak) else: sugarLocs = self.findSugar(mapNum, curPeak, curPhos) densityScore = sugarLocs[0][3] + curPeak[3] score = ((PHOS_DIST_WEIGHT * ln(self.__phosDistInterp.interp(dist(curPhos, curPeak)))) + ln(self.__phosAngleInterp.interp(angle(prevPhos, curPhos, curPeak))) + ln(self.__sugarPhosSugarAngleInterp.interp(angle(prevSugar, curPhos, sugarLocs[0][0:3]))) + (DENSITY_WEIGHT * ln(densityScore)) ) peakScores.append((curPeak, score, sugarLocs)) #FOR DEBUGGING OUTPUT #peakScores.append((curPeak, score, sugarLocs, ln(self.__phosDistInterp.interp(dist(curPhos, curPeak))), ln(self.__phosAngleInterp.interp(angle(prevPhos, curPhos, curPeak))), ln(self.__sugarPhosSugarAngleInterp.interp(angle(prevSugar, prevPhos, sugarLocs[0][0:3]))), sugarLocs[0][3], sugarLocs[0][4], sugarLocs[0][5], sugarLocs[0][6], curPeak[3], sugarLocs[0][7])) #peakScores.append((curPeak, score, sugarLocs, ln(self.__phosDistInterp.interp(dist(curPhos, curPeak))), ln(self.__phosAngleInterp.interp(angle(prevPhos, curPhos, curPeak))), ln(self.__sugarPhosSugarAngleInterp.interp(angle(prevSugar, curPhos, sugarLocs[0][0:3]))), ln(densityScore), DENSITY_WEIGHT * ln(densityScore), sugarLocs[0][3], curPeak[3])) #print "Score for peak %(curPeak)s is %(score)f" % vars() #print "\tDist: " + str(dist(curPhos, curPeak)) + "\t" + str(self.__phosDistInterp.interp(dist(curPhos, curPeak))) #print "\tAngle: " + str(angle(prevPhos, curPhos, curPeak)) + "\t" + str(self.__phosAngleInterp.interp(angle(prevPhos, curPhos, curPeak))) #sort the peaks according to score peakScores.sort(key = lambda x: x[1], reverse = 1) #return only the coordinates, not the scores peakList = [x[0] for x in peakScores] sugarList = [x[2] for x in peakScores] #FOR DEBUGGING OUTPUT #print "Current scores:" #print "overall\t\tphosDist\tphosAngle\tsPsAngle\tdensity score\tw. den score\tsugar score\tphos score" #for x in peakScores: # #print "SCORE: %f\tSUGAR SCORE: %f" % (x[1], x[2][0][3]) # print "\t".join(map(str, (x[1],) + x[3:])) return (peakList, sugarList)