#!/usr/bin/python # Copyright 2005, 2007 Nanorex, Inc. See LICENSE file for details. """Nanotube generation tool for nanoENGINEER-1 Usage: nanotube.py where (n,m) is the chirality of the nanotube, and the length is in nanometers. The resulting MMP file is written to standard output. According to Dresselhaus et al, the bond lengths in the nanotube are all 1.421 angstroms. The nanotube is passivated with oxygens and hydrogens at the ends. """ # Some nice references on nanotube geometry and properties: # http://physicsweb.org/articles/world/11/1/9 # http://physicsweb.org/articles/world/11/1/9/1/world-11-1-9-1 import sys import string import math sqrt3 = 3 ** 0.5 def prettyClose(u,v): # floats are never perfectly equal return (u-v)**2 < 1.0e-10 def prettyClose3(x1,y1,z1,x2,y2,z2): return (prettyClose(x1, x2) and prettyClose(y1, y2) and prettyClose(z1, z2)) class Chirality: # Nanotube bond length according to Dresselhaus, M. S., # Dresselhaus, G., Eklund, P. C. "Science of Fullerenes and Carbon # Nanotubes" Academic Press: San Diego, CA, 1995; pp. 760. BONDLENGTH = 1.421 # angstroms def __init__(self, n, m): self.n, self.m = n, m x = (n + 0.5 * m) * sqrt3 y = 1.5 * m angle = math.atan2(y, x) twoPiRoverA = (x**2 + y**2) ** .5 AoverR = (2 * math.pi) / twoPiRoverA self.__cos = math.cos(angle) self.__sin = math.sin(angle) # time to get the constants s, t = self.x1y1(0,0) u, v = self.x1y1(1./3, 1./3) w, x = self.x1y1(0,1) F = (t - v)**2 G = 2 * (1 - math.cos(AoverR * (s - u))) H = (v - x)**2 J = 2 * (1 - math.cos(AoverR * (u - w))) L = self.BONDLENGTH denom = F * J - G * H self.R = (L**2 * (F - H) / denom) ** .5 self.B = (L**2 * (J - G) / denom) ** .5 self.A = self.R * AoverR class ConstError(TypeError): pass def __setattr__(self,name,value): # Don't touch my precious constants if self.__dict__.has_key(name): raise self.ConstError, "Can't reassign " + name self.__dict__[name] = value def x1y1(self, n, m): c, s = self.__cos, self.__sin x = (n + .5*m) * sqrt3 y = 1.5 * m x1 = x * c + y * s y1 = -x * s + y * c return (x1, y1) def mlimits(self, y3min, y3max, n): if y3max < y3min: y3min, y3max = y3max, y3min B, c, s = self.B, self.__cos, self.__sin P = sqrt3 * B * s Q = 1.5 * B * (c - s / sqrt3) m1, m2 = (y3min + P * n) / Q, (y3max + P * n) / Q return int(m1-1.5), int(m2+1.5) def xyz(self, n, m): x1, y1 = self.x1y1(n, m) x2, y2 = self.A * x1, self.B * y1 R = self.R x3 = R * math.sin(x2/R) z3 = R * math.cos(x2/R) return (x3, y2, z3) class Molecule: def __init__(self): self.atoms = [ ] def add(self, elt, x, y, z): for a2 in self.atoms: assert not prettyClose3(x, y, z, a2.x, a2.y, a2.z) class Atom: pass a = Atom() a.element = elt a.x, a.y, a.z = x, y, z self.atoms.append(a) def bondsForAtom(self, atomIndex): # atomIndex goes from 1..N, not 0..N-1 lst = [ ] for k in range(len(self.bonds)): i, j = self.bonds[k] if i == atomIndex or j == atomIndex: lst.append(k) return lst def passivate(self): for k in range(len(self.atoms)): lst = self.bondsForAtom(k + 1) if len(lst) == 2: self.atoms[k].element = "O" elif len(lst) == 1: self.atoms[k].element = "H" def makeBonds(self, maxlen): self.bonds = [ ] n = len(self.atoms) for i in range(n): a = self.atoms[i] ax, ay, az = a.x, a.y, a.z for j in range(i+1,n): b = self.atoms[j] dist = ((ax - b.x) ** 2 + (ay - b.y) ** 2 + (az - b.z) ** 2) ** .5 if dist <= maxlen: self.bonds.append((i+1, j+1)) def mmp(self): outf = sys.stdout outf.write("""mmpformat 050920 required kelvin 300 group (View Data) info opengroup open = False egroup (View Data) group (Untitled) info opengroup open = True mol (Nanotube.1) def """) for i in range(len(self.atoms)): a = self.atoms[i] enum = {"C": 6, "O": 8, "H": 1}[a.element] outf.write("atom %d (%d) (%d, %d, %d) vdw\n" % (i+1, enum, int(1000*a.x), int(1000*a.y), int(1000*a.z))) if a.element == "C": outf.write("info atom atomtype = sp2\n") r = "bonda" elif a.element in ("O", "H"): outf.write("info atom atomtype = sp3\n") r = "bond1" else: raise Exception, "unknown element" bondlist = self.bondsForAtom(i+1) for k in bondlist: a1, a2 = self.bonds[k] if a2 < i+1: r += " " + repr(a2) elif a1 < i+1: r += " " + repr(a1) outf.write(r + "\n") outf.write("""egroup (Untitled) end1 group (Clipboard) info opengroup open = False egroup (Clipboard) end molecular machine part Untitled """) outf.close() def rasmol(self): import os filename = "/tmp/foo.xyz" outf = open(filename, "w") r = repr(len(self.atoms)) + "\n" r += "glop\n" for a in self.atoms: r += a.element + (" %g %g %g\n" % (a.x, a.y, a.z)) outf.write(r) outf.close() os.system("rasmol -xyz " + filename) def test(self): mindist = None maxdist = None for a, b in self.bonds: a, b = self.atoms[a-1], self.atoms[b-1] dist = ((a.x - b.x) ** 2 + (a.y - b.y) ** 2 + (a.z - b.z) ** 2) ** .5 if mindist == None or dist < mindist: mindist = dist if maxdist == None or dist > maxdist: maxdist = dist return (mindist, maxdist) if (__name__ == '__main__'): if len(sys.argv) < 4: sys.stderr.write(__doc__) sys.exit(1) n = string.atoi(sys.argv[1]) m = string.atoi(sys.argv[2]) length = string.atof(sys.argv[3]) chirality = Chirality(n,m) M = Molecule() for n in range(chirality.n): mmin, mmax = chirality.mlimits(-.5 * length, .5 * length, n) for m in range(mmin-1, mmax+1): x, y, z = chirality.xyz(n, m) if -.5 * length <= y <= .5 * length: M.add("C", x, y, z) x, y, z = chirality.xyz(n+1./3, m+1./3) if -.5 * length <= y <= .5 * length: M.add("C", x, y, z) M.makeBonds(Chirality.BONDLENGTH * 1.2) M.passivate() M.mmp()