summaryrefslogtreecommitdiff
path: root/cad/src/geometry/InternalCoordinatesToCartesian.py
blob: 84eca0840b8c0d313803f4ab730a598f25f1c790 (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
# Copyright 2008 Nanorex, Inc.  See LICENSE file for details.

"""
InternalCoordinatesToCartesian.py

@author: EricM from code by Piotr
@version:$Id$
@copyright: 2008 Nanorex, Inc.  See LICENSE file for details.

"""

from geometry.VQT import V

from Numeric import zeros, Float, cos, sin, sqrt, pi

DEG2RAD = (pi/180.0)

class InternalCoordinatesToCartesian(object):
    """
    Translator to convert internal coordinates (such as those in AMBER
    .in files, as described by http://amber.scripps.edu/doc/prep.html)
    into cartesian coordinates.  Internal coordinates represent the
    location of each point relative to three other already defined
    points.  Indicies for those three points are given, along with
    distances, angles, and torsion angles among the given points.
    Each point in either cartesian or internal coordinates has three
    degrees of freedom, but they are specified differently between the
    two systems.

    To use:

    Create the translator object.  If you wish to add the resulting
    structure onto a particular location on an existing structure, you
    should pass in suitable cartesian coordinates for the
    initialCoordinates, which define the position and orientation of
    the coordinate system used to translate the points.  If you leave
    this as None, the results will begin at the origin in a standard
    orientation.

    For each set of internal coordinates represenging one point, call
    addInternal() passing in the internal coordinates.

    For each point so defined, you can retrieve the cartesian
    equivalent coordinates with a call to getCartesian().
    """

    def __init__(self, length, initialCoordinates):
        """
        @param length: How many points will be converted, including
                       the 3 dummy points.

        @param initialCoordinates: Either None, or an array of three
                                   sets of x, y, z cartesian
                                   coordinates.  These are the
                                   coordinates of the three dummy
                                   points, indices 1, 2, and 3, which
                                   are defined before any actual data
                                   points are added.
        """
        self._coords = zeros([length+1, 3], Float)
        if (initialCoordinates):
            self._coords[1][0] = initialCoordinates[0][0]
            self._coords[1][1] = initialCoordinates[0][1]
            self._coords[1][2] = initialCoordinates[0][2]

            self._coords[2][0] = initialCoordinates[1][0]
            self._coords[2][1] = initialCoordinates[1][1]
            self._coords[2][2] = initialCoordinates[1][2]

            self._coords[3][0] = initialCoordinates[2][0]
            self._coords[3][1] = initialCoordinates[2][1]
            self._coords[3][2] = initialCoordinates[2][2]
        else:
            self._coords[1][0] = -1.0
            self._coords[1][1] = -1.0
            self._coords[1][2] =  0.0

            self._coords[2][0] = -1.0
            self._coords[2][1] =  0.0
            self._coords[2][2] =  0.0

            self._coords[3][0] =  0.0
            self._coords[3][1] =  0.0
            self._coords[3][2] =  0.0

        self._nextIndex = 4

    def addInternal(self, i, na, nb, nc, r, theta, phi):
        """
        Add another point, given its internal coordinates.  Once added
        via this routine, the cartesian coordinates for the point can
        be retrieved with getCartesian().

        @param i: Index of the point being added.  After this call, a
                  call to getCartesian with this index value will
                  succeed.  Index values less than 4 are ignored.
                  Index values should be presented here in sequence
                  beginning with 4.

        @param na: Index value for point A.  Point 'i' will be 'r'
                   distance units from point A.

        @param nb: Index value for point B.  Point 'i' will be located
                   such that the angle i-A-B is 'theta' degrees.

        @param nc: Index value for point C.  Point 'i' will be located
                      such that the torsion angle i-A-B-C is 'torsion'
                      degrees.

        @param r: Radial distance (in same units as resulting
                  cartesian coordinates) between A and i.

        @param theta: Angle in degrees of i-A-B.

        @param phi: Torsion angle in degrees of i-A-B-C
        """

        if (i < 4):
            return

        if (i != self._nextIndex):
            raise IndexError, "next index is %d not %r" % (self._nextIndex, i)

        cos_theta = cos(DEG2RAD * theta)
        xb = self._coords[nb][0] - self._coords[na][0]
        yb = self._coords[nb][1] - self._coords[na][1]
        zb = self._coords[nb][2] - self._coords[na][2]
        rba = 1.0 / sqrt(xb*xb + yb*yb + zb*zb)

        if abs(cos_theta) >= 0.999:
            # Linear case
            # Skip angles, just extend along A-B.
            rba = r * rba * cos_theta
            xqd = xb * rba
            yqd = yb * rba
            zqd = zb * rba
        else:
            xc = self._coords[nc][0] - self._coords[na][0]
            yc = self._coords[nc][1] - self._coords[na][1]
            zc = self._coords[nc][2] - self._coords[na][2]

            xyb = sqrt(xb*xb + yb*yb)

            inv = False
            if xyb < 0.001:
                # A-B points along the z axis.
                tmp = zc
                zc = -xc
                xc = tmp
                tmp = zb
                zb = -xb
                xb = tmp
                xyb = sqrt(xb*xb + yb*yb)
                inv = True

            costh = xb / xyb
            sinth = yb / xyb
            xpc = xc * costh + yc * sinth
            ypc = yc * costh - xc * sinth
            sinph = zb * rba
            cosph = sqrt(abs(1.0- sinph * sinph))
            xqa = xpc * cosph + zc * sinph
            zqa = zc * cosph - xpc * sinph
            yzc = sqrt(ypc * ypc + zqa * zqa)
            if yzc < 1e-8:
                coskh = 1.0
                sinkh = 0.0
            else:
                coskh = ypc / yzc
                sinkh = zqa / yzc

            sin_theta = sin(DEG2RAD * theta)
            sin_phi = -sin(DEG2RAD * phi)
            cos_phi = cos(DEG2RAD * phi)

            # Apply the bond length.
            xd = r * cos_theta
            yd = r * sin_theta * cos_phi
            zd = r * sin_theta * sin_phi

            # Compute the atom position using bond and torsional angles.
            ypd = yd * coskh - zd * sinkh
            zpd = zd * coskh + yd * sinkh
            xpd = xd * cosph - zpd * sinph
            zqd = zpd * cosph + xd * sinph
            xqd = xpd * costh - ypd * sinth
            yqd = ypd * costh + xpd * sinth

            if inv:
                tmp = -zqd
                zqd = xqd
                xqd = tmp

        self._coords[i][0] = xqd + self._coords[na][0]
        self._coords[i][1] = yqd + self._coords[na][1]
        self._coords[i][2] = zqd + self._coords[na][2]
        self._nextIndex = self._nextIndex + 1

    def getCartesian(self, index):
        """
        Return the cartesian coordinates for a point added via
        addInternal().  Units are the same as for the 'r' parameter of
        addInternal().
        """
        if (index < self._nextIndex and index > 0):
            return V(self._coords[index][0],
                     self._coords[index][1],
                     self._coords[index][2])
        raise IndexError, "%r not defined" % index