The script generates nanotubes of arbitrary structure. See related media in the end of the page.
To use it, save script to some file, then load into PyMol with the command:
PyMol> run /path/to/script.py
then issue the command:
PyMol> ntgen obj_name, n, m, l
This will produce an object «obj_name», containing SWNT characterized by two indices (n,m) as described here). The length of SWNT is defined by the forth argument l.
The script is also capable of generating valid GROMACS structure and topology files for further molecular mechanical modelling. To generate them issue the following command
PyMol> ntgen obj_name, n, m, l, filename
This last command will produce «filename.gro» and «filename.top» files.
Here comes the script:
# Nanotubes generating script for PyMol by piton at erg.biophys.msu.ru
import cmd
from math import cos,sin,pi,ceil,floor, acos
from chempy import models, cpv
class cell:
def __init__(self,N,M):
l = 1.42
a = 2.*l*cos(pi*30./180.)
self.a1 = [a, 0., 0.]
self.a2 = [a*cos(pi*60./180.), a*sin(pi*60./180.), 0.]
self.crds = [[0., 0., 0.],
[l*cos(pi*30./180.), l*sin(pi*30./180.), 0]]
self.Ch = cpv.add(cpv.scale(self.a1, N), cpv.scale(self.a2, M))
self.lenCh = cpv.length(self.Ch)
self.Ch = cpv.normalize(self.Ch)
self.T = cpv.normalize(cpv.cross_product(self.Ch, cpv.cross_product(self.a1, self.a2)))
self.radius = self.lenCh/math.pi/2.
def get_crds(self, n=0, m=0):
d = cpv.add(cpv.scale(self.a1,n),cpv.scale(self.a2,m))
crds = []
for crd in self.crds:
v = cpv.add(crd, d)
ang = 2. * math.pi * cpv.dot_product(v,self.Ch) / self.lenCh
r = cpv.dot_product(v, self.T)
crds.append([self.radius*math.cos(ang), self.radius*math.sin(ang),r])
return crds
def new_at(crd, nm, sym, typ, charge=0.0):
at = chempy.Atom()
at.charge = charge
at.name = nm
at.symbol = sym
at.type = typ
at.coord = crd
at.hetatm = False
at.resn = 'CNT'
at.resi = '1'
at.resi_number = 1
at.bonds = []
return at
def ntgen(obj, sN,sM,sL, save = None):
"""
Usage: ntgen obj_name, n, m, l
"""
bdist = 2.0
rCH = 1.1
N,M,L = int(sN), int(sM), int(sL)
if (M > N):
_N = N
N = M
M = _N
rN,rM,rL = float(N), float(M), float(L)
C = cell(N,M)
## Generate atoms
nt = models.Indexed()
# for j in range(L):
# for i in range(N):
# n,m = i + (j*M)/N, (i*M)/N -j
iat = 0
for n in range(0,N+M*L/N):
if M > 0:
m_start = max(int(ceil(-rL*(1 + rM**2/rN**2) + rM*n/rN)), int(floor(-rN*n/rM)) )
m_stop = min(int(floor(rM*n/rN)), int(floor(rM+rN**2/rM-rN*n/rM)))
else:
m_start, m_stop = -L, 0
for m in range(m_start, m_stop):
for crd in C.get_crds(n,m):
nt.add_atom(new_at(crd, "C%d" % (iat+1), 'C', 'opls_145'))
iat += 1
## Add bonds
for i in range(1,len(nt.atom)):
for j in range(i):
d = cpv.distance(nt.atom[i].coord, nt.atom[j].coord)
if d <= bdist:
b = chempy.Bond()
b.index = [j,i]
nt.add_bond(b)
nt.atom[i].bonds.append(j)
nt.atom[j].bonds.append(i)
if d <= 0.5:
print "WARNING: Atoms #%d and #%d are too close" % (i,j)
## Add protons
nat = iat
for iat in range(len(nt.atom)):
if len(nt.atom[iat].bonds) == 2:
r0 = nt.atom[iat].coord
r1 = cpv.sub(nt.atom[nt.atom[iat].bonds[0]].coord, r0)
r2 = cpv.sub(nt.atom[nt.atom[iat].bonds[1]].coord, r0)
rh = cpv.add(r0, cpv.scale(cpv.normalize(cpv.add(r1,r2)), -rCH))
nt.add_atom(new_at(rh, "H%d" % (nat+1), 'H', 'opls_146', 0.115))
nt.atom[iat].charge = -0.115
b = chempy.Bond()
b.index = [iat,nat]
nt.add_bond(b)
nt.atom[iat].bonds.append(nat)
nt.atom[nat].bonds.append(iat)
nat += 1
if len(nt.atom[iat].bonds) < 2:
print "WARNING: Lone carbon detected, id #%d" % iat
# Center system
# for iat in range(len(nt.atom)): print iat, nt.atom[iat].bonds
C = [0.,0.,0.]
for iat in range(len(nt.atom)): C = cpv.add(C, nt.atom[iat].coord)
C = cpv.scale(C, -1./len(nt.atom))
for iat in range(len(nt.atom)): nt.atom[iat].coord = cpv.add(nt.atom[iat].coord, C)
if save:
## Save GRO
f = open(save+'.gro', 'w')
print >>f, "SWNT %d-%d-%d" % (N,M,L)
print >>f, "%5d" % len(nt.atom)
for iat in range(len(nt.atom)):
print >>f, "%5d%5s%5s%5d%8.3f%8.3f%8.3f%8.3f%8.3f%8.3f" % (1, 'CNT', nt.atom[iat].name, iat+1, nt.atom[iat].coord[0]/10., nt.atom[iat].coord[1]/10., nt.atom[iat].coord[2]/10., 0.0, 0.0, 0.0)
print >>f, "0.0 0.0 0.0"
f.close()
## Generate topology
f = open(save+'.itp', 'w')
## Atoms
print >>f, """
[ moleculetype ]
; name nrexcl
CNT 3
[ atoms ]
; nr type resnr residu atom cgnr charge"""
for iat in range(len(nt.atom)):
print >>f, "%-5d %-10s %-5d %-10s %-10s %-5d %8.3f" % (iat+1, nt.atom[iat].type, 1, 'CNT', nt.atom[iat].name, iat+1, nt.atom[iat].charge)
## Bonds
print >>f, """
[ bonds ]
; i j"""
for iat in range(len(nt.atom)):
for ib in nt.atom[iat].bonds:
if ib < iat:
print >>f, "%-5d %-5d 1" % (iat+1, ib+1)
## Angles
print >>f, """
[ angles ]
; i j k 1"""
for j in range(len(nt.atom)):
for i in nt.atom[j].bonds:
for k in nt.atom[j].bonds:
if k < i:
print >>f, "%-5d %-5d %-5d 1" % (i+1, j+1, k+1)
## Dihedrals
print >>f, """
[ dihedrals ]
; i j k l 2 Theta k"""
for b in nt.bond:
j,k = b.index[0], b.index[1]
if len(nt.atom[j].bonds) >= 2 and len(nt.atom[k].bonds) >= 2:
for i in nt.atom[j].bonds:
for l in nt.atom[k].bonds:
if i != k and l != j:
rjk = cpv.sub(nt.atom[k].coord, nt.atom[j].coord)
rji = cpv.sub(nt.atom[i].coord, nt.atom[j].coord)
rkl = cpv.sub(nt.atom[l].coord, nt.atom[k].coord)
nijk = cpv.normalize(cpv.cross_product(rji, rjk))
njkl = cpv.normalize(cpv.cross_product(rkl, rjk))
cosa = cpv.dot_product(nijk, njkl)
if cosa > 1.0: cosa = 1.0
if cosa < -1.0: cosa = -1.0
# print i,j,k,l,cosa
a = 180*acos(cosa)/pi
# print a
if a > 90.0:
print >>f, "%-5d %-5d %-5d %-5d 2 %-8.3f 20.0" % (i+1, j+1, k+1, l+1, a)
f.close()
## Load object
# for iat in range(len(nt.atom)): nt.atom[iat].id = iat+1
# nt.update_index()
cmd.delete(obj)
cmd.load_model(nt, obj)
cmd.extend("ntgen", ntgen)
