1 from __future__ import division
2 """
3 Units class hierarchy: is used to manage both wavelength and flux
4 unit conversions
5
6 Warning: vegamag unit conversions require spectrum and locations => circular
7 imports.
8 """
9
10 import math
11 import numpy as N
12 import locations, spectrum
13
14 import observationmode
15
16
17
18
19 C = 2.99792458e18
20 H = 6.62620E-27
21 HC = H * C
22 ABZERO = -48.60
23 STZERO = -21.10
24
25
26
27
29 """This needs to be a factory function in order to return an object
30 of the correct subclass."""
31 if isinstance(uname,BaseUnit):
32 return uname
33 else:
34 try:
35 if issubclass(uname,BaseUnit):
36 return uname()
37 except TypeError:
38
39 try:
40 return factory(uname)
41 except KeyError:
42 if uname == str(None):
43 return None
44 else:
45 raise ValueError("Unknown units %s"%uname)
46
47
49 """Method to allow smart comparisons between classes, instances,
50 and string representations of units and give the right answer."""
51 match=False
52
53 if a == b:
54 return True
55 else:
56
57 try:
58 if isinstance(a,b):
59 return True
60 except TypeError:
61 try:
62 if isinstance(b,a):
63 return True
64 except TypeError:
65
66 try:
67 if isinstance(a,type(b)):
68 return True
69 except TypeError:
70 try:
71 if isinstance(b,type(a)):
72 return True
73 except TypeError:
74
75 if str(a).lower() == str(b).lower():
76 return True
77 else:
78 return False
79
80
81
82
84 """ Base class for all units; defines UI"""
86 self.Dispatch=None
87 self.name=uname
88
91
92 - def Convert(self,wave,flux,target_units):
93
94 try:
95 return self.Dispatch[target_units.lower()](wave,flux)
96 except KeyError:
97 raise TypeError("%s cannot be converted to %s"%(self.name,
98 target_units))
99
101 """All WaveUnits know how to convert themselves to Angstroms"""
103 self.name=None
104 self.isFlux = False
105 self.Dispatch = {'angstrom' : self.ToAngstrom}
106
107 - def Convert(self,wave,target_units):
108 """WaveUnits only need a wavelength table to do a conversion."""
109 try:
110 return self.Dispatch[target_units.lower()](wave)
111 except KeyError:
112 raise TypeError("%s cannot be converted to %s"%(self.name,
113 target_units))
114
116 raise NotImplementedError("Required method ToAngstrom not yet implemented")
117
119 """All FluxUnits know how to convert themselves to Photlam"""
121 self.isFlux = True
122 self.isMag = False
123 self.isDensity = True
124 self.name=None
125 self.Dispatch = {'photlam':self.ToPhotlam}
126 self.nativewave = Angstrom
127
128
129
130
131
132
133 - def Convert(self,wave,flux,target_units):
134 """FluxUnits need both wavelength and flux tables to do a unit conversion."""
135 try:
136 return self.Dispatch[target_units](wave,flux)
137 except KeyError:
138 raise TypeError("%s is not a valid flux unit"%(target_units))
139
141 raise NotImplementedError("Required method ToPhotlam not yet implemented")
142
143
145 """Base class for magnitudes, which often require special handling"""
147 FluxUnits.__init__(self)
148 self.isMag=True
149 self.linunit=None
150 self.zeropoint=None
151
152
153
156 WaveUnits.__init__(self)
157 self.name = 'angstrom'
158 self.Dispatch = {'angstrom' : self.ToAngstrom,
159 'angstroms' : self.ToAngstrom,
160 'nm': self.ToNm,
161 'micron': self.ToMicron,
162 'microns': self.ToMicron,
163 '1/um': self.ToInverseMicron,
164 'inversemicron': self.ToInverseMicron,
165 'inversemicrons': self.ToInverseMicron,
166 'mm': self.ToMm,
167 'cm': self.ToCm,
168 'm': self.ToMeter,
169 'hz': self.ToHz}
170
171
173 if hasattr(wave,'copy'):
174 return wave.copy()
175 else:
176 return wave
177
178 - def ToNm(self, wave):
180
183
186
187 - def ToMm(self, wave):
189
190 - def ToCm(self, wave):
192
194 return wave * 1.0e-10
195
196 - def ToHz(self, wave):
198
199
200
201
203 ''' photlam = photons cm^-2 s^-1 Ang^-1)'''
205 FluxUnits.__init__(self)
206 self.name = 'photlam'
207 self.Dispatch = {'flam': self.ToFlam,
208 'fnu': self.ToFnu,
209 'photlam': self.ToPhotlam,
210 'photnu': self.ToPhotnu,
211 'jy': self.ToJy,
212 'mjy': self.TomJy,
213 'mujy': self.TomuJy,
214 'microjy': self.TomuJy,
215 'ujy': self.TomuJy,
216 'njy': self.TonJy,
217 'nanojy': self.TonJy,
218 'abmag': self.ToABMag,
219 'stmag': self.ToSTMag,
220 'obmag': self.ToOBMag,
221 'vegamag': self.ToVegaMag,
222 'counts': self.ToCounts,
223 'counts': self.ToCounts}
224
225 self.nativewave = Angstrom
226
227
234
235
236 - def ToFlam(self, wave, flux):
238
239 - def ToFnu(self, wave, flux):
241
243 if hasattr(flux,'copy'):
244 return flux.copy()
245 else:
246 return flux
247
250
251 - def ToJy(self, wave, flux):
253
254 - def TomJy(self, wave, flux):
256
257 - def TomuJy(self, wave, flux):
259
260 - def TonJy(self, wave, flux):
262
266
270
275
281
284
285
286
287
288
289 -class Hz(WaveUnits):
296
304
306 """ Encapsulates some easy unit-conversion machinery. Angstrom
307 is not subclassed from here because it needs to be especially smart in
308 other ways. (Although multiple inheritence might be possible.)"""
310 return wave*self.factor
311
312 -class Nm(_MetricWavelength):
317
318 -class Micron(_MetricWavelength):
323
324 -class Mm(_MetricWavelength):
329
330 -class Cm(_MetricWavelength):
335
336 -class Meter(_MetricWavelength):
341
342
343
344
345
346 -class Flam(FluxUnits):
347 ''' flam = erg cm^-2 s^-1 Ang^-1'''
348
354
357
358
366
367
369 ''' photnu = photon cm^-2 s^-1 Hz^-1'''
374
377
378
386
387
388 -class Fnu(FluxUnits):
389 ''' fnu = erg cm^-2 s^-1 Hz^-1'''
394
397
398
406
407 -class Jy(FluxUnits):
408 ''' jy = 10^-23 erg cm^-2 s^-1 Hz^-1'''
413
416
417
425
426 -class mJy(FluxUnits):
427 ''' mjy = 10^-26 erg cm^-2 s^-1 Hz^-1'''
432
435
436
444
445 -class muJy(FluxUnits):
446 ''' mujy = 10^-29 erg cm^-2 s^-1 Hz^-1'''
451
454
460
461 -class nJy(FluxUnits):
462 ''' njy = 10^-32 erg cm^-2 s^-1 Hz^-1'''
467
470
476
477 -class ABMag(LogFluxUnits):
495
496 -class STMag(LogFluxUnits):
514
515 -class OBMag(LogFluxUnits):
522
526
528
529 total = band.throughput.sum()
530 return 2.5*math.log10(total)
531
537
541
543 sp=band*self.vegaspec
544 total=sp.integrate()
545 return 2.5*math.log10(total)
546
552
555
556
558
559 total = band.throughput.sum()
560 return 1.0/total
561
562
563
564
565
566 -def factory(uname, *args, **kwargs):
567 unitsClasses = {'flam' : Flam,
568 'fnu' : Fnu,
569 'photlam' : Photlam,
570 'photnu' : Photnu,
571 'jy' : Jy,
572 'mjy' : mJy,
573 'mujy' : muJy,
574 'microjy' : muJy,
575 'ujy' : muJy,
576 'njy' : nJy,
577 'nanojy' : nJy,
578 'abmag' : ABMag,
579 'stmag' : STMag,
580 'obmag' : OBMag,
581 'vegamag' : VegaMag,
582 'counts' : Counts,
583 'count' : Counts,
584 'angstrom' : Angstrom,
585 'angstroms' : Angstrom,
586 'nm' : Nm,
587 'micron' : Micron,
588 'microns' : Micron,
589 'um' : Micron,
590 'inversemicron': InverseMicron,
591 'inversemicrons': InverseMicron,
592 '1/um' : InverseMicron,
593 'mm' : Mm,
594 'cm' : Cm,
595 'm' : Meter,
596 'meter' : Meter,
597 'hz' : Hz}
598
599 key=uname.lower()
600 ans= unitsClasses[key]()
601 return ans
602
604 """ Compute delta wavelngth for an array of wavelengths.
605 If we had a WaveTable class, this function would be a method
606 on that class: possible refactoring candidate. """
607
608 last = wave.shape[0]-1
609
610 hold1 = N.empty(shape=wave.shape, dtype=N.float64)
611 hold2 = N.empty(shape=wave.shape, dtype=N.float64)
612
613 hold1[1::] = wave[0:last]
614 hold2[0:last] = wave[1::]
615
616 delta = (hold2 - hold1) / 2.0
617
618 delta[0] = wave[1] - wave[0]
619 delta[last] = wave[last] - wave[last-1]
620
621 return delta
622