miriad 4.1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/miriad.rb ADDED
@@ -0,0 +1,564 @@
1
+ # The MIRIAD-Ruby package...
2
+ #
3
+ # 1. makes MIRIAD datasets accessible from Ruby by wrapping the MIRIAD UVIO
4
+ # routines.
5
+ #
6
+ # 2. makes Ruby usable with MIRIAD datasets by adding convenience, utility, and
7
+ # astronomy related methods to Ruby classes.
8
+ #
9
+ #--
10
+ #$Id: miriad.rb 901 2008-04-17 22:44:07Z davidm $
11
+ #++
12
+
13
+ require 'rbconfig'
14
+ require 'rubygems'
15
+ require 'date'
16
+ require 'narray'
17
+ miriad_shared_lib = 'miriad.' + Config::CONFIG['DLEXT']
18
+ require miriad_shared_lib
19
+ require 'miriad_ruby_shared_library' if false # Fake out RDoc
20
+ #require 'fftw3'
21
+
22
+ # Add CKMS constant to Math module
23
+ Math::const_set :CKMS, 299792.458 unless Math::const_defined? :CKMS
24
+
25
+ # The MIRIAD package adds angle conversion methods to the Math module
26
+ module Math
27
+ # Convert possibly non-integer degrees +fr+ to [degrees, minutes, seconds]
28
+ # where degrees and minutes are integers.
29
+ def self.d_to_dms(fr)
30
+ sign = fr <=> 0
31
+ fr *= sign
32
+ d = fr.to_i; fr %= 1; fr *= 60
33
+ m = fr.to_i; fr %= 1; fr *= 60
34
+ return d*sign, m, fr
35
+ end
36
+
37
+ # Convert possibly non-integer hours +fr+ to [hours, minutes, seconds]
38
+ # where hours and minutes are integers.
39
+ def self.h_to_hms(fr); d_to_dms(fr); end
40
+
41
+ # Convert degrees, minutes, seconds to possibly non-integer degrees.
42
+ # Missing arguments are assumed to be 0.
43
+ def self.dms_to_d(*args)
44
+ d = 0.to_r
45
+ d += args.shift unless args.empty?
46
+ sign = d <=> 0
47
+ d += args.shift * Rational(sign,60) unless args.empty?
48
+ d += args.shift * Rational(sign,3600) unless args.empty?
49
+ return d
50
+ end
51
+
52
+ # Convert hours, minutes, seconds to possibly non-integer hours.
53
+ # Missing arguments are assumed to be 0.
54
+ def self.hms_to_h(*args); dms_to_d(*args); end
55
+
56
+ # Convert degrees to radians
57
+ def self.d2r(d) d*PI/180; end
58
+ # Convert radians to degrees
59
+ def self.r2d(r) r*180/PI; end
60
+ # Convert hours to radians
61
+ def self.h2r(h) h*PI/12; end
62
+ # Convert radians to hours
63
+ def self.r2h(r) r*12/PI; end
64
+ # Convert degrees to hours
65
+ def self.d2h(d) d/15.0; end
66
+ # Convert hours to degrees
67
+ def self.h2d(h) h*15.0; end
68
+ end
69
+
70
+ # Th MIRIAD package adds angle conversion and angle formatting methods to
71
+ # Numeric class.
72
+ class Numeric
73
+ # Convert +self+ to [degrees, minutes, seconds] where degrees and minutes are
74
+ # integers.
75
+ def to_dms() Math.d_to_dms(self); end
76
+ # Convert +self+ to [hours, minutes, seconds] where hours and minutes are
77
+ # integers.
78
+ alias :to_hms :to_dms
79
+ # Convert +self+ to "##d##m##.###s" format with +prec+ fractional places.
80
+ def to_dmsstr(prec=3)
81
+ width = prec == 0 ? 2 : prec+3
82
+ "%02dd%02dm%0#{width}.#{prec}fs" % to_dms
83
+ end
84
+ # Convert +self+ to "##:##:##.###" format with +prec+ fractional places.
85
+ def to_hmsstr(prec=3)
86
+ width = prec == 0 ? 2 : prec+3
87
+ "%02d:%02d:%0#{width}.#{prec}f" % to_dms
88
+ end
89
+ # Convert +self+ from degrees to radians (i.e. <tt>Math.d2r(self)</tt>).
90
+ def d2r() Math.d2r(self); end
91
+ # Convert +self+ from radians to degrees (i.e. <tt>Math.r2d(self)</tt>).
92
+ def r2d() Math.r2d(self); end
93
+ # Convert +self+ from hours to radians (i.e. <tt>Math.h2r(self)</tt>).
94
+ def h2r() Math.h2r(self); end
95
+ # Convert +self+ from radians to hours (i.e. <tt>Math.r2h(self)</tt>).
96
+ def r2h() Math.r2h(self); end
97
+ # Convert +self+ from degrees to hours (i.e. <tt>Math.d2h(self)</tt>).
98
+ def d2h() Math.d2h(self); end
99
+ # Convert +self+ from hours to degrees (i.e. <tt>Math.h2d(self)</tt>).
100
+ def h2d() Math.h2d(self); end
101
+ end
102
+
103
+ # The MIRIAD package adds angle conversion methods to Array class.
104
+ class Array
105
+ # Convert +self+ from [degrees, minutes, seconds] to degrees.
106
+ # Missing arguments are assumed to be 0.
107
+ def dms_to_d; Math::dms_to_d(*self); end
108
+ # Convert +self+ from [hours, minutes, seconds] to hours.
109
+ # Missing arguments are assumed to be 0.
110
+ alias :hms_to_h :dms_to_d
111
+ end
112
+
113
+ # The MIRIAD package adds angle parsing methods to String class.
114
+ class String
115
+ # Parse a "dd:mm:ss.sss" String to Float degrees.
116
+ # <b>NOT</b> the inverse of Numeric#to_dmsstr (but may become so).
117
+ def dms_to_d; Math::dms_to_d(*split(':').map{|s| s.to_f}); end
118
+ # Parse a "hh:mm:ss.sss" String to Float hours.
119
+ alias :hms_to_h :dms_to_d
120
+ end
121
+
122
+ # The MIRIAD package adds astronomy-related and other utilty methods to
123
+ # the +DateTime+ class.
124
+ class DateTime
125
+
126
+ # The J2000 epoch
127
+ J2000 = civil(2000,1,1,12)
128
+
129
+ # Create a +DateTime+ object from a numeric Astronomical Julian Date.
130
+ def self.ajd(d)
131
+ J2000 + d - J2000.ajd
132
+ end
133
+
134
+ # Besselian year at the time represented by +self+.
135
+ #
136
+ # Adapted from http://hpiers.obspm.fr/eop-pc/models/constants.html
137
+ def by
138
+ 2000+(amjd-51544.5)/365.242189813
139
+ end
140
+
141
+ def ut2_ut1
142
+ # Taken from
143
+ # http://www.iers.org/products/6/11136/orig/bulletina-xx-042.txt
144
+ pi2t=Math::PI*2*(by%1)
145
+ ENV['UT2_UT1'] || 0.022 * Math::sin(pi2t) \
146
+ - 0.012 * Math::cos(pi2t) \
147
+ - 0.006 * Math::sin(2*pi2t) \
148
+ + 0.007 * Math::cos(2*pi2t)
149
+ end
150
+
151
+ # UT1-UTC (unit is seconds) at the time represented by +self+
152
+ #
153
+ # This is an increasingly out-of-date approximation only. Taken from
154
+ # http://www.iers.org/products/6/11136/orig/bulletina-xx-042.txt
155
+ def ut1_utc
156
+ # TODO Get more accurate measurement of this value from usno
157
+ ENV.has_key?('UT1_UTC') ?
158
+ ENV['UT1_UTC'].to_f : -0.2221 - 0.00092 * (amjd - 54399) - ut2_ut1
159
+ end
160
+
161
+ # A +DateTime+ object corresponding to the same time as +self+, but
162
+ # with an offset of 0. Returns +self+ if <tt>self.offset == 0</tt>.
163
+ def to_utc
164
+ (offset == 0) ? self : new_offset(0)
165
+ end
166
+
167
+ # Create a +DateTime+ object from a numeric Astronomical Julian Date.
168
+ def self.utc
169
+ now.to_utc
170
+ end
171
+
172
+ # UT1 (expressed in hours) at the time represented by +self+
173
+ def ut1
174
+ ((amjd % 1) * 24 + ut1_utc/(60*60)) % 24
175
+ end
176
+
177
+ # UT1 (expressed in hours) at the current time.
178
+ def self.ut1
179
+ now.ut1
180
+ end
181
+
182
+ # Greenwich Mean Sidereal Time at the time represented by +self+.
183
+ #
184
+ # Adapted from http://aa.usno.navy.mil/faq/docs/GAST.php
185
+ def gmst
186
+ # The local variables here have been slightly renamed from the reference to
187
+ # be more ruby-esque. Here is the mapping...
188
+ #
189
+ # Local Reference
190
+ # jdut1 JD
191
+ # jd0 JD0
192
+ # h H
193
+ # d D
194
+ # d0 D0
195
+ # t T
196
+
197
+ # The UT Julian date of the time of interest
198
+ jdut1 = ajd + ut1_utc/(24*60*60)
199
+ # Julian date of the previous midnight (0h) UT
200
+ jd0 = (jdut1-0.5).floor + 0.5
201
+ # Hours of UT elapsed since jd0
202
+ h = (jdut1 - jd0) * 24.0
203
+ # Days and fraction of day since J2000 epoch
204
+ d = jdut1 - J2000.ajd
205
+ # Integer days (and a half) since epoch
206
+ d0 = jd0 - J2000.ajd
207
+ # Fractional centuries since epoch
208
+ t = d/36525.0
209
+ # gmst
210
+ (6.697374558 + 0.06570982441908 * d0 + 1.00273790935 * h + 0.000026 * t**2) % 24.0
211
+ end
212
+
213
+ # Equation of the Equinoxes at the time represented by +self+.
214
+ #
215
+ # Adapted from http://aa.usno.navy.mil/faq/docs/GAST.php
216
+ def eqeq
217
+ # The local variables here have been slightly renamed from the reference to
218
+ # be more ruby-esque. Here is the mapping...
219
+ #
220
+ # Local Reference
221
+ # jdut1 JD
222
+ # d D
223
+ # omega Omega (Greek letter)
224
+ # dpsi Delta Psi (Greek letters)
225
+ # eps epsilon (Greek letter)
226
+
227
+ # The UT Julian date of the time of interest
228
+ jdut1 = ajd + ut1_utc/(24*60*60)
229
+ # Days and fraction of day since J2000 epoch
230
+ d = jdut1 - J2000.ajd
231
+ # Longitude of the ascending node of the Moon
232
+ omega = 125.04 - 0.052954 * d
233
+ # Mean Longitude of the Sun
234
+ l = 280.47 + 0.98565 * d
235
+ # Delta psi
236
+ dpsi = -0.000319 * Math.sin(omega.d2r) \
237
+ -0.000024 * Math.sin(2*l.d2r)
238
+ # Obliquity
239
+ eps = 23.4393 - 0.0000004 * d
240
+ # eqeq
241
+ dpsi * Math.cos(eps.d2r)
242
+ end
243
+
244
+ # Greenwich Apparent Sidereal Time at the time represented by +self+.
245
+ def gast
246
+ (gmst + eqeq) % 24.0
247
+ end
248
+
249
+ # Local Mean Sidereal Time in hours at the time represented by
250
+ # +self+. +longitude+ is in degrees.
251
+ def lmst(longitude=0.0)
252
+ (gmst + longitude.d2h) % 24.0
253
+ end
254
+
255
+ # Local Apparent Sidereal Time in hours at the time represented by
256
+ # +self+. +longitude+ is in degrees.
257
+ def last(longitude=0.0)
258
+ (gast + longitude.d2h) % 24.0
259
+ end
260
+ end
261
+
262
+ # A subclass of StandardError raised by wrapped MIRIAD code.
263
+ class MirlibError < StandardError
264
+ end
265
+
266
+ # In addition to the Uvio and Visibility classes, the Miriad module also
267
+ # provides some utility methods.
268
+ module Miriad
269
+
270
+ # The version of mirlib on which this package is based.
271
+ MIRLIB_VERSION = "4.1.0"
272
+
273
+ # Contains Polarization code constants. The values follow the AIPS/FITS
274
+ # conventions.
275
+ module Pol
276
+ # Stokes I
277
+ I = 1
278
+ # Stokes Q
279
+ Q = 2
280
+ # Stokes U
281
+ U = 3
282
+ # Stokes V
283
+ V = 4
284
+ # Circular RR
285
+ RR = -1
286
+ # Circular LL
287
+ LL = -2
288
+ # Circular RL
289
+ RL = -3
290
+ # Circular LR
291
+ LR = -4
292
+ # Linear XX
293
+ XX = -5
294
+ # Linear YY
295
+ YY = -6
296
+ # Linear XY
297
+ XY = -7
298
+ # Linear YX
299
+ YX = -8
300
+ end
301
+
302
+ # Compute the [u,v,w] projection of a baseline with endpoints <tt>xyz1</tt>
303
+ # and <tt>xyz2</tt> in the direction of +obsra+, +obsdec+ (i.e. right
304
+ # ascension and declination in the current epoch) at the local apparent
305
+ # sidereal time +lst+. <tt>xyz1</tt> and <tt>xyz2</tt> are three element
306
+ # arrays containing geocentric coordinates of the baseline endpoints, and
307
+ # +obsra+, +obsdec+, and +lst+ are all in radians
308
+ #
309
+ # The body of this method was transcribed from the MIRIAD source file
310
+ # <tt>uvcal.for</tt>.
311
+ def self.xyz2uvw(xyz1,xyz2,obsra,obsdec,lst)
312
+ x1,y1,z1 = xyz1
313
+ x2,y2,z2 = xyz2
314
+
315
+ ha = lst - obsra
316
+ sinha = Math.sin(ha)
317
+ cosha = Math.cos(ha)
318
+ sind = Math.sin(obsdec)
319
+ cosd = Math.cos(obsdec)
320
+
321
+ bxx = x2 - x1
322
+ byy = y2 - y1
323
+ bzz = z2 - z1
324
+
325
+ u = bxx * sinha + byy * cosha
326
+ v = -(bxx * cosha - byy * sinha)*sind + bzz*cosd
327
+ w = (bxx * cosha - byy * sinha)*cosd + bzz*sind
328
+
329
+ [u,v,w]
330
+ end
331
+
332
+ # Precess from one mean RA,DEC to another.
333
+ #
334
+ # A simple precession routine, to precess from one set of mean
335
+ # equatorial coordinates (RA,DEC), to another at a different epoch.
336
+ # This is accurate to order 0.3 arcsec over 50 years.
337
+ #
338
+ # Reference:
339
+ # Explanatory Supplement to the Astronomical Almanac, 1993. p 105-106.
340
+ #
341
+ # NOTE: This does not take account of atmospheric refraction,
342
+ # nutation, aberration nor gravitational deflection.
343
+ #
344
+ # Input:
345
+ # ra,dec RA,DEC at the from_jd epoch (radians).
346
+ # to_jd Julian day of the new epoch. (defaults to current time)
347
+ # from_jd Julian day of the known epoch. (defaults to J2000)
348
+ #
349
+ # Output:
350
+ # [ra+,dec] Precessed coordinates (radians).
351
+ #
352
+ # The body of this method was transcribed from the miriad source file
353
+ # <tt>ephem.for</tt> originally created by Bob Sault.
354
+ def self.precess(ra, dec, to_jd=DateTime.now.ajd, from_jd=DateTime::J2000.ajd)
355
+ t = (from_jd - 2451545)/36525
356
+ m = Math::PI/180 * (1.2812323 + (0.0003879 + 0.0000101*t)*t)*t
357
+ n = Math::PI/180 * (0.5567530 - (0.0001185 + 0.0000116*t)*t)*t
358
+ rm = ra - 0.5*(m + n*Math.sin(ra)*Math.tan(dec))
359
+ dm = dec - 0.5*n*Math.cos(rm)
360
+ #
361
+ # J2000 coordinates.
362
+ #
363
+ r0 = ra - m - n*Math.sin(rm)*Math.tan(dm)
364
+ d0 = dec - n*Math.cos(rm)
365
+ #
366
+ # Coordinates of the other epoch.
367
+ #
368
+ t = (to_jd - 2451545)/36525
369
+ m = Math::PI/180 * (1.2812323 + (0.0003879 + 0.0000101*t)*t)*t
370
+ n = Math::PI/180 * (0.5567530 - (0.0001185 + 0.0000116*t)*t)*t
371
+ rm = r0 + 0.5*(m + n*Math.sin(r0)*Math.tan(d0))
372
+ dm = d0 - 0.5*n*Math.cos(rm)
373
+
374
+ ra = r0 + m + n*Math.sin(rm)*Math.tan(dm)
375
+ dec = d0 + n*Math.cos(rm)
376
+
377
+ [ra, dec]
378
+ end
379
+
380
+ # Format a MIRIAD baseline number as "A1-A2".
381
+ def self.basstr(baseline)
382
+ baseline = baseline.to_i
383
+ "%d-%d" % basant(baseline)
384
+ end
385
+
386
+ # Convert a MIRIAD baseline number into two antenna numbers.
387
+ def self.basant(baseline)
388
+ baseline = baseline.to_i
389
+ [baseline/256, baseline%256]
390
+ end
391
+
392
+ # Convert two antenna numbers into a MIRIAD baseline number
393
+ def self.antbas(a1,a2)
394
+ a1, a2 = a2, a1 if a2 < a1
395
+ a1.to_i*256 + a2.to_i
396
+ end
397
+
398
+ # Convert two antenna numbers, <tt>a1</tt> and <tt>a2</tt>, into a baseline
399
+ # index (NOT a MIRIAD baseline number) using +n+ as the maximum antenna
400
+ # number.
401
+ def self.basidx(a1,a2,n)
402
+ a1, a2 = a2, a1 if a2 < a1
403
+ return a1 + ((2*n+1)*(a2-a1) - (a2-a1)**2) / 2
404
+ end
405
+
406
+ # Convert a baseline index (NOT a MIRIAD baseline number) +i+ into two
407
+ # antenna numbers using +n+ as the maximum antenna number.
408
+ def self.idxbas(i,n)
409
+ b = -(2*n+1)
410
+ ac = 2*i
411
+ r = Math.sqrt(b**2-4*ac)
412
+ d = (-b-r.floor)/2
413
+ d -= 1 while (a1 = i + (b*d + d**2)/2) < 0
414
+ a2 = a1 + d
415
+ [a1, a2]
416
+ end
417
+
418
+ # The Visibility class holds preamble, visdata, and flags for one integration
419
+ # on one baseline. It also provides convenience methods to query and access
420
+ # these attributes and their sub-attributes.
421
+ class Visibility
422
+ # The MIRIAD preamble
423
+ attr_accessor :preamble
424
+ # The MIRIAD visdata
425
+ attr_accessor :data
426
+ # The MIRIAD flags
427
+ attr_accessor :flags
428
+ # Constructs a new Visibility object containing the given +preamble+,
429
+ # +data+, and +flags+ objects. Not used often. Usually Visibility objects
430
+ # are obtained from Uvio#read.
431
+ def initialize(preamble, data, flags)
432
+ @preamble = preamble
433
+ @data = data
434
+ @flags = flags
435
+ end
436
+ # call-seq: Visibility[preamble, data, flags]
437
+ #
438
+ # Alternate way to contruct a Visibility object.
439
+ def self.[](preamble, data, flags)
440
+ self.new(preamble, data, flags)
441
+ end
442
+ # Returns the [u,v,w] coordinates from the preamble.
443
+ def coord() preamble[0..2]; end
444
+ # Returns the Julian date from the preamble.
445
+ def jd() preamble[3]; end
446
+ # Returns the baseline number from the preamble.
447
+ def baseline() preamble[4].to_i; end
448
+ # Returns a DateTime object corresponding to the Julian date from the
449
+ # preamble.
450
+ def time() DateTime.ajd(jd); end
451
+ # Returns the two antenna numbers corresponding to the baseline number
452
+ # from the preamble.
453
+ def ants() Miriad.basant(baseline); end
454
+ # True if the baseline is an autocorrelation.
455
+ def auto?() a1,a2=Miriad.basant(baseline); a1 == a2; end
456
+ # True if the baseline is a cross correlation.
457
+ def cross?() a1,a2=Miriad.basant(baseline); a1 != a2; end
458
+ ## TODO Make this more robust (i.e. less hard-coded)
459
+ #def lags()
460
+ # i = NArray.int(1024).indgen!(512) % 1024
461
+ # d = data[i]
462
+ # d[0] = 0 # Zero-out the DC channel
463
+ # d -= d.mean # Remove the mean
464
+ # FFTW3.dft(d,1)/1024
465
+ #end
466
+ end
467
+
468
+ # The Uvio class is mostly a SWIG wrapper around the UVIO C routines from
469
+ # MIRIAD. Some convenience methods are added in
470
+ # miriad.rb[link:files/miriad_rb.html].
471
+ class Uvio
472
+ # Calls <tt>putvr(var, data, ?a)</tt>
473
+ def putvra(var,data) putvr(var, data, ?a); end
474
+ # Calls <tt>putvr(var, data, ?i)</tt>
475
+ def putvri(var,data) putvr(var, data, ?i); end
476
+ # Calls <tt>putvr(var, data, ?r)</tt>
477
+ def putvrr(var,data) putvr(var, data, ?r); end
478
+ # Calls <tt>putvr(var, data, ?d)</tt>
479
+ def putvrd(var,data) putvr(var, data, ?d); end
480
+
481
+ # Convenience wrapper around self.new. Self.new may become private, so use
482
+ # this method instead.
483
+ def self.open(name,mode='r')
484
+ raise 'invalid dataset name' if name.nil? or name.empty?
485
+ case mode[0]
486
+ when ?r then
487
+ raise 'invalid dataset given' unless test ?d, name
488
+ when ?w then
489
+ raise 'dataset exists' if test ?d, name
490
+ when ?a then
491
+ raise 'invalid dataset given' unless test ?d, name
492
+ end
493
+
494
+ self.new(name,mode)
495
+ end
496
+
497
+ # call-seq:
498
+ # read() -> uvread(5, nchan, nchan) -> Visibility
499
+ # read(nil) -> uvread(5, nchan, nchan) -> Visibility
500
+ # read(vis) -> uvread(vis.preamble, vis.data, vis.flags) -> Visibility
501
+ # read(*args) -> uvread(*args) -> Visibility
502
+ #
503
+ # Convenience wrapper around uvread. Shortens data and flags if they were
504
+ # passed in longer than necessary. Always returns a Visibility, except at
505
+ # end of file in which case it returns +nil+.
506
+ #
507
+ # See uvread for details.
508
+ #
509
+ # <b>This method should be preferred over uvread. uvread may disappear in
510
+ # future versions!</b>
511
+ def read(*args)
512
+ if args.length == 0 || args[0].nil?
513
+ n, p, d, f = uvread(5,nchan,nchan)
514
+ elsif args.length == 1 && Visibility === args[0]
515
+ n, p, d, f = uvread(args[0].preamble,args[0].data,args[0].flags)
516
+ else
517
+ n, p, d, f = uvread(*args)
518
+ end
519
+
520
+ # Return nil on EOF
521
+ return nil if n == 0
522
+
523
+ # FIXME for Array inputs
524
+ # If nread is different than what was passed in
525
+ if n != d.length
526
+ # Shorten data and flags
527
+ d = d[0...n]
528
+ f = f[0...n]
529
+ end
530
+
531
+ # Return Visibility
532
+ return args[0] if Visibility === args[0]
533
+ Visibility[p,d,f]
534
+ end
535
+
536
+ # call-seq: baseline -> [ant1, ant2]
537
+ #
538
+ # Decodes the value of the +baseline+ uv variable and returns a two element
539
+ # array containing the two antenna numbers.
540
+ def baseline
541
+ bl = getvr('baseline')
542
+ bl = [bl.to_i/256, bl.to_i%256] if bl
543
+ end
544
+
545
+ # Uvio.foo, if foo is not a predefined attribute or method, will return the
546
+ # current value of the MIRIAD UV variable +foo+ or +nil+ if it doesn't
547
+ # (yet) exist.
548
+ #
549
+ # Uvio.foo=bar, if foo= is not a predefined attribute or method, will set
550
+ # the value of the MIRIAD UV variable +foo+ to +bar+. Only valid for
551
+ # datasets opened in write mode. [SETTER FUNCTIONALITY NOT YET
552
+ # IMPLEMENTED!]
553
+ def method_missing(sym,*args)
554
+ val = nil
555
+ var = sym.to_s
556
+ case var
557
+ when /=$/: break
558
+ else val = getvr(var)
559
+ end
560
+ val
561
+ end
562
+ end
563
+
564
+ end