File: angles.py

package info (click to toggle)
sasmodels 0.99-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 14,760 kB
  • sloc: python: 22,292; ansic: 6,140; makefile: 149; sh: 91
file content (246 lines) | stat: -rwxr-xr-x 8,648 bytes parent folder | download | duplicates (4)
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#!/usr/bin/env python
"""
Generate code for orientation transforms using symbolic algebra.

To make it easier to generate correct transforms for oriented shapes, we
use the sympy symbolic alegbra package to do the matrix multiplication.
The transforms are displayed both using an ascii math notation, and as
C or python code which can be pasted directly into the kernel driver.

If ever we decide to change conventions, we simply need to adjust the
order and parameters to the rotation matrices.  For display we want to
use forward transforms for the mesh describing the shape, first applying
jitter, then adjusting the view.  For calculation we know the effective q
so we instead need to first unwind the view, using the inverse rotation,
then undo the jitter to get the q to calculate for the shape in its
canonical orientation.

Set *OUTPUT* to the type of code you want to see: ccode, python, math
or any combination.
"""

from __future__ import print_function

import codecs
import sys
import re

import sympy as sp
from sympy import pi, sqrt, sin, cos, Matrix, Eq

# Select output
OUTPUT = ""
OUTPUT = OUTPUT + "ccode"
#OUTPUT = OUTPUT + "python "
OUTPUT = OUTPUT + "math "
REUSE_SINCOS = True
QC_ONLY = True # show only what is needed for dqc in the symmetric case

# include unicode symbols in output, even if piping to a pager
if sys.version_info[0] < 3:
    sys.stdout = codecs.getwriter('utf8')(sys.stdout)
sp.init_printing(use_unicode=True)

def subs(s):
    """
    Transform sympy generated code to follow sasmodels naming conventions.
    """
    if REUSE_SINCOS:
        s = re.sub(r'(phi|psi|theta)\^\+', r'\1', s)  # jitter rep:  V^+ => V
    s = re.sub(r'([a-z]*)\^\+', r'd\1', s)  # jitter rep:  V^+ => dV
    s = re.sub(r'(cos|sin)\(([a-z]*)\)', r'\1_\2', s)  # cos(V) => cos_V
    s = re.sub(r'pow\(([a-z]*), 2\)', r'\1*\1', s)  # pow(V, 2) => V*V
    return s

def comment(s):
    r"""
    Add a comment to the generated code.  Use '\n' to separate lines.
    """
    if 'ccode' in OUTPUT:
        for line in s.split("\n"):
            print("// " + line if line else "")
    if 'python' in OUTPUT:
        for line in s.split("\n"):
            print("    ## " + line if line else "")

def vprint(var, vec, comment=None, post=None):
    """
    Generate assignment statements.

    *var* could be a single sympy symbol or a 1xN vector of symbols.

    *vec* could be a single sympy expression or a 1xN vector of expressions
    such as results from a matrix-vector multiplication.

    *comment* if present is added to the start of the block as documentation.
    """
    #for v, row in zip(var, vec): sp.pprint(Eq(v, row))
    desc = sp.pretty(Eq(var, vec), wrap_line=False)
    if not isinstance(var, Matrix):
        var, vec = [var], [vec]
    if 'ccode' in OUTPUT:
        if 'math' in OUTPUT:
            print("\n// " + comment if comment else "")
            print("/*")
            for line in desc.split("\n"):
                print(" * "+line)
            print(" *\n */")
        else:
            print("\n    // " + comment if comment else "")
        if post:
            print("    // " + post)
        for v, row in zip(var, vec):
            print(subs("    const double " + sp.ccode(row, assign_to=v)))
    if 'python' in OUTPUT:
        if comment:
            print("\n    ## " + comment)
        if 'math' in OUTPUT:
            for line in desc.split("\n"):
                print("    # " + line)
        if post:
            print("    ## " + post)
        for v, row in zip(var, vec):
            print(subs("    " + sp.ccode(row, assign_to=v)[:-1]))

    if OUTPUT == 'math ':
        print("\n// " + comment if comment else "")
        if post: print("// " + post)
        print(desc)

def mprint(var, mat, comment=None, post=None):
    """
    Generate assignment statements for matrix elements.
    """
    n = sp.prod(var.shape)
    vprint(var.reshape(n, 1), mat.reshape(n, 1), comment=comment, post=post)

# From wikipedia:
#    https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
def Rx(a):
    """Rotate y and z about x"""
    R = [[1, 0, 0],
         [0, +cos(a), -sin(a)],
         [0, +sin(a), +cos(a)]]
    return Matrix(R)

def Ry(a):
    """Rotate x and z about y"""
    R = [[+cos(a), 0, +sin(a)],
         [0, 1, 0],
         [-sin(a), 0, +cos(a)]]
    return Matrix(R)

def Rz(a):
    """Rotate x and y about z"""
    R = [[+cos(a), -sin(a), 0],
         [+sin(a), +cos(a), 0],
         [0, 0, 1]]
    return Matrix(R)


## ===============  Describe the transforms ====================

