This PyMol script is capable of generating a set of a molecule’s conformations, which can be further used to calculate their potential energy or any other properties. The script enables one to vary several internal degrees of freedom (DOFs) synchronously as well as scanning over a multidimensional grid in a space of DOFs.
To use the script save the code into a file (say «scan.py»), and issue the following command from PyMOL:
PyMOL> run /path/to/scan.py
Now load the molecule into PyMOL:
PyMOL> load mymol.pdb, mymol
The molecule is loaded into the object «mymol»
You need to know atoms IDs in order to define the DOFs of interest. Label atoms with their IDs using the following command:
PyMOL> label mymol, ID
You also need to define a set of atoms to move. Select the atoms of interest using a mouse or any other method. Assume the selection name is «sele».
The following command generates a set of conformations by displacing a group of the atoms along the line passing through the atoms with IDs 3 and 7:
PyMOL> scan mymol, lin(3 7) sele -1.0 3.0 5
As a result 5 new states of the object «mymol» are created. The first state of the object contains the original conformation. The second state of the «mymol» contains the conformation with «sele» group of atoms displaced along the line in backward direction by -1.0 angtsrom. Further states contain the conformations with «sele» atoms shifted gradually to +3.0 position along the reference line.
The following command rotates the «sele» atoms around the line passing through atoms with IDs 3 and 7 by 360 degrees in 36 steps (torsion angle scan):
PyMOL> scan mymol, dih(3 7) sele 0 360 37
The following command scans the covalent angle defined by atoms 1, 3 and 7. The angle varies from -30 deg to 30 deg from original position in 30 steps:
PyMOL> scan mymol, ang(1 3 7) sele -30 30 31
The following command varies two former DOFs simultaneously in 100 steps:
PyMOL> scan mymol, lin(3 7) sele -1.0 3.0 & dih(3 7) sele 0 360 100
The following command scans over a 2-dimensional grid in the space of two DOFs by displacing «sele» atoms along vector 3->7 and rotating them around vector 3->7:
PyMOL> scan mymol, lin(3 7) sele -1.0 3.0 5 & dih(3 7) sele 0 360 100
The general invocation pattern of the «scan» command is as follows:
Usage: scan obj, scan_str obj: object to modify scan_str: coord_type(atom1 atom2 ...) sele_name start stop [& coord_type(atom1 atom2 ...) ... [& ...]] nsteps [| coord_type ...]
Here is the scan.py code:
from pymol import cmd, stored import re import numpy as np #from scandim import * class scandim: "Scan dimension class" def __init__(self, obj, type, atlist, sele, start, stop, npt, friend=None): self.obj = obj self.type = type self.atlist = atlist self.sele = sele self.vals = [] self.i = 0 for i in range(0,int(npt)): self.vals.append(float(start) + i*(float(stop) - float(start))/(int(npt)-1)) self.friend = friend # recursive list of dependent coords def M(self, S=1): t = self.type stored.m = {'H':1.01, 'C':12.01, 'N':14.01, 'O':16., 'S':32.06, 'Fe':55.84} stored.atm = [] for id in self.atlist: cmd.iterate_state(S, self.obj + " & id " + str(id), "stored.atm.append(np.array([x, y, z]))") stored.M = 0.0 if t == 'lin': cmd.iterate_state(S, self.sele, 'stored.M += stored.m[elem]') elif t in ['ang', 'dih']: if t == 'ang': v1 = stored.atm[1] - stored.atm[0] v2 = stored.atm[2] - stored.atm[1] v = np.cross(v1,v2) v /= np.sqrt((v**2).sum()) else: v = stored.atm[1] - stored.atm[0] v /= np.sqrt((v**2).sum()) stored.v = v stored.o = stored.atm[1] cmd.iterate_state(S, self.sele, 'a = np.array([x,y,z])-stored.o; l = np.cross(a, stored.v); stored.M += stored.m[elem] * np.dot(l,l)') else: raise NameError, "Unknown coord type '%s'" % self.type return t,stored.M def inc(self): if self.friend: self.friend.inc() self.i += 1 if self.i >= len(self.vals): self.i = 0 return True else: return False def apply(self, state): obj=self.obj stored.atm = [] for id in self.atlist: cmd.iterate_state(state, obj + " & id " + str(id), "stored.atm.append(np.array([x, y, z]))") if self.type in ['lin','dih']: o = stored.atm[1] v = stored.atm[1] - stored.atm[0] v /= np.sqrt((v**2).sum()) if self.type == 'lin': v *= self.vals[self.i] cmd.translate([v[0],v[1],v[2]], selection=self.sele, state=state, camera=0) else: cmd.rotate([v[0],v[1],v[2]], self.vals[self.i], selection=self.sele, origin=[o[0],o[1],o[2]], state=state, camera=0) elif self.type == 'ang': o = stored.atm[1] v1 = stored.atm[1] - stored.atm[0] v2 = stored.atm[2] - stored.atm[1] v = np.cross(v1,v2) v /= np.sqrt((v**2).sum()) cmd.rotate([v[0],v[1],v[2]], self.vals[self.i], selection=self.sele, origin=[o[0],o[1],o[2]], state=state, camera=0) else: raise NameError, "Unknown coord type '%s'" % self.type if self.friend: self.friend.apply(state) return def scan(obj, scanstr, save="" ): """ Usage: scan obj, scan_str obj: object to modify scan_str: coord_type(atom1 atom2 ...) sele_name start stop [& coord_type(atom1 atom2 ...) ... [& ...]] nsteps [| coord_type ...] """ # parse input and generate list of independent coordinates dims = [] scanpat = "^\s*(?P<typ>\w+)\s*\((?P<atoms>[\d\s]+)\)\s+(?P<mov>[\w\d_-]+)\s+(?P<start>-?\d+(\.\d+)?)\s+(?P<stop>-?\d+(\.\d+)?)\s+(?P<nstep>-?\d+)\s*$" friendpat = "^\s*(?P<typ>\w+)\s*\((?P<atoms>[\d\s]+)\)\s+(?P<mov>[\w\d_-]+)\s+(?P<start>-?\d+(\.\d+)?)\s+(?P<stop>-?\d+(\.\d+)?)\s*$" # split and iterate independent coords for s in scanstr.split('|'): # deal with dependent coords ss = s.split('&') # last of dependent coordinates ends with numpoints (parse using 'scanpat' pattern) i = len(ss)-1 m = re.match(scanpat, ss[i]) if (m): nstep = m.groupdict()['nstep'] friend = scandim(obj, m.groupdict()['typ'], m.groupdict()['atoms'].split(), m.groupdict()['mov'], m.groupdict()['start'], m.groupdict()['stop'], nstep) else: raise NameError, "Unrecognized scan string '%s'" % ss[i] # form recursive list of dependent coords (using 'friendpat' pattern) while (i): i -= 1 m = re.match(friendpat, ss[i]) if m: friend = scandim(obj, m.groupdict()['typ'], m.groupdict()['atoms'].split(), m.groupdict()['mov'], m.groupdict()['start'], m.groupdict()['stop'], nstep, friend) else: raise NameError, "Unrecognized scan string '%s'" % ss[i] # add first dependent coord (last iterated) to the list of independent coords dims.append(friend) # calculate total number of points NP = 1 for i in range(len(dims)): NP *= len(dims[i].vals) print "Total number of points: %d" % NP # iterate through mesh points for i in range(NP): cmd.create(obj, obj, 1, i+2) # apply transformations for dim in dims: dim.apply(i+2) # save conformation if requested if save: log = open(save + ".txt", 'w') print >>log, scanstr log.close() fname=save for dim in dims: fname += "_" + ("%02d" % dim.i) fname += ".pdb" cmd.save(fname, obj, i+2, "pdb") # increment indices d = len(dims)-1 while (dims[d].inc()): d -= 1 if d < 0: d = len(dims)-1 cmd.extend("scan", scan)