source: sasmodels/explore/angles.py @ ea60e08

core_shell_microgelsmagnetic_modelticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since ea60e08 was ea60e08, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

determine rotation matrix for SasView? 3.x

  • Property mode set to 100644
File size: 7.9 KB
Line 
1#!/usr/bin/env python
2"""
3Generate code for orientation transforms using symbolic algebra.
4
5To make it easier to generate correct transforms for oriented shapes, we
6use the sympy symbolic alegbra package to do the matrix multiplication.
7The transforms are displayed both using an ascii math notation, and as
8C or python code which can be pasted directly into the kernel driver.
9
10If ever we decide to change conventions, we simply need to adjust the
11order and parameters to the rotation matrices.  For display we want to
12use forward transforms for the mesh describing the shape, first applying
13jitter, then adjusting the view.  For calculation we know the effective q
14so we instead need to first unwind the view, using the inverse rotation,
15then undo the jitter to get the q to calculate for the shape in its
16canonical orientation.
17
18Set *OUTPUT* to the type of code you want to see: ccode, python, math
19or any combination.
20"""
21
22from __future__ import print_function
23
24import codecs
25import sys
26import re
27
28import sympy as sp
29from sympy import pi, sqrt, sin, cos, Matrix, Eq
30
31# Select output
32OUTPUT = ""
33OUTPUT = OUTPUT + "ccode"
34#OUTPUT = OUTPUT + "python "
35OUTPUT = OUTPUT + "math "
36REUSE_SINCOS = True
37QC_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
40if sys.version_info[0] < 3:
41    sys.stdout = codecs.getwriter('utf8')(sys.stdout)
42sp.init_printing(use_unicode=True)
43
44def 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
55def 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
66def 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
110def 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
117# From wikipedia:
118#    https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
119def Rx(a):
120    """Rotate y and z about x"""
121    R = [[1, 0, 0],
122         [0, +cos(a), -sin(a)],
123         [0, +sin(a), +cos(a)]]
124    return Matrix(R)
125
126def Ry(a):
127    """Rotate x and z about y"""
128    R = [[+cos(a), 0, +sin(a)],
129         [0, 1, 0],
130         [-sin(a), 0, +cos(a)]]
131    return Matrix(R)
132
133def Rz(a):
134    """Rotate x and y about z"""
135    R = [[+cos(a), -sin(a), 0],
136         [+sin(a), +cos(a), 0],
137         [0, 0, 1]]
138    return Matrix(R)
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.
145dphi, dpsi, dtheta = sp.var("phi^+ psi^+ theta^+")
146phi, psi, theta = sp.var("phi psi theta")
147#dphi, dpsi, dtheta = sp.var("beta^+ gamma^+ alpha^+")
148#phi, psi, theta = sp.var("beta gamma alpha")
149x, y, z = sp.var("x y z")
150q = sp.var("q")
151qx, qy, qz = sp.var("qx qy qz")
152dqx, dqy, dqz = sp.var("qx^+ qy^+ qz^+")
153qa, qb, qc = sp.var("qa qb qc")
154dqa, dqb, dqc = sp.var("qa^+ qb^+ qc^+")
155qab = sp.var("qab")
156
157# 3x3 matrix M
158J = Matrix([sp.var("J(1:4)(1:4)")]).reshape(3,3)
159V = Matrix([sp.var("V(1:4)(1:4)")]).reshape(3,3)
160R = Matrix([sp.var("R(1:4)(1:4)")]).reshape(3,3)
161
162# various vectors
163q_xy = Matrix([[qx], [qy], [0]])
164q_abc = Matrix([[qa], [qb], [qc]])
165q_xyz = Matrix([[qx], [qy], [qz]])
166dq_abc = Matrix([[dqa], [dqb], [dqc]])
167dq_xyz = Matrix([[dqx], [dqy], [dqz]])
168
169def print_steps(jitter, jitter_inv, view, view_inv, qc_only):
170    """
171    Show the forward/reverse transform code for view and jitter.
172    """
173    if 0:  # forward calculations
174        vprint(q_xyz, jitter*q_abc, "apply jitter")
175        vprint(dq_xyz, view*q_xyz, "apply view after jitter")
176
177        #vprint(dq_xyz, view*jitter*q_abc, "combine view and jitter")
178        mprint(M, view*jitter, "forward matrix")
179
180    if 1:  # reverse calculations
181        pre_view = "set angles from view" if REUSE_SINCOS else None
182        pre_jitter = "set angles from jitter" if REUSE_SINCOS else None
183        index = slice(2,3) if qc_only else slice(None,None)
184
185        comment("\n**** direct ****")
186        vprint(q_abc, view_inv*q_xy, "reverse view", post=pre_view)
187        vprint(dq_abc[index,:], (jitter_inv*q_abc)[index,:],
188               "reverse jitter after view", post=pre_jitter)
189
190        comment("\n\n**** precalc ****")
191        #vprint(q_abc, jitter_inv*view_inv*q_xy, "combine jitter and view reverse")
192        mprint(V[:,:2], view_inv[:,:2], "reverse view matrix", post=pre_view)
193        mprint(J[index,:], jitter_inv[index,:], "reverse jitter matrix", post=pre_jitter)
194        mprint(R[index,:2], (J*V)[index,:2], "reverse matrix")
195        comment("\n**** per point ****")
196        mprint(q_abc[index,:], (R*q_xy)[index,:], "applied reverse matrix")
197        #mprint(q_abc, J*V*q_xy, "applied reverse matrix")
198        #mprint(M, jitter_inv*view_inv, "reverse matrix direct")
199
200        #vprint(q_abc, M*q_xy, "matrix application")
201
202if 1:
203    comment("==== asymmetric ====")
204    print_steps(
205        jitter=Rx(dphi)*Ry(dtheta)*Rz(dpsi),
206        jitter_inv=Rz(-dpsi)*Ry(-dtheta)*Rx(-dphi),
207        view=Rz(phi)*Ry(theta)*Rz(psi),
208        view_inv=Rz(-psi)*Ry(-theta)*Rz(-phi),
209        qc_only=False,
210    )
211
212    comment("\n\n==== symmetric ====")
213    print_steps(
214        jitter=Rx(dphi)*Ry(dtheta),
215        jitter_inv=Ry(-dtheta)*Rx(-dphi),
216        view=Rz(phi)*Ry(theta),
217        view_inv=Ry(-theta)*Rz(-phi),
218        qc_only=QC_ONLY,
219    )
220
221    comment("\n**** qab from qc ****")
222    # The indirect calculation of qab is better than directly c
223    # alculating qab^2 = qa^2 + qb^2 since qc can be computed
224    # as qc = M31*qx + M32*qy, thus requiring only two elements
225    # of the rotation matrix.
226    #vprint(qab, sqrt(qa**2 + qb**2), "Direct calculation of qab")
227    vprint(dqa, sqrt((qx**2+qy**2) - dqc**2),
228        "Indirect calculation of qab, from qab^2 = |q|^2 - qc^2")
229
230if 0:
231    comment("==== asymmetric (old) ====")
232    view_inv = Rz(-psi)*Rx(theta)*Ry(-(pi/2 - phi))
233    vprint(q_abc, view_inv*q_xy, "reverse view")
Note: See TracBrowser for help on using the repository browser.