1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """
17 Utilities to perform operations on (polar, azimuthal) vectors.
18 """
19
20 from __future__ import division
21
22 import numpy as np
23 np.seterr(all="raise")
24
25 import lal
26
27 __author__ = "Nickolas Fotopoulos <nvf@gravity.phys.uwm.edu>"
28
29
30
31
32
33
35 """
36 Take an Nx2 array of N (theta, phi) vectors on the unit sphere
37 (that is, (polar, azimuthal) angles in radians) and apply
38 rotations through the Euler angles alpha, beta, and gamma
39 in radians, using the ZXZ convention.
40 """
41 c = np.cos
42 s = np.sin
43
44
45 R = np.array(
46 [[c(alpha) * c(gamma) - c(beta) * s(alpha) * s(gamma),
47 c(gamma) * s(alpha) + c(alpha) * c(beta) * s(gamma),
48 s(beta) * s(gamma)],
49 [-c(beta) * c(gamma) * s(alpha) - c(alpha) * s(gamma),
50 c(alpha) * c(beta) * c(gamma) - s(alpha) * s(gamma),
51 c(gamma) * s(beta)],
52 [s(alpha) * s(beta), -c(alpha) * s(beta), c(beta)]], dtype=float)
53
54
55 cart_orig = np.empty(shape=(len(sph_coords), 3), dtype=float)
56 cart_orig[:, 0] = c(sph_coords[:, 1])
57 cart_orig[:, 1] = s(sph_coords[:, 1])
58 cart_orig[:, 0:2] *= s(sph_coords[:, 0])[:, None]
59 cart_orig[:, 2] = c(sph_coords[:, 0])
60
61
62
63 cart_new = np.dot(cart_orig, R)
64
65
66 sph_new = np.empty(shape=(len(sph_coords), 2), dtype=float)
67 sph_new[:, 0] = np.arccos(cart_new[:, 2])
68 sph_new[:, 1] = np.arctan2(cart_new[:, 1], cart_new[:, 0])
69
70 return sph_new
71
73 """
74 From the new Z axis expressed in (polar, azimuthal) angles of the
75 initial coordinate system, return the (alpha, beta) Euler angles
76 that rotate the old Z axis (0, 0) to the new Z axis.
77 """
78 return (lal.PI_2 + new_z[1]) % (2 * lal.PI), new_z[0]
79
81 """
82 Return the result of rotating x about axis by ang (in radians).
83 axis must be a unit vector.
84 """
85 if abs(np.dot(axis, axis) - 1) >= 1e-6:
86 raise ValueError, "axis must be a unit vector"
87 if len(x) != 3:
88 raise ValueError, "x must be three-dimensional"
89 if len(axis) != 3:
90 raise ValueError, "axis must be three-dimensional"
91 cosa = np.cos(ang)
92 sina = np.sin(ang)
93
94
95 R = np.array(
96 [[cosa+axis[0]*axis[0]*(1.0-cosa),
97 axis[0]*axis[1]*(1.0-cosa)-axis[2]*sina,
98 axis[0]*axis[2]*(1.0-cosa)+axis[1]*sina],
99 [axis[1]*axis[0]*(1.0-cosa)+axis[2]*sina,
100 cosa+axis[1]*axis[1]*(1.0-cosa),
101 axis[1]*axis[2]*(1.0-cosa)-axis[0]*sina],
102 [axis[2]*axis[0]*(1.0-cosa)-axis[1]*sina,
103 axis[2]*axis[1]*(1.0-cosa)+axis[0]*sina,
104 cosa+axis[2]*axis[2]*(1.0-cosa)]])
105 return np.dot(R, x)
106
107
108
109
111 """
112 For some angular difference c = |a - b| in radians, find the
113 magnitude of the difference, taking into account the wrap-around at 2*pi.
114 """
115 c = abs(c) % (2 * lal.PI)
116
117 return np.where(c < lal.PI, c, 2 * lal.PI - c)
118
120 return np.sin(angle / 2)**2
121
123 """
124 Compute the inverse of the _haversine function, using clip as protection
125 for the antipodes.
126 """
127 h = np.clip(h, 0., 1.)
128 return 2 * np.arcsin(np.sqrt(h))
129
131 """
132 Find the angle in radians between a and b, each expressed in
133 (polar, azimuthal) angles in radians. If a and b are Nx2 arrays
134 of vectors, then return the N pairwise angles between them.
135
136 This formula is the law of _haversines, which is derivable from the
137 spherical law of cosines, but is more numerically stable about a == b.
138 This technique is slightly unstable for antipodal a and b.
139 """
140 s = np.sin
141 dtheta, dphi = (a - b).T
142 h = _haversine(dtheta) + s(a[..., 0]) * s(b[..., 0]) * _haversine(dphi)
143 result = _abs_diff(_archaversine(h))
144 if result.shape == (1,):
145 return result[0]
146 else:
147 return result
148
149
150
151
152
154 """
155 Return a random (polar, azimuthal) angle drawn from the Fisher
156 distribution. Assume that the concentration parameter (kappa) is large
157 so that we can use a Rayleigh distribution about the north pole and
158 rotate it to be centered at the (polar, azimuthal) coordinate mu.
159
160 Assume kappa = 1 / sigma**2
161
162 References:
163 * http://en.wikipedia.org/wiki/Von_Mises-Fisher_distribution
164 * http://arxiv.org/pdf/0902.0737v1 (states the Rayleigh limit)
165 """
166 rayleigh_rv = \
167 np.array((np.random.rayleigh(scale=1. / np.sqrt(kappa), size=size),
168 np.random.uniform(low=0, high=2*lal.PI, size=size)))\
169 .reshape((2, size)).T
170 a, b = new_z_to_euler(mu)
171 return rotate_euler(rayleigh_rv, a, b, 0)
172
174 """
175 Return the PDF of theta, the opening angle of X with respect to mu where
176 X is Fisher-distributed about mu. See fisher_rvs for the definition of mu.
177 """
178 return kappa / (2 * np.sinh(kappa)) * np.exp(kappa * np.cos(theta))\
179 * np.sin(theta)
180
182 return 0.5 * (np.exp(kappa) - np.exp(kappa * np.cos(theta))) / np.sinh(kappa)
183