summaryrefslogtreecommitdiff
path: root/cad/tests/nanotube.py
blob: 77d1923c4b9260002a9982d9381c6714aa7db5a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
 #!/usr/bin/python

# Copyright 2005, 2007 Nanorex, Inc.  See LICENSE file for details. 
"""Nanotube generation tool for nanoENGINEER-1
Usage:
    nanotube.py <n> <m> <length>
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()