Warren, Thanks for the information. So, instead of utilizing the get_dihedral command I've gone ahead and changed my mode of attack. By using the get_model command (which is extremely fast), I can access the coordinate information directly. Then, I wrote a new subroutine that will read in these coordinates and calculates the necessary dihedral angles from coordinates and NOT from selections. This drastically improved the performance and I was able to analyze the sugar pucker in a blink of an eye (as expected). For those who are interested, the modified code (though lengthier) is below.
Sean from pymol.cgo import * # get constants from math import * from pymol import cmd def pucker(selection): # Author: Sean Law # Institute: Michigan State University # E-mail: s...@msu.edu obj_name = selection objects = cmd.get_names(selection) for obj in objects: if (obj == selection): obj_name=obj sel=cmd.get_model(selection) first=1 old=" " oldchain=" " residue={} theta={} count=0 for atom in sel.atom: new=atom.chain+" "+str(atom.resi) newchain=atom.chain+" "+atom.segi if (not (oldchain == newchain) and first): print " " #Blank line to separate chain output print "%6s %6s %8s Residue" % ("Phase", "Amp", "Pucker") if (not(new == old) and (not first)): #Check that all 5 atoms exist if(len(residue) == 15): #Construct planes get_dihedrals(residue,theta) #Calculate pucker info = pseudo(residue,theta) print info+" "+old else: print "There is no sugar in this residue" if (not (oldchain == newchain)): print " " #Blank line to separate chain output print "%6s %6s %8s Residue" % ("Phase", "Amp", "Pucker") #Clear values residue={} dihedrals={} theta={} #Store new value store_atom(atom,residue) else: store_atom(atom,residue) first=0 old=new oldchain=newchain #Final Residue #Calculate dihedrals for final residue if (len(residue) == 15): #Construct planes get_dihedrals(residue,theta) #Calculate pucker for final residue info = pseudo(residue,theta) print info+" "+old else: print "There is no sugar in this residue" return def sele_exists(sele): return sele in cmd.get_names("selections"); def pseudo(residue,theta): other=2*(sin(math.radians(36.0))+sin(math.radians(72.0))) #phase=atan2((theta4+theta1)-(theta3+theta0),2*theta2*(sin(math.radians(36.0))+sin(math.radians(72.0)))) phase=atan2((theta['4']+theta['1'])-(theta['3']+theta['0']),theta['2']*other) amplitude=theta['2']/cos(phase) phase=math.degrees(phase) if (phase < 0): phase=phase+360 # 0 <= Phase < 360 #Determine pucker if (phase < 36): pucker = "C3'-endo" elif (phase < 72): pucker = "C4'-exo" elif (phase <108): pucker = "O4'-endo" elif (phase < 144): pucker = "C1'-exo" elif (phase < 180): pucker = "C2'-endo" elif (phase < 216): pucker = "C3'-exo" elif (phase < 252): pucker = "C4'-endo" elif (phase < 288): pucker = "O4'-exo" elif (phase < 324): pucker = "C1'-endo" elif (phase < 360): pucker = "C2'-exo" else: pucker = "Phase is out of range" info = "%6.2f %6.2f %8s" % (phase, amplitude, pucker) return info def store_atom(atom,residue): if (atom.name == "O4'" or atom.name == "O4*"): residue['O4*X'] = atom.coord[0] residue['O4*Y'] = atom.coord[1] residue['O4*Z'] = atom.coord[2] elif (atom.name == "C1'" or atom.name == "C1*"): residue['C1*X'] = atom.coord[0] residue['C1*Y'] = atom.coord[1] residue['C1*Z'] = atom.coord[2] elif (atom.name == "C2'" or atom.name == "C2*"): residue['C2*X'] = atom.coord[0] residue['C2*Y'] = atom.coord[1] residue['C2*Z'] = atom.coord[2] elif (atom.name == "C3'" or atom.name == "C3*"): residue['C3*X'] = atom.coord[0] residue['C3*Y'] = atom.coord[1] residue['C3*Z'] = atom.coord[2] elif (atom.name == "C4'" or atom.name == "C4*"): residue['C4*X'] = atom.coord[0] residue['C4*Y'] = atom.coord[1] residue['C4*Z'] = atom.coord[2] return def get_dihedrals(residue,theta): C = [] ribose = residue.keys() ribose.sort() shift_up(ribose,6) for i in range (0,12): C.append(residue[ribose[i]]) theta['0']=dihedral(C) C = [] shift_down(ribose,3) for i in range (0,12): C.append(residue[ribose[i]]) theta['1']=dihedral(C) C = [] shift_down(ribose,3) for i in range (0,12): C.append(residue[ribose[i]]) theta['2']=dihedral(C) C = [] shift_down(ribose,3) for i in range (0,12): C.append(residue[ribose[i]]) theta['3']=dihedral(C) C = [] shift_down(ribose,3) for i in range (0,12): C.append(residue[ribose[i]]) theta['4']=dihedral(C) return def shift_up(list,value): for i in range (0,value): list.insert(0,list.pop()) return def shift_down(list,value): for i in range (0,value): list.insert(len(list),list.pop(0)) return def dihedral(C): DX12=C[0]-C[3] DY12=C[1]-C[4] DZ12=C[2]-C[5] DX23=C[3]-C[6] DY23=C[4]-C[7] DZ23=C[5]-C[8] DX43=C[9]-C[6]; DY43=C[10]-C[7]; DZ43=C[11]-C[8]; #Cross product to get normal PX1=DY12*DZ23-DY23*DZ12; PY1=DZ12*DX23-DZ23*DX12; PZ1=DX12*DY23-DX23*DY12; NP1=sqrt(PX1*PX1+PY1*PY1+PZ1*PZ1); PX1=PX1/NP1 PY1=PY1/NP1 PZ1=PZ1/NP1 PX2=DY43*DZ23-DY23*DZ43; PY2=DZ43*DX23-DZ23*DX43; PZ2=DX43*DY23-DX23*DY43; NP2=sqrt(PX2*PX2+PY2*PY2+PZ2*PZ2); PX2=PX2/NP2 PY2=PY2/NP2 PZ2=PZ2/NP2 DP12=PX1*PX2+PY1*PY2+PZ1*PZ2 TS=1.0-DP12*DP12 if (TS < 0): TS=0 else: TS=sqrt(TS) ANGLE=math.pi/2.0-atan2(DP12,TS) PX3=PY1*PZ2-PY2*PZ1 PY3=PZ1*PX2-PZ2*PX1 PZ3=PX1*PY2-PX2*PY1 DP233=PX3*DX23+PY3*DY23+PZ3*DZ23 if (DP233 > 0): ANGLE=-ANGLE ANGLE=math.degrees(ANGLE) if (ANGLE > 180): ANGLE=ANGLE-360 if (ANGLE < -180): ANGLE=ANGLE+360 return ANGLE cmd.extend("pucker",pucker) > Subject: RE: [PyMOL] get_dihedral bottleneck > Date: Wed, 1 Apr 2009 10:23:33 -0700 > From: war...@delsci.com > To: magic...@hotmail.com; pymol-users@lists.sourceforge.net > > Sean, > > get_dihedral is expensive because, in the general case, it requires > evaluation of four atom selections - a process which is essentially order N > with respect to the number of atoms currently loaded inside PyMOL. > > As you've discovered, the easiest way to optimize performance is therefore to > minimize the number of atoms present in PyMOL by removing all uninvolved > atoms. > > We are working on ways of improving selection performance, at least one of > which will be present in 1.2 (the "domain" argument to the select command). > > Cheers, > Warren > ________________________________________ > From: Sean Law [mailto:magic...@hotmail.com] > Sent: Tuesday, March 31, 2009 1:48 PM > To: pymol-users@lists.sourceforge.net > Subject: [PyMOL] get_dihedral bottleneck > > Hi PyMol-Community, > > I have been looking for a PyMOL command to calculate the ribose sugar pucker > information (in DNA) but have not found anything. Thus, I began writing a > simple Python script that is supposed to take a selection, determine whether > or not it contains a ribose sugar ring, and then calculate the phase, > amplitude, and pucker orientation for each nucleotide residue within the > selection. The completed script works just fine (see the script below) but > it slows down significantly in performance when calling cmd.get_dihedral (at > least, my crude debugging method using print statements has identified the > culprit to be the get_dihedral command) with a LARGE protein-DNA complex > (160,000 atoms) loaded. However, if I only load the DNA (with no protein), > the performance is lightning fast. Can anybody help me improve the overall > performance or suggest an alternative? Thanks in advance! > > Sean > > > > > ========================================================= > > from pymol.cgo import * # get constants > from math import * > from pymol import cmd > > def pucker(selection): > > # Author: Sean Law > # Institute: Michigan State University > # E-mail: s...@msu.edu > > obj_name = selection > objects = cmd.get_names(selection) > for obj in objects: > if (obj == selection): > obj_name=obj > sel=cmd.get_model(selection) > first=1 > old=" " > oldchain=" " > residue={} > count=0 > for atom in sel.atom: > new=atom.chain+" "+str(atom.resi) > newchain=atom.chain+" "+atom.segi > if (not (oldchain == newchain) and first): > print " " #Blank line to separate chain output > print "%6s %6s %8s Residue" % ("Phase", "Amp", "Pucker") > if (not(new == old) and (not first)): > #Check that all 5 atoms exist > if(len(residue) == 5): > #Calculate pucker > info = pseudo(residue) > print info+" "+old > else: > print "There is no sugar in this residue" > if (not (oldchain == newchain)): > print " " #Blank line to separate chain output > print "%6s %6s %8s Residue" % ("Phase", "Amp", "Pucker") > #Clear values > residue={} > #Store new value > store_atom(atom,residue,obj_name) > else: > store_atom(atom,residue,obj_name) > first=0 > old=new > oldchain=newchain > #Final Residue > #Calculate dihedrals for final residue > if (len(residue) == 5): > #Calculate pucker for final residue > info = pseudo(residue) > print info+" "+old > else: > print "There is no sugar in this residue" > return > > def sele_exists(sele): > return sele in cmd.get_names("selections"); > > def pseudo(residue): > other=2*(sin(math.radians(36.0))+sin(math.radians(72.0))) > > theta0=cmd.get_dihedral(residue['C4*'],residue['O4*'],residue['C1*'],residue['C2*']) > > theta1=cmd.get_dihedral(residue['O4*'],residue['C1*'],residue['C2*'],residue['C3*']) > > theta2=cmd.get_dihedral(residue['C1*'],residue['C2*'],residue['C3*'],residue['C4*']) > > theta3=cmd.get_dihedral(residue['C2*'],residue['C3*'],residue['C4*'],residue['O4*']) > > theta4=cmd.get_dihedral(residue['C3*'],residue['C4*'],residue['O4*'],residue['C1*']) > > #phase=atan2((theta4+theta1)-(theta3+theta0),2*theta2*(sin(math.radians(36.0))+sin(math.radians(72.0)))) > phase=atan2((theta4+theta1)-(theta3+theta0),theta2*other) > amplitude=theta2/cos(phase) > phase=math.degrees(phase) > if (phase < 0): > phase=phase+360 # 0 <= Phase < 360 > #Determine pucker > if (phase < 36): > pucker = "C3'-endo" > elif (phase < 72): > pucker = "C4'-exo" > elif (phase <108): > pucker = "O4'-endo" > elif (phase < 144): > pucker = "C1'-exo" > elif (phase < 180): > pucker = "C2'-endo" > elif (phase < 216): > pucker = "C3'-exo" > elif (phase < 252): > pucker = "C4'-endo" > elif (phase < 288): > pucker = "O4'-exo" > elif (phase < 324): > pucker = "C1'-endo" > elif (phase < 360): > pucker = "C2'-exo" > else: > pucker = "Phase is out of range" > info = "%6.2f %6.2f %8s" % (phase, amplitude, pucker) > return info > > > def store_atom(atom,residue,obj_name): > if (atom.name == "O4'" or atom.name == "O4*"): > residue['O4*'] = str(atom_sele(atom,obj_name)) > elif (atom.name == "C1'" or atom.name == "C1*"): > residue['C1*'] = str(atom_sele(atom,obj_name)) > elif (atom.name == "C2'" or atom.name == "C2*"): > residue['C2*'] = str(atom_sele(atom,obj_name)) > elif (atom.name == "C3'" or atom.name == "C3*"): > residue['C3*'] = str(atom_sele(atom,obj_name)) > elif (atom.name == "C4'" or atom.name == "C4*"): > residue['C4*'] = str(atom_sele(atom,obj_name)) > return > > def atom_sele(atom,obj_name): > atom_sele = "" > if (not (obj_name == "")): > atom_sele = atom_sele+obj_name+" & " > if (not (atom.segi == "")): > atom_sele = atom_sele+"segi "+atom.segi+" & " > if (not (atom.chain == "")): > atom_sele = atom_sele+"chain "+atom.chain+" & " > if (not (atom.resn == "")): > atom_sele = atom_sele+"resn "+atom.resn+" & " > if (not (atom.resi == "")): > atom_sele = atom_sele+"resi "+str(atom.resi)+" & " > if (not (atom.name == "")): > atom_sele = atom_sele+"name "+atom.name > return atom_sele > > cmd.extend("pucker",pucker) > ________________________________________ > Communicate, update and plan on Windows Live Messenger. Get started today. > _________________________________________________________________ Create a cool, new character for your Windows Live⢠Messenger. http://go.microsoft.com/?linkid=9656621