PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/healpy/rotator.py

http://healpy.googlecode.com/
Python | 794 lines | 764 code | 4 blank | 26 comment | 10 complexity | 043de0fcde8e6cf2eb38583ccba45720 MD5 | raw file
Possible License(s): GPL-2.0
  1. #
  2. # This file is part of Healpy.
  3. #
  4. # Healpy is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # Healpy is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with Healpy; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  17. #
  18. # For more information about Healpy, see http://code.google.com/p/healpy
  19. #
  20. import numpy as npy
  21. import warnings
  22. coordname = {'G': 'Galactic', 'E': 'Ecliptic', 'C': 'Equatorial'}
  23. class ConsistencyWarning(Warning):
  24. """Warns for a problem in the consistency of data
  25. """
  26. pass
  27. if __name__ != '__main__':
  28. warnings.filterwarnings("always", category=ConsistencyWarning, module=__name__)
  29. class Rotator:
  30. """This class provides tools for spherical rotations. It is meant to be used
  31. in the healpy library for plotting, and for this reason reflects the
  32. convention used in the Healpix IDL library.
  33. Example:
  34. >>> r = Rotator(coord=['G','E'])
  35. >>> theta_ecl, phi_ecl = r(theta_gal, phi_gal)
  36. or in a shorter way:
  37. >>> theta_ecl, phi_ecl = Rotator(coord=['G','E'])(theta_gal, phi_gal)
  38. """
  39. ErrMessWrongPar = ("rot and coord must be single elements or "
  40. "sequence of same size.")
  41. def __init__(self,rot=None,coord=None,inv=None,deg=True,
  42. eulertype='ZYX'):
  43. """Create a rotator with given parameters.
  44. - rot: a float, a tuple of 1,2 or 3 floats or a sequence of tuples.
  45. If it is a sequence of tuple, it must have the same length as coord.
  46. - coord: a string or a tuple of 1 or 2 strings or a sequence of tuple
  47. If it is a sequence of tuple, it must have same length as rot.
  48. - inv: whether to use inverse rotation or not
  49. - deg: if True, angles in rot are assumed in degree (default: True)
  50. - eulertype: the convention for Euler angles in rot.
  51. Note: the coord system conversion is applied first, then the rotation.
  52. """
  53. rot_is_seq = (hasattr(rot,'__len__')
  54. and hasattr(rot[0], '__len__'))
  55. coord_is_seq = (hasattr(coord,'__len__')
  56. and hasattr(coord[0],'__len__')
  57. and type(coord[0]) is not str)
  58. if rot_is_seq and coord_is_seq:
  59. if len(rot) != len(coord):
  60. raise ValueError(Rotator.ErrMessWrongPar)
  61. else:
  62. rots = rot
  63. coords = coord
  64. elif (rot_is_seq or coord_is_seq) and (rot is not None and
  65. coord is not None):
  66. raise ValueError(Rotator.ErrMessWrongPar)
  67. else:
  68. rots = [rot]
  69. coords = [coord]
  70. inv_is_seq = hasattr(inv,'__len__')
  71. if inv_is_seq:
  72. if len(inv) != len(rots):
  73. raise ValueError("inv must have same length as rot and/or coord")
  74. invs = inv
  75. else:
  76. invs = [inv]*len(rots)
  77. # check the argument and normalize them
  78. if eulertype in ['ZYX','X','Y']:
  79. self._eultype = eulertype
  80. else:
  81. self._eultype = 'ZYX'
  82. self._rots = []
  83. self._coords = []
  84. self._invs = []
  85. for r,c,i in zip(rots,coords,invs):
  86. rn = normalise_rot(r,deg=deg)
  87. # if self._eultype in ['X','Y']:
  88. # rn[1] = -rn[1]
  89. cn = normalise_coord(c)
  90. self._rots.append(rn) # append(rn) or insert(0, rn) ?
  91. self._coords.append(cn) # append(cn) or insert(0, cn) ?
  92. self._invs.append(bool(i))
  93. if not self.consistent:
  94. warnings.warn("The chain of coord system rotations is not consistent",
  95. category=ConsistencyWarning)
  96. self._update_matrix()
  97. def _update_matrix(self):
  98. self._matrix = npy.identity(3)
  99. self._do_rotation = False
  100. for r,c,i in zip(self._rots, self._coords,self._invs):
  101. rotmat,do_rot,rotnorm = get_rotation_matrix(r,
  102. eulertype=self._eultype)
  103. convmat,do_conv,coordnorm = get_coordconv_matrix(c)
  104. r = npy.dot(rotmat,convmat)
  105. if i: r = r.T
  106. self._matrix = npy.dot(self._matrix, r)
  107. self._do_rotation = self._do_rotation or (do_rot or do_conv)
  108. def _is_coords_consistent(self):
  109. c,i = zip(self._coords,self._invs)[0]
  110. for cnext,inext in zip(self._coords[1:],self._invs[1:]):
  111. if c[i] != cnext[not inext]:
  112. return False
  113. c,i = cnext,inext
  114. return True
  115. consistent = property(_is_coords_consistent,
  116. doc="consistency of the coords transform chain")
  117. def __eq__(self,a):
  118. if type(a) is not type(self): return False
  119. # compare the _rots
  120. v = [npy.allclose(x,y,rtol=0,atol=1e-15) for x,y in zip(self._rots,a._rots)]
  121. return ( npy.array(v).all() and
  122. (self._coords == a._coords) and
  123. (self._invs == a._invs) )
  124. def __call__(self,*args,**kwds):
  125. """Use the rotator to rotate either spherical coordinates (theta, phi)
  126. or a vector (x,y,z). You can use lonla keyword to use longitude, latitude
  127. (in degree) instead of theta, phi (in radian). In this case, returns
  128. longitude, latitude in degree.
  129. Accepted forms:
  130. >>> r = Rotator()
  131. >>> r(x,y,z) # x,y,z either scalars or arrays
  132. >>> r(theta,phi) # theta, phi scalars or arrays
  133. >>> r(lon,lat,lonlat=True) # lon, lat scalars or arrays
  134. >>> r(vec) # vec 1-D array with 3 elements, or 2-D array 3xN
  135. >>> r(direction) # direction 1-D array with 2 elements, or 2xN array
  136. """
  137. if kwds.pop('inv',False): m=self._matrix.T
  138. else: m=self._matrix
  139. lonlat = kwds.pop('lonlat',False)
  140. if len(args) == 1:
  141. arg=args[0]
  142. if not hasattr(arg,'__len__') or len(arg) < 2 or len(arg) > 3:
  143. raise TypeError('Argument must be a sequence of 2 or 3 '
  144. 'elements')
  145. if len(arg) == 2:
  146. return rotateDirection(m,arg[0],arg[1],
  147. self._do_rotation,lonlat=lonlat)
  148. else:
  149. return rotateVector(m,arg[0],arg[1],arg[2],
  150. self._do_rotation)
  151. elif len(args) == 2:
  152. return rotateDirection(m,args[0],args[1],
  153. self._do_rotation,lonlat=lonlat)
  154. elif len(args) == 3:
  155. return rotateVector(m,args[0],args[1],args[2],
  156. self._do_rotation)
  157. else:
  158. raise TypeError('Either 1, 2 or 3 arguments accepted')
  159. def __mul__(self,a):
  160. """Composition of rotation.
  161. """
  162. if not isinstance(a,Rotator):
  163. raise TypeError("A Rotator can only multiply another Rotator "
  164. "(composition of rotations)")
  165. rots = self._rots + a._rots
  166. coords = self._coords + a._coords
  167. invs = self._invs + a._invs
  168. return Rotator(rot=rots,coord=coords,inv=invs,deg=False)
  169. def __rmul__(self,b):
  170. if not isinstance(b,Rotator):
  171. raise TypeError("A Rotator can only be multiplied by another Rotator "
  172. "(composition of rotations)")
  173. rots = b._rots + self._rots
  174. coords = b._coords + self._coords
  175. invs = self._invs + a._invs
  176. return Rotator(rot=rots,coord=coords,inv=invs,deg=False)
  177. def __nonzero__(self):
  178. return self._do_rotation
  179. def get_inverse(self):
  180. rots = self._rots[::-1]
  181. coords = self._coords[::-1]
  182. invs = [ not i for i in self._invs[::-1]]
  183. return Rotator(rot=rots,coord=coords,inv=invs,deg=False)
  184. #I = property(get_inverse,doc='Return a new rotator representing the '
  185. # 'inverse rotation')
  186. def I(self,*args,**kwds):
  187. """Rotate the given vector or direction using the inverse matrix.
  188. rot.I(vec) <==> rot(vec,inv=True)
  189. """
  190. kwds['inv'] = True
  191. return self.__call__(*args,**kwds)
  192. def get_matrix(self):
  193. return npy.matrix(self._matrix)
  194. mat = property(get_matrix,doc='Return a matrix representing the rotation')
  195. def get_coordin(self):
  196. if not self.consistent: return None
  197. c,i = zip(self._coords,self._invs)[-1]
  198. return c[i]
  199. coordin = property(get_coordin, doc="the input coordinate system")
  200. def get_coordout(self):
  201. if not self.consistent: return None
  202. c,i = zip(self._coords,self._invs)[0]
  203. return c[not i]
  204. coordout = property(get_coordout, doc="the output coordinate system")
  205. def get_coordin_str(self):
  206. return coordname.get(self.coordin,'')
  207. coordinstr = property(get_coordin_str, doc="the input coordinate system in str")
  208. def get_coordout_str(self):
  209. return coordname.get(self.coordout,'')
  210. coordoutstr = property(get_coordout_str, doc="the output coordinate system in str")
  211. def get_rots(self):
  212. return self._rots
  213. rots = property(get_rots, doc="the sequence of rots defining")
  214. def get_coords(self):
  215. return self._coords
  216. coords = property(get_coords, doc="the sequence of coords")
  217. def do_rot(self,i):
  218. return not npy.allclose(self.rots[i],npy.zeros(3),rtol=0.,atol=1.e-15)
  219. def angle_ref(self,*args,**kwds):
  220. """Compute the angle between transverse reference direction of initial and final frames
  221. For example, if angle of polarisation is psi in initial frame, it will be psi+angle_ref in final
  222. frame.
  223. Input:
  224. - direction or vector (see Rotator.__call__)
  225. Keywords:
  226. - lonlat: if True, assume input is longitude,latitude in degrees. Otherwise,
  227. theta,phi in radian. Default: False
  228. - inv: if True, use the inverse transforms. Default: False
  229. Return:
  230. - angle in radian (a scalar or an array if input is a sequence of direction/vector)
  231. """
  232. R = self
  233. lonlat = kwds.get('lonlat',False)
  234. inv = kwds.get('inv',False)
  235. if len(args) == 1:
  236. arg=args[0]
  237. if not hasattr(arg,'__len__') or len(arg) < 2 or len(arg) > 3:
  238. raise TypeError('Argument must be a sequence of 2 or 3 '
  239. 'elements')
  240. if len(arg) == 2:
  241. v = dir2vec(arg[0],arg[1],lonlat=lonlat)
  242. else:
  243. v = arg
  244. elif len(args) == 2:
  245. v = dir2vec(args[0],args[1],lonlat=lonlat)
  246. elif len(args) == 3:
  247. v = args
  248. else:
  249. raise TypeError('Either 1, 2 or 3 arguments accepted')
  250. vp = R(v,inv=inv)
  251. north_pole = R([0.,0.,1.],inv=inv)
  252. sinalpha = north_pole[0]*vp[1]-north_pole[1]*vp[0]
  253. cosalpha = north_pole[2] - vp[2]*npy.dot(north_pole,vp)
  254. return npy.arctan2(sinalpha,cosalpha)
  255. def __repr__(self):
  256. return '[ '+', '.join([str(self._coords),
  257. str(self._rots),
  258. str(self._invs)]) +' ]'
  259. __str__ = __repr__
  260. ################################################################
  261. #
  262. # Helpers function for rotation
  263. # used in the Rotator class.
  264. def rotateVector(rotmat,vec,vy=None,vz=None, do_rot=True):
  265. """Rotate a vector (or a list of vectors) using the euler matrix
  266. given as first argument.
  267. Usage: vec_rot = rotateVector(rotmat,vec,vy=None,vz=None,do_rot=True)
  268. - rotmat : the 3x3 rotation matrix
  269. - vec[0], vec[1], vec[2] : vx,vy,vz (can be vectors)
  270. or: vec, vy, vz : vx,vy,vz if vy and vz are given.
  271. - do_rot: really perform the rotation if True, do nothing if false
  272. Return: vx,vy,vz
  273. """
  274. if vy is None and vz is None:
  275. if do_rot: return npy.tensordot(rotmat,vec,axes=(1,0))
  276. else: return vec
  277. elif vy is not None and vz is not None:
  278. if do_rot: return npy.tensordot(rotmat,npy.array([vec,vy,vz]),axes=(1,0))
  279. else: return vec,vy,vz
  280. else:
  281. raise TypeError("You must give either vec only or vec, vy "
  282. "and vz parameters")
  283. def rotateDirection(rotmat,theta,phi=None,do_rot=True,lonlat=False):
  284. """Rotate the direction pointed by theta,phi using the rotation matrix
  285. given as first argument.
  286. Usage: dir_rot = rotateDirection(rotmat,theta,phi=None,do_rot=True)
  287. - rotmat : the 3x3 rotation matrix
  288. - theta[0],theta[1] : theta, phi (can be vectors)
  289. or: theta, phi : theta, phi if phi is given.
  290. - do_rot: really perform the rotation if True, do nothing if false
  291. Return: theta_rot,phi_rot
  292. """
  293. vx,vy,vz=rotateVector(rotmat,dir2vec(theta,phi,lonlat=lonlat),do_rot=do_rot)
  294. return vec2dir(vx,vy,vz,lonlat=lonlat)
  295. def vec2dir(vec,vy=None,vz=None,lonlat=False):
  296. """Transform a vector to a direction given by theta,phi.
  297. """
  298. if vy is None and vz is None:
  299. vx,vy,vz = vec
  300. elif vy is not None and vz is not None:
  301. vx=vec
  302. else:
  303. raise TypeError("You must either give both vy and vz or none of them")
  304. r = npy.sqrt(vx**2+vy**2+vz**2)
  305. theta = npy.arccos(vz/r)
  306. phi = npy.arctan2(vy,vx)
  307. if lonlat:
  308. return npy.asarray([phi*180/npy.pi,90-theta*180/npy.pi])
  309. else:
  310. return npy.asarray([theta,phi])
  311. def dir2vec(theta,phi=None,lonlat=False):
  312. """Transform a direction theta,phi to a unit vector.
  313. """
  314. if phi is None:
  315. theta,phi=theta
  316. if lonlat:
  317. lon,lat=theta,phi
  318. theta,phi = npy.pi/2.-lat*npy.pi/180,lon*npy.pi/180
  319. ct,st,cp,sp = npy.cos(theta),npy.sin(theta),npy.cos(phi),npy.sin(phi)
  320. return npy.asarray([st*cp,st*sp,ct])
  321. def angdist(dir1,dir2,lonlat=False):
  322. """Return the angular distance between dir1 and dir2.
  323. """
  324. if hasattr(lonlat,'__len__') and len(lonlat) == 2:
  325. lonlat1,lonlat2 = lonlat
  326. else:
  327. lonlat1=lonlat2=lonlat
  328. if len(dir1) == 2: # theta,phi or lonlat, convert to vec
  329. vec1 = npy.asarray(dir2vec(dir1,lonlat=lonlat1))
  330. else:
  331. vec1 = npy.asarray(dir1)
  332. if vec1.ndim == 1:
  333. vec1 = npy.expand_dims(vec1,-1)
  334. if len(dir2) == 2:
  335. vec2 = npy.asarray(dir2vec(dir2,lonlat=lonlat1)).T
  336. else:
  337. vec2 = npy.asarray(dir2)
  338. if vec2.ndim == 1:
  339. vec2 = npy.expand_dims(vec2,-1)
  340. # compute scalar product
  341. pscal = (vec1*vec2).sum(axis=0)
  342. return npy.arccos(pscal)
  343. #######################################################
  344. #
  345. # Manage the coord system conventions
  346. #
  347. def check_coord(c):
  348. """Check if parameter is a valid coord system.
  349. Raise a TypeError exception if it is not, otherwise returns the normalized
  350. coordinate system name.
  351. """
  352. if c is None:
  353. return c
  354. if type(c) is not str:
  355. raise TypeError('Coordinate must be a string (G[alactic],'
  356. ' E[cliptic], C[elestial]'
  357. ' or Equatorial=Celestial)')
  358. if c[0].upper() == 'G':
  359. x='G'
  360. elif c[0].upper() == 'E' and c != 'Equatorial':
  361. x='E'
  362. elif c[0].upper() == 'C' or c == 'Equatorial':
  363. x='C'
  364. else:
  365. raise ValueError('Wrong coordinate (either G[alactic],'
  366. ' E[cliptic], C[elestial]'
  367. ' or Equatorial=Celestial)')
  368. return x
  369. def normalise_coord(coord):
  370. """Normalise the coord argument.
  371. Coord sys are either 'E','G', 'C' or 'X' if undefined.
  372. Input: either a string or a sequence of string.
  373. Output: a tuple of two strings, each being one of the norm coord sys name
  374. above.
  375. eg, 'E' -> ['E','E'], ['Ecliptic','G'] -> ['E','G']
  376. None -> ['X','X'] etc.
  377. """
  378. coord_norm = []
  379. if coord is None:
  380. coord = (None,None)
  381. coord=tuple(coord)
  382. if len(coord) > 2:
  383. raise TypeError('Coordinate must be a string (G[alactic],'
  384. ' E[cliptic] or C[elestial])'
  385. ' or a sequence of 2 strings')
  386. for x in coord:
  387. coord_norm.append(check_coord(x))
  388. if len(coord_norm) < 2:
  389. coord_norm.append(coord_norm[0])
  390. return tuple(coord_norm)
  391. def normalise_rot(rot,deg=False):
  392. """Return rot possibly completed with zeroes to reach size 3.
  393. If rot is None, return a vector of 0.
  394. If deg is True, convert from degree to radian, otherwise assume input
  395. is in radian.
  396. """
  397. if deg: convert=npy.pi/180.
  398. else: convert=1.
  399. if rot is None:
  400. rot=npy.zeros(3)
  401. else:
  402. rot=npy.array(rot,npy.float64).flatten()*convert
  403. rot.resize(3)
  404. return rot
  405. def get_rotation_matrix(rot, deg=False, eulertype='ZYX'):
  406. """Return the rotation matrix corresponding to angles given in rot.
  407. Usage: matrot,do_rot,normrot = get_rotation_matrix(rot)
  408. Input:
  409. - rot: either None, an angle or a tuple of 1,2 or 3 angles
  410. corresponding to Euler angles.
  411. Output:
  412. - matrot: 3x3 rotation matrix
  413. - do_rot: True if rotation is not identity, False otherwise
  414. - normrot: the normalized version of the input rot.
  415. """
  416. rot = normalise_rot(rot, deg=deg)
  417. if not npy.allclose(rot,npy.zeros(3),rtol=0.,atol=1.e-15):
  418. do_rot = True
  419. else:
  420. do_rot = False
  421. if eulertype == 'X':
  422. matrot=euler_matrix_new(rot[0],-rot[1],rot[2],X=True)
  423. elif eulertype == 'Y':
  424. matrot=euler_matrix_new(rot[0],-rot[1],rot[2],Y=True)
  425. else:
  426. matrot=euler_matrix_new(rot[0],-rot[1],rot[2],ZYX=True)
  427. return matrot,do_rot,rot
  428. def get_coordconv_matrix(coord):
  429. """Return the rotation matrix corresponding to coord systems given
  430. in coord.
  431. Usage: matconv,do_conv,normcoord = get_coordconv_matrix(coord)
  432. Input:
  433. - coord: a tuple with initial and final coord systems.
  434. See normalise_coord.
  435. Output:
  436. - matconv: the euler matrix for coord sys conversion
  437. - do_conv: True if matconv is not identity, False otherwise
  438. - normcoord: the tuple of initial and final coord sys.
  439. History: adapted from CGIS IDL library.
  440. """
  441. coord_norm = normalise_coord(coord)
  442. if coord_norm[0] == coord_norm[1]:
  443. matconv = npy.identity(3)
  444. do_conv = False
  445. else:
  446. eps = 23.452294 - 0.0130125 - 1.63889E-6 + 5.02778E-7
  447. eps = eps * npy.pi / 180.
  448. # ecliptic to galactic
  449. e2g = npy.array([[-0.054882486, -0.993821033, -0.096476249],
  450. [ 0.494116468, -0.110993846, 0.862281440],
  451. [-0.867661702, -0.000346354, 0.497154957]])
  452. # ecliptic to equatorial
  453. e2q = npy.array([[1., 0. , 0. ],
  454. [0., npy.cos( eps ), -1. * npy.sin( eps )],
  455. [0., npy.sin( eps ), npy.cos( eps ) ]])
  456. # galactic to ecliptic
  457. g2e = npy.linalg.inv(e2g)
  458. # galactic to equatorial
  459. g2q = npy.dot(e2q , g2e)
  460. # equatorial to ecliptic
  461. q2e = npy.linalg.inv(e2q)
  462. # equatorial to galactic
  463. q2g = npy.dot(e2g , q2e)
  464. if coord_norm == ('E','G'):
  465. matconv = e2g
  466. elif coord_norm == ('G','E'):
  467. matconv = g2e
  468. elif coord_norm == ('E','C'):
  469. matconv = e2q
  470. elif coord_norm == ('C','E'):
  471. matconv = q2e
  472. elif coord_norm == ('C','G'):
  473. matconv = q2g
  474. elif coord_norm == ('G','C'):
  475. matconv = g2q
  476. else:
  477. raise ValueError('Wrong coord transform :',coord_norm)
  478. do_conv = True
  479. return matconv,do_conv,coord_norm
  480. ###################################################
  481. ## ##
  482. ## euler functions ##
  483. ## ##
  484. ###### #######
  485. def euler(ai, bi, select, FK4 = 0):
  486. """
  487. NAME:
  488. euler
  489. PURPOSE:
  490. Transform between Galactic, celestial, and ecliptic coordinates.
  491. EXPLANATION:
  492. Use the procedure ASTRO to use this routine interactively
  493. CALLING SEQUENCE:
  494. EULER, AI, BI, AO, BO, [ SELECT, /FK4, SELECT = ]
  495. INPUTS:
  496. AI - Input Longitude in DEGREES, scalar or vector. If only two
  497. parameters are supplied, then AI and BI will be modified
  498. to contain the output longitude and latitude.
  499. BI - Input Latitude in DEGREES
  500. OPTIONAL INPUT:
  501. SELECT - Integer (1-6) specifying type of coordinate
  502. transformation.
  503. SELECT From To | SELECT From To
  504. 1 RA-Dec (2000) Galactic | 4 Ecliptic RA-Dec
  505. 2 Galactic RA-DEC | 5 Ecliptic Galactic
  506. 3 RA-Dec Ecliptic | 6 Galactic Ecliptic
  507. If not supplied as a parameter or keyword, then EULER will prompt
  508. for the value of SELECT
  509. Celestial coordinates (RA, Dec) should be given in equinox J2000
  510. unless the /FK4 keyword is set.
  511. OUTPUTS:
  512. AO - Output Longitude in DEGREES
  513. BO - Output Latitude in DEGREES
  514. INPUT KEYWORD:
  515. /FK4 - If this keyword is set and non-zero, then input and output
  516. celestial and ecliptic coordinates should be given in
  517. equinox B1950.
  518. /SELECT - The coordinate conversion integer (1-6) may
  519. alternatively be specified as a keyword
  520. NOTES:
  521. EULER was changed in December 1998 to use J2000 coordinates as the
  522. default, ** and may be incompatible with earlier versions***.
  523. REVISION HISTORY:
  524. Written W. Landsman, February 1987
  525. Adapted from Fortran by Daryl Yentis NRL
  526. Converted to IDL V5.0 W. Landsman September 1997
  527. Made J2000 the default, added /FK4 keyword
  528. W. Landsman December 1998
  529. Add option to specify SELECT as a keyword W. Landsman March 2003
  530. Converted to python by K. Ganga December 2007
  531. """
  532. # npar = N_params()
  533. # if npar LT 2 then begin
  534. # print,'Syntax - EULER, AI, BI, A0, B0, [ SELECT, /FK4, SELECT= ]'
  535. # print,' AI,BI - Input longitude,latitude in degrees'
  536. # print,' AO,BO - Output longitude, latitude in degrees'
  537. # print,' SELECT - Scalar (1-6) specifying transformation type'
  538. # return
  539. # endif
  540. PI = npy.pi
  541. twopi = 2.0*PI
  542. fourpi = 4.0*PI
  543. deg_to_rad = 180.0/PI
  544. #
  545. # ; J2000 coordinate conversions are based on the following constants
  546. # ; (see the Hipparcos explanatory supplement).
  547. # ; eps = 23.4392911111 # Obliquity of the ecliptic
  548. # ; alphaG = 192.85948d Right Ascension of Galactic North Pole
  549. # ; deltaG = 27.12825d Declination of Galactic North Pole
  550. # ; lomega = 32.93192d Galactic longitude of celestial equator
  551. # ; alphaE = 180.02322d Ecliptic longitude of Galactic North Pole
  552. # ; deltaE = 29.811438523d Ecliptic latitude of Galactic North Pole
  553. # ; Eomega = 6.3839743d Galactic longitude of ecliptic equator
  554. #
  555. if FK4 == 1:
  556. equinox = '(B1950)'
  557. psi = [ 0.57595865315, 4.9261918136,
  558. 0.00000000000, 0.0000000000,
  559. 0.11129056012, 4.7005372834]
  560. stheta =[ 0.88781538514,-0.88781538514,
  561. 0.39788119938,-0.39788119938,
  562. 0.86766174755,-0.86766174755]
  563. ctheta =[ 0.46019978478, 0.46019978478,
  564. 0.91743694670, 0.91743694670,
  565. 0.49715499774, 0.49715499774]
  566. phi = [ 4.9261918136, 0.57595865315,
  567. 0.0000000000, 0.00000000000,
  568. 4.7005372834, 0.11129056012]
  569. else:
  570. equinox = '(J2000)'
  571. psi = [ 0.57477043300, 4.9368292465,
  572. 0.00000000000, 0.0000000000,
  573. 0.11142137093, 4.71279419371]
  574. stheta =[ 0.88998808748,-0.88998808748,
  575. 0.39777715593,-0.39777715593,
  576. 0.86766622025,-0.86766622025]
  577. ctheta =[ 0.45598377618, 0.45598377618,
  578. 0.91748206207, 0.91748206207,
  579. 0.49714719172, 0.49714719172]
  580. phi = [ 4.9368292465, 0.57477043300,
  581. 0.0000000000, 0.00000000000,
  582. 4.71279419371, 0.11142137093]
  583. #
  584. i = select - 1 # IDL offset
  585. a = ai/deg_to_rad - phi[i]
  586. b = bi/deg_to_rad
  587. sb = npy.sin(b)
  588. cb = npy.cos(b)
  589. cbsa = cb * npy.sin(a)
  590. b = -stheta[i] * cbsa + ctheta[i] * sb
  591. #bo = math.asin(where(b<1.0, b, 1.0)*deg_to_rad)
  592. bo = npy.arcsin(b)*deg_to_rad
  593. #
  594. a = npy.arctan2( ctheta[i] * cbsa + stheta[i] * sb, cb * npy.cos(a) )
  595. ao = npy.fmod( (a+psi[i]+fourpi), twopi) * deg_to_rad
  596. return ao, bo
  597. def euler_matrix_new(a1,a2,a3,X=True,Y=False,ZYX=False,deg=False):
  598. """
  599. NAME:
  600. euler_matrix_new
  601. PURPOSE:
  602. computes the Euler matrix of an arbitrary rotation described
  603. by 3 Euler angles
  604. correct bugs present in Euler_Matrix
  605. CALLING SEQUENCE:
  606. result = euler_matrix_new (a1, a2, a3 [,X, Y, ZYX, DEG ])
  607. INPUTS:
  608. a1, a2, a3 = Euler angles, scalar
  609. (in radian by default, in degree if DEG is set)
  610. all the angles are measured counterclockwise
  611. correspond to x, y, zyx-conventions (see Goldstein)
  612. the default is x
  613. KEYWORD PARAMETERS:
  614. DEG : if set the angle are measured in degree
  615. X : rotation a1 around original Z
  616. rotation a2 around interm X
  617. rotation a3 around final Z
  618. DEFAULT, classical mechanics convention
  619. Y : rotation a1 around original Z
  620. rotation a2 around interm Y
  621. rotation a3 around final Z
  622. quantum mechanics convention (override X)
  623. ZYX : rotation a1 around original Z
  624. rotation a2 around interm Y
  625. rotation a3 around final X
  626. aeronautics convention (override X)
  627. * these last three keywords are obviously mutually exclusive *
  628. OUTPUTS:
  629. result is a 3x3 matrix
  630. USAGE:
  631. if vec is an Nx3 array containing N 3D vectors,
  632. vec # euler_matrix_new(a1,a2,a3,/Y) will be the rotated vectors
  633. MODIFICATION HISTORY:
  634. March 2002, EH, Caltech, rewritting of euler_matrix
  635. convention euler_matrix_new euler_matrix
  636. X: M_new(a,b,c,/X) = M_old(-a,-b,-c,/X) = Transpose( M_old(c, b, a,/X))
  637. Y: M_new(a,b,c,/Y) = M_old(-a, b,-c,/Y) = Transpose( M_old(c,-b, a,/Y))
  638. ZYX: M_new(a,b,c,/Z) = M_old(-a, b,-c,/Z)
  639. """
  640. t_k = 0
  641. if ZYX: t_k = t_k + 1
  642. #if X: t_k = t_k + 1
  643. if Y: t_k = t_k + 1
  644. if t_k > 1:
  645. raise ValueError('Choose either X, Y or ZYX convention')
  646. convert = 1.0
  647. if deg:
  648. convert = npy.pi/180.
  649. c1 = npy.cos(a1*convert)
  650. s1 = npy.sin(a1*convert)
  651. c2 = npy.cos(a2*convert)
  652. s2 = npy.sin(a2*convert)
  653. c3 = npy.cos(a3*convert)
  654. s3 = npy.sin(a3*convert)
  655. if ZYX:
  656. m1 = npy.array([[ c1,-s1, 0],
  657. [ s1, c1, 0],
  658. [ 0, 0, 1]]) # around z
  659. m2 = npy.array([[ c2, 0, s2],
  660. [ 0, 1, 0],
  661. [-s2, 0, c2]]) # around y
  662. m3 = npy.array([[ 1, 0, 0],
  663. [ 0, c3,-s3],
  664. [ 0, s3, c3]]) # around x
  665. elif Y:
  666. m1 = npy.array([[ c1,-s1, 0],
  667. [ s1, c1, 0],
  668. [ 0, 0, 1]]) # around z
  669. m2 = npy.array([[ c2, 0, s2],
  670. [ 0, 1, 0],
  671. [-s2, 0, c2]]) # around y
  672. m3 = npy.array([[ c3,-s3, 0],
  673. [ s3, c3, 0],
  674. [ 0, 0, 1]]) # around z
  675. else:
  676. m1 = npy.array([[ c1,-s1, 0],
  677. [ s1, c1, 0],
  678. [ 0, 0, 1]]) # around z
  679. m2 = npy.array([[ 1, 0, 0],
  680. [ 0, c2,-s2],
  681. [ 0, s2, c2]]) # around x
  682. m3 = npy.array([[ c3,-s3, 0],
  683. [ s3, c3, 0],
  684. [ 0, 0, 1]]) # around z
  685. M = npy.dot(m3.T,npy.dot(m2.T,m1.T))
  686. return M