# Define symbols used.  Note that if you change the symbols for the jitter
# angles, you will need to update the subs() function accordingly.
dphi, dpsi, dtheta = sp.var("phi^+ psi^+ theta^+")
phi, psi, theta = sp.var("phi psi theta")
#dphi, dpsi, dtheta = sp.var("beta^+ gamma^+ alpha^+")
#phi, psi, theta = sp.var("beta gamma alpha")
x, y, z = sp.var("x y z")
q = sp.var("q")
qx, qy, qz = sp.var("qx qy qz")
dqx, dqy, dqz = sp.var("qx^+ qy^+ qz^+")
qa, qb, qc = sp.var("qa qb qc")
dqa, dqb, dqc = sp.var("qa^+ qb^+ qc^+")
qab = sp.var("qab")

# 3x3 matrix M
J = Matrix([sp.var("J(1:4)(1:4)")]).reshape(3,3)
V = Matrix([sp.var("V(1:4)(1:4)")]).reshape(3,3)
R = Matrix([sp.var("R(1:4)(1:4)")]).reshape(3,3)

# various vectors
xyz = Matrix([[x], [y], [z]])
x_hat = Matrix([[x], [0], [0]])
y_hat = Matrix([[0], [y], [0]])
z_hat = Matrix([[0], [0], [z]])
q_xy = Matrix([[qx], [qy], [0]])
q_abc = Matrix([[qa], [qb], [qc]])
q_xyz = Matrix([[qx], [qy], [qz]])
dq_abc = Matrix([[dqa], [dqb], [dqc]])
dq_xyz = Matrix([[dqx], [dqy], [dqz]])

def print_steps(jitter, jitter_inv, view, view_inv, qc_only):
    """
    Show the forward/reverse transform code for view and jitter.
    """
    if 0:  # forward calculations
        vprint(q_xyz, jitter*q_abc, "apply jitter")
        #vprint(xyz, jitter*z_hat, "r")
        #mprint(J, jitter, "forward jitter")
        vprint(dq_xyz, view*q_xyz, "apply view after jitter")
        #mprint(V, view, "forward view")

        #vprint(dq_xyz, view*jitter*q_abc, "combine view and jitter")
        mprint(R, view*jitter, "forward matrix")

    if 1:  # reverse calculations
        pre_view = "set angles from view" if REUSE_SINCOS else None
        pre_jitter = "set angles from jitter" if REUSE_SINCOS else None
        index = slice(2,3) if qc_only else slice(None,None)

        comment("\n**** direct ****")
        vprint(q_abc, view_inv*q_xy, "reverse view", post=pre_view)
        vprint(dq_abc[index,:], (jitter_inv*q_abc)[index,:],
               "reverse jitter after view", post=pre_jitter)

        comment("\n\n**** precalc ****")
        #vprint(q_abc, jitter_inv*view_inv*q_xy, "combine jitter and view reverse")
        mprint(V[:,:2], view_inv[:,:2], "reverse view matrix", post=pre_view)
        mprint(J[index,:], jitter_inv[index,:], "reverse jitter matrix", post=pre_jitter)
        mprint(R[index,:2], (J*V)[index,:2], "reverse matrix")
        comment("\n**** per point ****")
        mprint(q_abc[index,:], (R*q_xy)[index,:], "applied reverse matrix")
        #mprint(q_abc, J*V*q_xy, "applied reverse matrix")
        #mprint(R[index,:2], jitter_inv*view_inv, "reverse matrix direct")

        #vprint(q_abc, M*q_xy, "matrix application")

if 1:
    comment("==== asymmetric ====")
    print_steps(
        jitter=Rx(dphi)*Ry(dtheta)*Rz(dpsi),
        jitter_inv=Rz(-dpsi)*Ry(-dtheta)*Rx(-dphi),
        view=Rz(phi)*Ry(theta)*Rz(psi),
        view_inv=Rz(-psi)*Ry(-theta)*Rz(-phi),
        qc_only=False,
    )

if 1:
    comment("\n\n==== symmetric ====")
    print_steps(
        jitter=Rx(dphi)*Ry(dtheta),
        jitter_inv=Ry(-dtheta)*Rx(-dphi),
        view=Rz(phi)*Ry(theta),
        view_inv=Ry(-theta)*Rz(-phi),
        qc_only=QC_ONLY,
    )

    comment("\n**** qab from qc ****")
    # The indirect calculation of qab is better than directly c
    # alculating qab^2 = qa^2 + qb^2 since qc can be computed
    # as qc = M31*qx + M32*qy, thus requiring only two elements
    # of the rotation matrix.
    #vprint(qab, sqrt(qa**2 + qb**2), "Direct calculation of qab")
    vprint(dqa, sqrt((qx**2+qy**2) - dqc**2),
        "Indirect calculation of qab, from qab^2 = |q|^2 - qc^2")

if 0:
    comment("==== asymmetric (3.x) ====")
    view_inv = Rz(-psi)*Rx(theta)*Ry(-(pi/2 - phi))
    vprint(q_abc, view_inv*q_xy, "reverse view")
    print("""  existing code
    cos_alpha = cos_theta*cos_phi*qxhat + sin_theta*qyhat;
    cos_mu = (-sin_theta*cos_psi*cos_phi - sin_psi*sin_phi)*qxhat + cos_theta*cos_psi*qyhat;
    cos_nu = (-cos_phi*sin_psi*sin_theta + sin_phi*cos_psi)*qxhat + sin_psi*cos_theta*qyhat;
    """)