[0d19f42] | 1 | #!/usr/bin/env python |
---|
| 2 | """ |
---|
| 3 | Generate code for orientation transforms using symbolic algebra. |
---|
| 4 | |
---|
| 5 | To make it easier to generate correct transforms for oriented shapes, we |
---|
| 6 | use the sympy symbolic alegbra package to do the matrix multiplication. |
---|
| 7 | The transforms are displayed both using an ascii math notation, and as |
---|
| 8 | C or python code which can be pasted directly into the kernel driver. |
---|
| 9 | |
---|
| 10 | If ever we decide to change conventions, we simply need to adjust the |
---|
| 11 | order and parameters to the rotation matrices. For display we want to |
---|
| 12 | use forward transforms for the mesh describing the shape, first applying |
---|
| 13 | jitter, then adjusting the view. For calculation we know the effective q |
---|
| 14 | so we instead need to first unwind the view, using the inverse rotation, |
---|
| 15 | then undo the jitter to get the q to calculate for the shape in its |
---|
| 16 | canonical orientation. |
---|
| 17 | |
---|
| 18 | Set *OUTPUT* to the type of code you want to see: ccode, python, math |
---|
| 19 | or any combination. |
---|
| 20 | """ |
---|
| 21 | |
---|
[bf09f55] | 22 | from __future__ import print_function |
---|
| 23 | |
---|
[0d19f42] | 24 | import codecs |
---|
| 25 | import sys |
---|
| 26 | import re |
---|
| 27 | |
---|
[e755d8a] | 28 | import sympy as sp |
---|
[0d19f42] | 29 | from sympy import pi, sqrt, sin, cos, Matrix, Eq |
---|
| 30 | |
---|
| 31 | # Select output |
---|
| 32 | OUTPUT = "" |
---|
| 33 | OUTPUT = OUTPUT + "ccode" |
---|
| 34 | #OUTPUT = OUTPUT + "python " |
---|
| 35 | OUTPUT = OUTPUT + "math " |
---|
| 36 | REUSE_SINCOS = True |
---|
| 37 | QC_ONLY = True # show only what is needed for dqc in the symmetric case |
---|
| 38 | |
---|
| 39 | # include unicode symbols in output, even if piping to a pager |
---|
[f728001] | 40 | if sys.version_info[0] < 3: |
---|
| 41 | sys.stdout = codecs.getwriter('utf8')(sys.stdout) |
---|
[0d19f42] | 42 | sp.init_printing(use_unicode=True) |
---|
| 43 | |
---|
| 44 | def subs(s): |
---|
| 45 | """ |
---|
| 46 | Transform sympy generated code to follow sasmodels naming conventions. |
---|
| 47 | """ |
---|
| 48 | if REUSE_SINCOS: |
---|
| 49 | s = re.sub(r'(phi|psi|theta)\^\+', r'\1', s) # jitter rep: V^+ => V |
---|
| 50 | s = re.sub(r'([a-z]*)\^\+', r'd\1', s) # jitter rep: V^+ => dV |
---|
| 51 | s = re.sub(r'(cos|sin)\(([a-z]*)\)', r'\1_\2', s) # cos(V) => cos_V |
---|
| 52 | s = re.sub(r'pow\(([a-z]*), 2\)', r'\1*\1', s) # pow(V, 2) => V*V |
---|
| 53 | return s |
---|
| 54 | |
---|
| 55 | def comment(s): |
---|
| 56 | r""" |
---|
| 57 | Add a comment to the generated code. Use '\n' to separate lines. |
---|
| 58 | """ |
---|
| 59 | if 'ccode' in OUTPUT: |
---|
| 60 | for line in s.split("\n"): |
---|
| 61 | print("// " + line if line else "") |
---|
| 62 | if 'python' in OUTPUT: |
---|
| 63 | for line in s.split("\n"): |
---|
| 64 | print(" ## " + line if line else "") |
---|
| 65 | |
---|
| 66 | def vprint(var, vec, comment=None, post=None): |
---|
| 67 | """ |
---|
| 68 | Generate assignment statements. |
---|
| 69 | |
---|
| 70 | *var* could be a single sympy symbol or a 1xN vector of symbols. |
---|
| 71 | |
---|
| 72 | *vec* could be a single sympy expression or a 1xN vector of expressions |
---|
| 73 | such as results from a matrix-vector multiplication. |
---|
| 74 | |
---|
| 75 | *comment* if present is added to the start of the block as documentation. |
---|
| 76 | """ |
---|
| 77 | #for v, row in zip(var, vec): sp.pprint(Eq(v, row)) |
---|
| 78 | desc = sp.pretty(Eq(var, vec), wrap_line=False) |
---|
| 79 | if not isinstance(var, Matrix): |
---|
| 80 | var, vec = [var], [vec] |
---|
| 81 | if 'ccode' in OUTPUT: |
---|
| 82 | if 'math' in OUTPUT: |
---|
| 83 | print("\n// " + comment if comment else "") |
---|
| 84 | print("/*") |
---|
| 85 | for line in desc.split("\n"): |
---|
| 86 | print(" * "+line) |
---|
| 87 | print(" *\n */") |
---|
| 88 | else: |
---|
| 89 | print("\n // " + comment if comment else "") |
---|
| 90 | if post: |
---|
| 91 | print(" // " + post) |
---|
| 92 | for v, row in zip(var, vec): |
---|
| 93 | print(subs(" const double " + sp.ccode(row, assign_to=v))) |
---|
| 94 | if 'python' in OUTPUT: |
---|
| 95 | if comment: |
---|
| 96 | print("\n ## " + comment) |
---|
| 97 | if 'math' in OUTPUT: |
---|
| 98 | for line in desc.split("\n"): |
---|
| 99 | print(" # " + line) |
---|
| 100 | if post: |
---|
| 101 | print(" ## " + post) |
---|
| 102 | for v, row in zip(var, vec): |
---|
| 103 | print(subs(" " + sp.ccode(row, assign_to=v)[:-1])) |
---|
| 104 | |
---|
| 105 | if OUTPUT == 'math ': |
---|
| 106 | print("\n// " + comment if comment else "") |
---|
| 107 | if post: print("// " + post) |
---|
| 108 | print(desc) |
---|
| 109 | |
---|
| 110 | def mprint(var, mat, comment=None, post=None): |
---|
| 111 | """ |
---|
| 112 | Generate assignment statements for matrix elements. |
---|
| 113 | """ |
---|
| 114 | n = sp.prod(var.shape) |
---|
| 115 | vprint(var.reshape(n, 1), mat.reshape(n, 1), comment=comment, post=post) |
---|
| 116 | |
---|
[ef8e68c] | 117 | # From wikipedia: |
---|
| 118 | # https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations |
---|
[0d19f42] | 119 | def Rx(a): |
---|
| 120 | """Rotate y and z about x""" |
---|
[ef8e68c] | 121 | R = [[1, 0, 0], |
---|
| 122 | [0, +cos(a), -sin(a)], |
---|
| 123 | [0, +sin(a), +cos(a)]] |
---|
| 124 | return Matrix(R) |
---|
[0d19f42] | 125 | |
---|
| 126 | def Ry(a): |
---|
| 127 | """Rotate x and z about y""" |
---|
[ef8e68c] | 128 | R = [[+cos(a), 0, +sin(a)], |
---|
| 129 | [0, 1, 0], |
---|
| 130 | [-sin(a), 0, +cos(a)]] |
---|
| 131 | return Matrix(R) |
---|
[0d19f42] | 132 | |
---|
| 133 | def Rz(a): |
---|
| 134 | """Rotate x and y about z""" |
---|
[ef8e68c] | 135 | R = [[+cos(a), -sin(a), 0], |
---|
| 136 | [+sin(a), +cos(a), 0], |
---|
| 137 | [0, 0, 1]] |
---|
| 138 | return Matrix(R) |
---|
[0d19f42] | 139 | |
---|
| 140 | |
---|
| 141 | ## =============== Describe the transforms ==================== |
---|
| 142 | |
---|
| 143 | # Define symbols used. Note that if you change the symbols for the jitter |
---|
| 144 | # angles, you will need to update the subs() function accordingly. |
---|
| 145 | dphi, dpsi, dtheta = sp.var("phi^+ psi^+ theta^+") |
---|
[e755d8a] | 146 | phi, psi, theta = sp.var("phi psi theta") |
---|
[0d19f42] | 147 | #dphi, dpsi, dtheta = sp.var("beta^+ gamma^+ alpha^+") |
---|
| 148 | #phi, psi, theta = sp.var("beta gamma alpha") |
---|
[e755d8a] | 149 | x, y, z = sp.var("x y z") |
---|
[0d19f42] | 150 | q = sp.var("q") |
---|
[e755d8a] | 151 | qx, qy, qz = sp.var("qx qy qz") |
---|
[0d19f42] | 152 | dqx, dqy, dqz = sp.var("qx^+ qy^+ qz^+") |
---|
[bf09f55] | 153 | qa, qb, qc = sp.var("qa qb qc") |
---|
[0d19f42] | 154 | dqa, dqb, dqc = sp.var("qa^+ qb^+ qc^+") |
---|
| 155 | qab = sp.var("qab") |
---|
[e755d8a] | 156 | |
---|
[0d19f42] | 157 | # 3x3 matrix M |
---|
| 158 | J = Matrix([sp.var("J(1:4)(1:4)")]).reshape(3,3) |
---|
| 159 | V = Matrix([sp.var("V(1:4)(1:4)")]).reshape(3,3) |
---|
| 160 | R = Matrix([sp.var("R(1:4)(1:4)")]).reshape(3,3) |
---|
| 161 | |
---|
| 162 | # various vectors |
---|
[f39d4a3] | 163 | xyz = Matrix([[x], [y], [z]]) |
---|
| 164 | x_hat = Matrix([[x], [0], [0]]) |
---|
| 165 | y_hat = Matrix([[0], [y], [0]]) |
---|
| 166 | z_hat = Matrix([[0], [0], [z]]) |
---|
[0d19f42] | 167 | q_xy = Matrix([[qx], [qy], [0]]) |
---|
| 168 | q_abc = Matrix([[qa], [qb], [qc]]) |
---|
| 169 | q_xyz = Matrix([[qx], [qy], [qz]]) |
---|
| 170 | dq_abc = Matrix([[dqa], [dqb], [dqc]]) |
---|
| 171 | dq_xyz = Matrix([[dqx], [dqy], [dqz]]) |
---|
| 172 | |
---|
| 173 | def print_steps(jitter, jitter_inv, view, view_inv, qc_only): |
---|
| 174 | """ |
---|
| 175 | Show the forward/reverse transform code for view and jitter. |
---|
| 176 | """ |
---|
| 177 | if 0: # forward calculations |
---|
| 178 | vprint(q_xyz, jitter*q_abc, "apply jitter") |
---|
[f39d4a3] | 179 | #vprint(xyz, jitter*z_hat, "r") |
---|
| 180 | #mprint(J, jitter, "forward jitter") |
---|
[0d19f42] | 181 | vprint(dq_xyz, view*q_xyz, "apply view after jitter") |
---|
[f39d4a3] | 182 | #mprint(V, view, "forward view") |
---|
[0d19f42] | 183 | |
---|
| 184 | #vprint(dq_xyz, view*jitter*q_abc, "combine view and jitter") |
---|
[f39d4a3] | 185 | mprint(R, view*jitter, "forward matrix") |
---|
[0d19f42] | 186 | |
---|
| 187 | if 1: # reverse calculations |
---|
| 188 | pre_view = "set angles from view" if REUSE_SINCOS else None |
---|
| 189 | pre_jitter = "set angles from jitter" if REUSE_SINCOS else None |
---|
| 190 | index = slice(2,3) if qc_only else slice(None,None) |
---|
| 191 | |
---|
| 192 | comment("\n**** direct ****") |
---|
| 193 | vprint(q_abc, view_inv*q_xy, "reverse view", post=pre_view) |
---|
| 194 | vprint(dq_abc[index,:], (jitter_inv*q_abc)[index,:], |
---|
| 195 | "reverse jitter after view", post=pre_jitter) |
---|
| 196 | |
---|
| 197 | comment("\n\n**** precalc ****") |
---|
| 198 | #vprint(q_abc, jitter_inv*view_inv*q_xy, "combine jitter and view reverse") |
---|
| 199 | mprint(V[:,:2], view_inv[:,:2], "reverse view matrix", post=pre_view) |
---|
| 200 | mprint(J[index,:], jitter_inv[index,:], "reverse jitter matrix", post=pre_jitter) |
---|
| 201 | mprint(R[index,:2], (J*V)[index,:2], "reverse matrix") |
---|
| 202 | comment("\n**** per point ****") |
---|
| 203 | mprint(q_abc[index,:], (R*q_xy)[index,:], "applied reverse matrix") |
---|
| 204 | #mprint(q_abc, J*V*q_xy, "applied reverse matrix") |
---|
[f39d4a3] | 205 | #mprint(R[index,:2], jitter_inv*view_inv, "reverse matrix direct") |
---|
[0d19f42] | 206 | |
---|
| 207 | #vprint(q_abc, M*q_xy, "matrix application") |
---|
| 208 | |
---|
[ea60e08] | 209 | if 1: |
---|
| 210 | comment("==== asymmetric ====") |
---|
| 211 | print_steps( |
---|
| 212 | jitter=Rx(dphi)*Ry(dtheta)*Rz(dpsi), |
---|
| 213 | jitter_inv=Rz(-dpsi)*Ry(-dtheta)*Rx(-dphi), |
---|
| 214 | view=Rz(phi)*Ry(theta)*Rz(psi), |
---|
| 215 | view_inv=Rz(-psi)*Ry(-theta)*Rz(-phi), |
---|
| 216 | qc_only=False, |
---|
| 217 | ) |
---|
| 218 | |
---|
[f39d4a3] | 219 | if 1: |
---|
[ea60e08] | 220 | comment("\n\n==== symmetric ====") |
---|
| 221 | print_steps( |
---|
| 222 | jitter=Rx(dphi)*Ry(dtheta), |
---|
| 223 | jitter_inv=Ry(-dtheta)*Rx(-dphi), |
---|
| 224 | view=Rz(phi)*Ry(theta), |
---|
| 225 | view_inv=Ry(-theta)*Rz(-phi), |
---|
| 226 | qc_only=QC_ONLY, |
---|
| 227 | ) |
---|
| 228 | |
---|
| 229 | comment("\n**** qab from qc ****") |
---|
| 230 | # The indirect calculation of qab is better than directly c |
---|
| 231 | # alculating qab^2 = qa^2 + qb^2 since qc can be computed |
---|
| 232 | # as qc = M31*qx + M32*qy, thus requiring only two elements |
---|
| 233 | # of the rotation matrix. |
---|
| 234 | #vprint(qab, sqrt(qa**2 + qb**2), "Direct calculation of qab") |
---|
| 235 | vprint(dqa, sqrt((qx**2+qy**2) - dqc**2), |
---|
| 236 | "Indirect calculation of qab, from qab^2 = |q|^2 - qc^2") |
---|
| 237 | |
---|
| 238 | if 0: |
---|
[20fe0cd] | 239 | comment("==== asymmetric (3.x) ====") |
---|
[ea60e08] | 240 | view_inv = Rz(-psi)*Rx(theta)*Ry(-(pi/2 - phi)) |
---|
| 241 | vprint(q_abc, view_inv*q_xy, "reverse view") |
---|
[20fe0cd] | 242 | print(""" existing code |
---|
| 243 | cos_alpha = cos_theta*cos_phi*qxhat + sin_theta*qyhat; |
---|
| 244 | cos_mu = (-sin_theta*cos_psi*cos_phi - sin_psi*sin_phi)*qxhat + cos_theta*cos_psi*qyhat; |
---|
| 245 | cos_nu = (-cos_phi*sin_psi*sin_theta + sin_phi*cos_psi)*qxhat + sin_psi*cos_theta*qyhat; |
---|
| 246 | """) |
---|