1 """
2 Spherical geometry and related helper functions.
3 """
4
5
6
7
8
9
10
11 import math
12 import new
13 import numpy
14
15 from gavo.stc import common
16 from gavo.stc import units
17
18 from gavo.utils.mathtricks import cartToSpher, spherToCart
19
20
21
22 defaultDistance = units.maxDistance*units.onePc/units.oneAU
23
24
25
26 _svPosUnit = ("rad", "rad", "AU")
27 _svVPosUnit = ("rad", "rad", "AU")
28 _svVTimeUnit = ("d", "d", "d")
29
30 _lightAUd = 86400.0/499.004782
34 """returns a 3-rotation matrix for rotating angle radians around x.
35 """
36 c, s = math.cos(angle), math.sin(angle)
37 return numpy.array([[1, 0, 0], [0, c, s], [0, -s, c]])
38
41 """returns a 3-rotation matrix for rotating angle radians around y.
42 """
43 c, s = math.cos(angle), math.sin(angle)
44 return numpy.array([[c, 0, -s], [0, 1, 0], [s, 0, c]])
45
48 """returns a 3-rotation matrix for rotating angle radians around u.
49 """
50 c, s = math.cos(angle), math.sin(angle)
51 return numpy.array([[c, s, 0], [-s, c, 0], [0, 0, 1]])
52
55 """returns a 3-rotation matrix for the z-x-z Euler angles.
56
57 There are some functions to obtain such angles below.
58 """
59 return numpy.dot(
60 numpy.dot(getRotZ(z2), getRotX(x)), getRotZ(z1))
61
64 """returns zxz Euler angles from a rotation matrix.
65
66 This is improvised, and someone should look up a numerically sound way
67 to do this.
68 """
69
70 z1 = math.atan2(matrix[2,0], -matrix[2,1])
71 z2 = math.atan2(matrix[0,2], matrix[1,2])
72 x = math.atan2(math.sqrt(matrix[2,0]**2+matrix[2,1]**2), matrix[2,2])
73 return z1, x, z2
74
77 """returns a rotation matrix for an Euler vector.
78
79 An euler vector gives the rotation axis, its magnitude the angle in rad.
80
81 This function is a rip-off of SOFA's rv2m.
82
83 eulerVector is assumed to be a numpy array.
84 """
85 x, y, z = eulerVector
86 phi = math.sqrt(x**2+y**2+z**2)
87 sp, cp = math.sin(phi), math.cos(phi)
88 f = 1-cp
89 if phi!=0:
90 x, y, z = eulerVector/phi
91 return numpy.array([
92 [x**2*f+cp, x*y*f+z*sp, x*z*f-y*sp],
93 [y*x*f-z*sp, y**2*f+cp, y*z*f+x*sp],
94 [z*x*f+y*sp, z*y*f-x*sp, z**2*f+cp]])
95
98 """returns a transformation matrix to transform from the reference
99 system into a rotated system.
100
101 The rotated system is defined by its pole, the spherical coordinates
102 at which it has longitude zero and whether or not it is right handed.
103
104 All angles are in rad.
105 """
106
107
108 x = spherToCart(*longZeroCoo)
109 z = spherToCart(*poleCoo)
110 if abs(numpy.dot(x, z))>1e-5:
111 raise common.STCValueError("%s and %s are not valid pole/zero points for"
112 " a rotated coordinate system"%(poleCoo, longZeroCoo))
113 y = (z[1]*x[2]-z[2]*x[1], z[2]*x[0]-z[0]*x[2], z[0]*x[1]-z[1]*x[0])
114 if changeHands:
115 y = (-y[0], -y[1], -y[2])
116 return numpy.array([x,y,z])
117
118
119 -def vabs(naVec):
120 return math.sqrt(numpy.dot(naVec, naVec))
121
124 """raises an error if coo's frame is not suitable for holding spherical
125 coordinates.
126 XXX TODO: assert spatial and vel coos have the same frame, etc.
127 """
128 if not coo.frame.isSpherical():
129 raise common.STCValueError("%s is not a valid frame for transformable"
130 " spherical coordinates."%(coo.frame))
131
137 """undoes relativistic corrections from 6-vector sv.
138
139 This follows sofa's pvstar. sv is changed in place.
140 """
141 radialProj, radialV, tangentialV = _decomposeRadial(sv[:3], sv[3:])
142 betaRadial = radialProj/_lightAUd
143 betaTangential = vabs(tangentialV)/_lightAUd
144
145 d = 1.0+betaRadial
146 w = 1.0-betaRadial**2-betaTangential**2
147 if d==0.0 or w<0:
148 return
149 delta = math.sqrt(w)-1.0
150 if betaRadial==0:
151 radialV = (betaRadial-delta)/(betaRadial*d)*radialV
152 sv[3:] = 1/d*radialV
153
156 """returns the components of rd radial and tangential to r.
157 """
158 rUnit = r/vabs(r)
159 radialProj = numpy.dot(rUnit, rd)
160 radialVector = radialProj*rUnit
161 tangentialVector = rd-radialVector
162 return radialProj, radialVector, tangentialVector
163
166 """returns the solution of XXX.
167
168 If the solution fails to converge within maxIter iterations, it
169 raises an STCError.
170 """
171 curEstR, curEstT = betaR, betaT
172 od, odel, odd, oddel = 0, 0, 0, 0
173 for i in range(maxIter):
174 d = 1.+curEstT
175 delta = math.sqrt(1.-curEstR**2-curEstT**2)-1.0
176 curEstR = d*betaR+delta
177 curEstT = d*betaT
178 if i:
179 dd = abs(d-od)
180 ddel = abs(delta-odel)
181 if dd==odd and ddel==oddel:
182 break
183 odd = dd
184 oddel = ddel
185 od = d
186 odel = delta
187 else:
188 raise common.STCError(
189 "6-vector relativistic correction failed to converge")
190 return curEstR, curEstT, d, delta
191
194 """applies relativistic corrections to the 6-vector sv.
195
196 This follows sofa's starpv. sv is changed in place.
197 """
198 radialProj, radialV, tangentialV = _decomposeRadial(sv[:3], sv[3:])
199 betaRadial = radialProj/_lightAUd
200 betaTangential = vabs(tangentialV)/_lightAUd
201
202 betaSR, betaST, d, delta = _solveStumpffEquation(betaRadial, betaTangential)
203
204 if betaSR!=0:
205 radialV = (d+delta/betaSR)*radialV
206 sv[3:] = radialV+(d*tangentialV)
207
211 """A container for the conversion from spherical coordinates
212 to 6-Vectors.
213
214 You create one with an example of your data; these are values
215 and units of STC objects, and everything may be None if it's
216 not given.
217
218 The resulting object has methods to6 taking values
219 like the one provided by you and returning a 6-vector, and from6
220 returning a pair of such values.
221
222 Further, the converter has the attributes distGiven,
223 posdGiven, and distdGiven signifying whether these items are
224 expected or valid on return. If posVals is None, no transforms
225 can be computed.
226
227 The relativistic=True constructior argument requests that the
228 transformation be Lorentz-invariant. Do not use that, though,
229 since there are unsolved numerical issues.
230
231 The slaComp=True constructor argument requests that some
232 operations exterior to the construction are done as slalib does
233 them, rather than alternative approaches.
234 """
235 posGiven = distGiven = posdGiven = distdGiven = True
236 defaultDistance = units.maxDistance*units.onePc/units.oneAU
237
238 - def __init__(self, posVals, posUnit, velVals=None, velSUnit=None,
239 velTUnit=None, relativistic=False, slaComp=False):
240 self.relativistic, self.slaComp = relativistic, slaComp
241 self._determineFeatures(posVals, velVals)
242 self._computeUnitConverters(posUnit, velSUnit, velTUnit)
243 self._makeTo6()
244 self._makeFrom6()
245
247 if posVals is None:
248
249
250
251 self.distGiven = self.posdGiven = False
252 self._velDefault = None
253 return
254
255 if len(posVals)==2:
256 self.distGiven = False
257 if velVals is None:
258 self.posdGiven = False
259 self._velDefault = None
260 else:
261 if len(velVals)==2:
262 self.distdGiven = False
263 self._velDefault = (0,0)
264 else:
265 self._velDefault = (0,0,0)
266
268 dims = len(posUnit)
269 self.toSVUnitsPos = units.getVectorConverter(posUnit,
270 _svPosUnit[:dims])
271 self.fromSVUnitsPos = units.getVectorConverter(posUnit,
272 _svPosUnit[:dims], True)
273 if self.posdGiven:
274 dims = len(velSUnit)
275 self.toSVUnitsVel = units.getVelocityConverter(velSUnit, velTUnit,
276 _svVPosUnit[:dims], _svVTimeUnit[:dims])
277 self.fromSVUnitsVel = units.getVelocityConverter(velSUnit, velTUnit,
278 _svVPosUnit[:dims], _svVTimeUnit[:dims], True)
279
281 velDefault = ""
282 if not self.posdGiven:
283 velDefault = "=None"
284 code = ["def to6(self, pos, vel%s):"%velDefault]
285 code.append(" pos = self.toSVUnitsPos(pos)")
286 if self.posdGiven:
287 code.append(" vel = self.toSVUnitsVel(vel)")
288 if not self.distGiven:
289 code.append(" pos = pos+(defaultDistance,)")
290 if self.posdGiven:
291 if not self.distdGiven:
292 code.append(" vel = vel+(0,)")
293 else:
294 code.append(" vel = (0,0,0)")
295 code.append(" (alpha, delta, r), (alphad, deltad, rd) = pos, vel")
296 code.append(" sa, ca = math.sin(alpha), math.cos(alpha)")
297 code.append(" sd, cd = math.sin(delta), math.cos(delta)")
298 code.append(" x, y = r*cd*ca, r*cd*sa")
299 code.append(" w = r*deltad*sd-cd*rd")
300 code.append(" res = numpy.array([x, y, r*sd,"
301 " -y*alphad-w*ca, x*alphad-w*sa, r*deltad*cd+sd*rd])")
302 if self.relativistic:
303 code.append(" _pleaseEinsteinFromSpher(res)")
304 code.append(" return res")
305 l = locals()
306 exec "\n".join(code) in globals() , l
307 self.to6 = new.instancemethod(l["to6"], self)
308
310 code = ["def from6(self, sv):"]
311 if self.relativistic:
312 code.append(" _pleaseEinsteinFromSpher(sv)")
313 code.append(" pos, vel = self._svToSpherRaw(sv)")
314 if not self.distGiven:
315 code.append(" pos = pos[:2]")
316 code.append(" pos = self.fromSVUnitsPos(pos)")
317 if self.posdGiven:
318 if not self.distdGiven:
319 code.append(" vel = vel[:2]")
320 code.append(" vel = self.fromSVUnitsVel(vel)")
321 else:
322 code.append(" vel = None")
323 code.append(" return pos, vel")
324 l = locals()
325 exec "\n".join(code) in globals() , l
326 self.from6 = new.instancemethod(l["from6"], self)
327
329 """returns spherical position and velocity vectors for the cartesian
330 6-vector sv.
331
332 This is based on SOFA's pv2s.
333 """
334 x, y, z, xd, yd, zd = sv
335 rInXY2 = x**2+y**2
336 r2 = rInXY2+z**2
337 rw = rTrue = math.sqrt(r2)
338
339 if rTrue==0.:
340 x, y, z = sv[3:]
341 rInXY2 = x**2+y**2
342 r2 = rInXY2+z**2
343 rw = math.sqrt(r2)
344
345 rInXY = math.sqrt(rInXY2)
346 xyp = x*xd+y*yd
347 radialVel = 0
348 if rw!=0:
349 radialVel = xyp/rw+z*zd/rw
350
351 if rInXY2!=0.:
352 theta = math.atan2(y, x)
353 if abs(theta)<1e-12:
354 theta = 0
355 if theta<0:
356 theta += 2*math.pi
357 posValues = (theta, math.atan2(z, rInXY), rTrue)
358 velValues = ((x*yd-y*xd)/rInXY2, (zd*rInXY2-z*xyp)/(r2*rInXY), radialVel)
359 else:
360 phi = 0
361 if z!=0:
362 phi = math.atan2(z, rInXY)
363 posValues = (0, phi, rTrue)
364 velValues = (0, 0, radialVel)
365 return posValues, velValues
366
384 return sTrafo
385
394 return sTrafo
395
396 @classmethod
398 """returns a new 6-vector transform for coordinates in STC.
399 """
400 pos, vel, posUnit, velUnitS, velUnitT = (None,)*5
401 if stc.place:
402 pos = stc.place.value
403 posUnit = stc.place.unit
404 if pos is None:
405 if stc.areas:
406 pos = (0,)*stc.place.frame.nDim
407 if stc.velocity:
408 vel = stc.velocity.value
409 velUnitS = stc.velocity.unit
410 velUnitT = stc.velocity.velTimeUnit
411 return cls(pos, posUnit, vel, velUnitS, velUnitT, **kwargs)
412
415 """returns spherical coordinates for a cartesian 3-vector.
416
417 threeVec needs not be normalized.
418 """
419 rho = math.sqrt(threeVec[0]**2+threeVec[1]**2)
420 long = math.atan2(threeVec[1], threeVec[0])
421 lat = math.atan2(threeVec[2], rho)
422 return long, lat
423
426 """returns a cartesian 3-vector for longitude and latitude.
427 """
428 return (math.cos(lat)*math.cos(long),
429 math.cos(lat)*math.sin(long),
430 math.sin(lat))
431