casa  $Rev:20696$
 All Classes Namespaces Files Functions Variables
solar_system_setjy.py
Go to the documentation of this file.
00001 #!/usr/bin/python -u
00002 #
00003 # bryan butler
00004 # nrao
00005 # spring 2012
00006 #
00007 # python functions to return expected flux density from solar system
00008 # bodies.  the flux density depends on the geometry (distance, size of
00009 # body, subearth latitude), and on the model brightness temperature.
00010 # uncertainties on the flux density can also be returned, but are all
00011 # set to 0.0 for now, because i don't have uncertainties on the model
00012 # brightness temperatures.
00013 #
00014 # the model brightness temperatures for the various bodies are taken
00015 # from a combination of modern models and historical observations.  see
00016 # the written description for a more complete description of the model
00017 # for each body.
00018 #
00019 # for all of the bodies but Mars, the model is contained in a text file
00020 # that has tabulated brightness temperature as a function of frequency.
00021 # for Mars it is also a function of time.  eventually, the full-up
00022 # model calculations should be in the code (for those bodies that have
00023 # proper models) but for now, just live with the tabulated versions.
00024 #
00025 # version 1.1
00026 # last edited: 2012Oct03
00027 #
00028 
00029 from numpy import searchsorted
00030 from scipy import array
00031 from scipy.interpolate import interp1d
00032 from math import exp, pi, cos, sin, isnan, sqrt
00033 #from os import environ, listdir
00034 import os
00035 from taskinit import gentools
00036 (tb,me)=gentools(['tb','me'])
00037 
00038 def solar_system_fd (source_name, MJDs, frequencies, observatory, casalog=None):
00039     '''
00040     find flux density for solar system bodies:
00041         Venus - Butler et al. 2001
00042         Mars - Butler et al. 2012
00043         Jupiter - Orton et al. 2012
00044         Uranus - Orton & Hofstadter 2012 (modified ESA4)
00045         Neptune - Orton & Hofstadter 2012 (modified ESA3)
00046         Io - Butler et al. 2012
00047         Europa - Butler et al. 2012
00048         Ganymede - Butler et al. 2012
00049         Titan - Gurwell et al. 2012
00050         Callisto - Butler et al. 2012
00051         Ceres - Keihm et al. 2012
00052         Juno - ?
00053         Pallas - Keihm et al. 2012
00054         Vesta - Keihm et al. 2012
00055         Hygeia - Keihm et al. 2012
00056 
00057     inputs:
00058         source_name = source name string.  example: "Venus"
00059         MJDs = list of MJD times (day + fraction).  example:
00060                [ 56018.232, 56018.273 ]
00061                must be sorted in ascending order.
00062         frequencies = list of [start, stop] frequencies for
00063                       which to calculate the integrated model.
00064                       example:
00065                       [ [ 224.234567e9, 224.236567e9 ],
00066                         [ 224.236567e9, 224.238567e9 ] ]
00067         observatory = observatory name string.  example: "ALMA"
00068 
00069     returned is a list, first element is the return status:
00070         0 -> success
00071         1 -> Error: unsupported body
00072         2 -> Error: unsupported frequency or time for body
00073         3 -> Error: Tb model file not found
00074         4 -> Error: ephemeris table not found, or time out of range
00075              (note - the case where the MJD times span two ephemeris
00076               files is not supported)
00077         5 -> Error: unknown observatory
00078     second element is a list of flux densities, one per time and
00079         frequency range, frequency changes fastest.
00080     third element is list of uncertainties (if known; 0 if unknown),
00081         one per time and frequency range, frequency changes fastest.
00082     fourth element is a list of major axis, minor axis, and PAs in
00083         asec and deg, one per MJD time.
00084     fifth element is a list of CASA directions, one per MJD time.
00085 
00086     bjb
00087     nrao
00088     spring/summer/fall 2012
00089     '''
00090 
00091     RAD2ASEC = 2.0626480624710e5
00092     AU = 1.4959787066e11
00093     SUPPORTED_BODIES = [ 'Venus', 'Mars', 'Jupiter', 'Uranus', 'Neptune',
00094                          'Io', 'Europa', 'Ganymede', 'Callisto', 'Titan',
00095                          'Ceres', 'Juno', 'Pallas', 'Vesta', 'Hygeia' ]
00096 
00097     capitalized_source_name = source_name.capitalize()
00098     statuses = []
00099     fds = []
00100     dfds = []
00101     Rhats = []
00102     directions = []
00103 
00104 #
00105 # check that body is supported
00106 #
00107     if (not capitalized_source_name in SUPPORTED_BODIES):
00108         for MJD in MJDs:
00109             estatuses = []
00110             efds = []
00111             edfds = []
00112             for frequency in frequencies:
00113                 estatuses.append(1)
00114                 efds.append(0)
00115                 edfds.append(0)
00116             statuses.append(estatuses)
00117             fds.append(efds)
00118             dfds.append(edfds)
00119             Rhats.append([0.0, 0.0, 0.0])
00120             directions.append(me.direction('J2000',0.0,0.0))
00121         return [ statuses, fds, dfds, Rhats, directions ]
00122 
00123 #
00124 # check that observatory is known
00125 #
00126     if not observatory in me.obslist():
00127         for MJD in MJDs:
00128             estatuses = []
00129             efds = []
00130             edfds = []
00131             for frequency in frequencies:
00132                 estatuses.append(5)
00133                 efds.append(0)
00134                 edfds.append(0)
00135             statuses.append(estatuses)
00136             fds.append(efds)
00137             dfds.append(edfds)
00138             Rhats.append([0.0, 0.0, 0.0])
00139             directions.append(me.direction('J2000',0.0,0.0))
00140         return [ statuses, fds, dfds, Rhats, directions ]
00141 
00142 #
00143 # before calculating the models be sure that we have the ephemeris 
00144 # information.  otherwise don't waste our time calculating the model.
00145 # only really important for mars, but do it for them all.
00146 #
00147     ephemeris_path = os.environ['CASAPATH'].split()[0]+'/data/ephemerides/JPL-Horizons/'
00148 #
00149 # for testing only...
00150 #
00151 #    ephemeris_path = '/home/rishi/ttsutsum/casatest/fluxCal/ephem/'
00152     file_list = os.listdir(ephemeris_path)
00153     files = []
00154     for efile in file_list:
00155         if (efile.split('_')[0] == capitalized_source_name and 'J2000' in efile):
00156             files.append(efile)
00157     file_OK = 0
00158 #
00159 # ephemeris tables have the following keywords:
00160 # GeoDist, GeoLat, GeoLong, MJD0, NAME, VS_CREATE, VS_DATE, VS_TYPE,
00161 # VS_VERSION, dMJD, earliest, latest, meanrad, obsloc, radii, rot_per
00162 # and columns:
00163 # MJD, RA, DEC, Rho (geodist), RadVel, NP_ang, NP_dist, DiskLong (Ob-long),
00164 # DiskLat(Ob-lat), Sl_lon, Sl_lat, r (heliodist), rdot, phang
00165 # Note by TT:
00166 # The column names, Obs_lon and Obs_lat have been changed to DiskLong and
00167 # DiskLat respectively to be consistent with what column names assumed for
00168 # ephemeris tables by casacore's MeasComet class.
00169 
00170     for efile in files:
00171         ephemeris_file = ephemeris_path + efile
00172         tb.open(ephemeris_file)
00173         table_source_name = tb.getkeyword('NAME').capitalize()
00174         if (table_source_name != capitalized_source_name):
00175             continue
00176         first_time = tb.getkeyword('earliest')['m0']['value']
00177         last_time = tb.getkeyword('latest')['m0']['value']
00178         if (first_time < MJDs[0] and last_time > MJDs[-1]):
00179             file_OK = 1
00180             break
00181         tb.close()
00182 #
00183 # if we didn't find an ephemeris file, set the statuses and return.
00184 #
00185     if (file_OK == 0):
00186         for MJD in MJDs:
00187             estatuses = []
00188             efds = []
00189             edfds = []
00190             for frequency in frequencies:
00191                 estatuses.append(4)
00192                 efds.append(0)
00193                 edfds.append(0)
00194             statuses.append(estatuses)
00195             fds.append(efds)
00196             dfds.append(edfds)
00197             Rhats.append([0.0, 0.0, 0.0])
00198             directions.append(me.direction('J2000',0.0,0.0))
00199         return [ statuses, fds, dfds, Rhats, directions ]
00200 
00201     Req = 1000.0 * (tb.getkeyword('radii')['value'][0] + tb.getkeyword('radii')['value'][1]) / 2
00202     Rp = 1000.0 * tb.getkeyword('radii')['value'][2]
00203     times = tb.getcol('MJD').tolist()
00204     RAs = tb.getcol('RA').tolist()
00205     DECs = tb.getcol('DEC').tolist()
00206     distances = tb.getcol('Rho').tolist()
00207     RadVels = tb.getcol('RadVel').tolist()
00208     column_names = tb.colnames()
00209     if ('DiskLat' in column_names):
00210         selats = tb.getcol('DiskLat').tolist()
00211         has_selats = 1
00212     else:
00213         has_selats = 0
00214         selat = 0.0
00215     if ('NP_ang' in column_names):
00216         NPangs = tb.getcol('NP_ang').tolist()
00217         has_NPangs= 1
00218     else:
00219         has_NPangs = 0
00220         NPang = 0.0
00221     tb.close()
00222     MJD_shifted_frequencies = []
00223     DDs = []
00224     Rmeans = []
00225 
00226     for ii in range(len(MJDs)):
00227         MJD = MJDs[ii]
00228         DDs.append(1.4959787066e11 * interpolate_list (times, distances, MJD)[1])
00229         if (has_selats):
00230             selat = interpolate_list (times, selats, MJD)[1]
00231             if (selat == -999.0):
00232                 selat = 0.0
00233 # apparent polar radius
00234         Rpap = sqrt (Req*Req * sin(selat)**2.0 +
00235                      Rp*Rp * cos(selat)**2.0)
00236         Rmean = sqrt (Rpap * Req)
00237         Rmeans.append(Rmean)
00238 #
00239 # need to check that the convention for NP angle is the
00240 # same as what is needed in the component list.
00241 #
00242         if (has_NPangs):
00243             NPang = interpolate_list (times, NPangs, MJD)[1]
00244             if (NPang == -999.0):
00245                 NPang = 0.0
00246         Rhats.append([2*RAD2ASEC*Req/DDs[-1], 2*RAD2ASEC*Rpap/DDs[-1], NPang])
00247         RA = interpolate_list (times, RAs, MJD)[1]
00248         RAstr=str(RA)+'deg'
00249         DEC = interpolate_list (times, DECs, MJD)[1]
00250         DECstr=str(DEC)+'deg'
00251         directions.append(me.direction('J2000',RAstr,DECstr))
00252 #
00253 # now get the doppler shift
00254 #
00255 # NOTE: this is not exactly right, because it doesn't include the
00256 # distance to the body in any of these calls.  the distance will matter
00257 # because it will change the line-of-sight vector from the observatory
00258 # to the body, which will change the doppler shift.  jeff thinks using
00259 # the comet measures calls might fix this, but i haven't been able to
00260 # figure them out yet.  i thought i had it figured out, with the
00261 # call to me.framecomet(), but that doesn't give the right answer,
00262 # unfortunately.  i spot-checked the error introduced because of this,
00263 # and it looks to be of order 1 m/s for these bodies, so i'm not going
00264 # to worry about it.
00265 #
00266         me.doframe(me.observatory(observatory))
00267         me.doframe(me.epoch('utc',str(MJD)+'d'))
00268         me.doframe(directions[-1])
00269 #
00270 # instead of the call to me.doframe() in the line above, i thought the
00271 # following call to me.framecomet() would be right, but it doesn't give
00272 # the right answer :/.
00273 #       me.framecomet(ephemeris_file)
00274 #
00275 # RadVel is currently in AU/day.  we want it in km/s.
00276 #
00277         RadVel = interpolate_list (times, RadVels, MJD)[1] * AU / 86400000.0
00278         rv = me.radialvelocity('geo',str(RadVel)+'km/s')
00279         shifted_frequencies = []
00280         for frequency in frequencies:
00281 #
00282 # the measure for radial velocity could be obtained via:
00283 # me.measure(rv,'topo')['m0']['value']
00284 # but what we really want is a frequency shift.  i could do it by
00285 # hand, but i'd rather do it with casa toolkit calls.  unfortunately,
00286 # it's a bit convoluted in casa...
00287 #
00288             newfreq0 = me.tofrequency('topo',me.todoppler('optical',me.measure(rv,'topo')),me.frequency('topo',str(frequency[0])+'Hz'))['m0']['value']
00289             newfreq1 = me.tofrequency('topo',me.todoppler('optical',me.measure(rv,'topo')),me.frequency('topo',str(frequency[1])+'Hz'))['m0']['value']
00290 #
00291 # should check units to be sure frequencies are in Hz
00292 #
00293 # now, we want to calculate the model shifted in the opposite direction
00294 # as the doppler shift, so take that into account.
00295 #
00296             delta_frequency0 = frequency[0] - newfreq0
00297             newfreq0 = frequency[0] + delta_frequency0
00298             delta_frequency1 = frequency[1] - newfreq1
00299             newfreq1 = frequency[1] + delta_frequency1
00300             shifted_frequencies.append([newfreq0,newfreq1])
00301             average_delta_frequency = (delta_frequency0 + delta_frequency1)/2
00302 #
00303 # should we print this to the log?
00304 #
00305 #           print 'MJD, geo & topo velocities (km/s), and shift (MHz) = %7.1f  %5.1f  %5.1f  %6.3f' % \
00306 #                 (MJD, RadVel, me.measure(rv,'topo')['m0']['value']/1000, average_delta_frequency/1.0e6)
00307             msg='MJD, geo & topo velocities (km/s), and shift (MHz) = %7.1f  %5.1f  %5.1f  %6.3f' % \
00308                  (MJD, RadVel, me.measure(rv,'topo')['m0']['value']/1000, average_delta_frequency/1.0e6)
00309             casalog.post(msg, 'INFO2')
00310         MJD_shifted_frequencies.append(shifted_frequencies)
00311 #       me.done()
00312 
00313     for ii in range(len(MJDs)):
00314         shifted_frequencies = MJD_shifted_frequencies[ii]
00315         if (capitalized_source_name == 'Mars'):
00316             [tstatuses,brightnesses,dbrightnesses] = brightness_Mars_int ([MJDs[ii]], shifted_frequencies)
00317             # modified by TT: take out an extra dimension (for times), to match the rest of the operation 
00318             tstatuses=tstatuses[0] 
00319             brightnesses=brightnesses[0]
00320             dbrightnesses=dbrightnesses[0]
00321         else:
00322             tstatuses = []
00323             brightnesses = []
00324             dbrightnesses = []
00325             for shifted_frequency in shifted_frequencies:
00326                 [status,brightness,dbrightness] = brightness_planet_int (capitalized_source_name, shifted_frequency)
00327                 tstatuses.append(status)
00328                 brightnesses.append(brightness)
00329                 dbrightnesses.append(dbrightness)
00330         tfds = []
00331         tdfds = []
00332         for jj in range (len(tstatuses)):
00333              if not tstatuses[jj]:
00334                 flux_density = brightnesses[jj] * 1.0e26 * pi * Rmeans[ii]*Rmeans[ii]/ (DDs[ii]*DDs[ii])
00335 #
00336 # mean apparent planet radius, in arcseconds (used if we ever
00337 # calculate the primary beam reduction)
00338 #
00339                 psize = (Rmeans[ii] / DDs[ii]) * RAD2ASEC
00340 #
00341 # primary beam reduction factor (should call a function, but
00342 # just set to 1.0 for now...
00343 #
00344                 pbfactor = 1.0
00345                 flux_density *= pbfactor
00346                 tfds.append(flux_density)
00347                 tdfds.append(0.0)
00348              else:
00349                 tfds.append(0.0)
00350                 tdfds.append(0.0)
00351         statuses.append(tstatuses)
00352         fds.append(tfds)
00353         dfds.append(tdfds)
00354 
00355     return [ statuses, fds, dfds, Rhats, directions ]
00356 
00357 
00358 def brightness_Mars_int (MJDs, frequencies):
00359     '''
00360     Planck brightness for Mars.  this one is different because
00361     the model is tabulated vs. frequency *and* time.
00362     inputs:
00363         MJDs = list of MJD times
00364         frequencies = list of [start, stop] frequencies for
00365                     which to calculate the integrated model.
00366                     example: [ [ 224.234567e9, 224.236567e9 ] ]
00367     '''
00368 
00369 #
00370 # constants.  these might be in CASA internally somewhere, but
00371 # i don't know where to pull them out, so oh well, define my own
00372 #
00373     HH = 6.6260755e-34
00374     KK = 1.380658e-23
00375     CC = 2.99792458e8
00376 
00377     statuses = []
00378     Tbs = []
00379     dTbs = []
00380     try:
00381         model_data_path = os.environ['CASAPATH'].split()[0]+'/data/alma/SolarSystemModels/'
00382         model_data_filename = model_data_path + 'Mars_Tb.dat'
00383         ff = open(model_data_filename)
00384     except:
00385         for MJD in MJDs:
00386             estatuses = []
00387             eTbs = []
00388             eTbds = []
00389             for frequency in frequencies:
00390                 estatuses.append(3)
00391                 eTbs.append(0)
00392                 edTbs.append(0)
00393             statuses.append(estatuses)
00394             Tbs.append(eTbs)
00395             dTbs.append(edTbs)
00396         return [ statuses, Tbs, dTbs ]
00397 
00398 # first line holds frequencies, like:
00399 # 30.0 80.0 115.0 150.0 200.0 230.0 260.0 300.0 330.0 360.0 425.0 650.0 800.0 950.0 1000.0
00400     line = ff.readline()[:-1]
00401     fields = line.split()
00402     freqs = []
00403     for field in fields:
00404         freqs.append(1.0e9*float(field))
00405 # model output lines look like:
00406 #2010 01 01 00  55197.00000 189.2 195.8 198.9 201.2 203.7 204.9 205.9 207.1 207.8 208.5 209.8 213.0 214.6 214.8 214.5 
00407     modelMJDs = []
00408     modelTbs = []
00409     for line in ff:
00410         fields = line[:-1].split()
00411         modelMJDs.append(float(fields[4]))
00412         lTbs = []
00413         for ii in range(len(freqs)):
00414             lTbs.append(float(fields[5+ii]))
00415         modelTbs.append(lTbs)
00416     ff.close()
00417     for MJD in MJDs:
00418         nind = nearest_index (modelMJDs, MJD)
00419         mTbs = []
00420         mfds = []
00421         for ii in range(len(freqs)):
00422             lMJD = []
00423             lTb = []
00424             for jj in range(nind-10, nind+10):
00425                 lMJD.append(modelMJDs[jj])
00426                 lTb.append(modelTbs[jj][ii])
00427             mTbs.append(interpolate_list(lMJD, lTb, MJD)[1])
00428 #
00429 # note: here, when we have the planck results, get a proper estimate of
00430 # the background temperature.
00431 #
00432 # note also that we want to do this here because the integral needs to
00433 # be done on the brightness, not on the brightness *temperature*.
00434 #
00435             Tbg = 2.725
00436             mfds.append((2.0 * HH * freqs[ii]**3.0 / CC**2.0) * \
00437                         ((1.0 / (exp(HH * freqs[ii] / (KK * mTbs[-1])) - 1.0)) - \
00438                          (1.0 / (exp(HH * freqs[ii] / (KK * Tbg)) - 1.0))))
00439         estatuses = []
00440         eTbs = []
00441         edTbs = []
00442         for frequency in frequencies:
00443             if (frequency[0] < freqs[0] or frequency[1] > freqs[-1]):
00444                 estatuses.append(2)
00445                 eTbs.append(0.0)
00446                 edTbs.append(0.0)
00447             else:
00448                 [estatus, eTb, edTb] = integrate_Tb (freqs, mfds, frequency)
00449 #
00450 # should we print out the Tb we found?  not sure.  i have a
00451 # vague recollection that crystal requested it, but i'm not
00452 # sure if it's really needed.  we'd have to back out the 
00453 # planck correction (along with the background), so it wouldn't
00454 # be trivial.
00455 #
00456                 estatuses.append(estatus)
00457                 eTbs.append(eTb)
00458                 edTbs.append(edTb)
00459         statuses.append(estatuses)
00460         Tbs.append(eTbs)
00461         dTbs.append(edTbs)
00462     return [statuses, Tbs, dTbs ]
00463 
00464 
00465 def brightness_planet_int (source_name, frequency):
00466     '''
00467     brightness temperature for supported planets.  integrates over
00468     a tabulated model.  inputs:
00469         source_name = source name string
00470         frequency = list of [start, stop] frequencies for
00471                     which to calculate the integrated model.
00472                     example: [ 224.234567e9, 224.236567e9 ]
00473     '''
00474 
00475 #
00476 # constants.  these might be in CASA internally somewhere, but
00477 # i don't know where to pull them out, so oh well, define my own
00478 #
00479     HH = 6.6260755e-34
00480     KK = 1.380658e-23
00481     CC = 2.99792458e8
00482 
00483     try:
00484         model_data_path = os.environ['CASAPATH'].split()[0]+'/data/alma/SolarSystemModels/'
00485         model_data_filename = model_data_path + source_name + '_Tb.dat'
00486         ff = open(model_data_filename)
00487     except:
00488         return [ 3, 0.0, 0.0 ]
00489     fds = []
00490     Tbs = []
00491     freqs = []
00492     for line in ff:
00493         [freq,Tb] = line[:-1].split()
00494         Tbs.append(float(Tb))
00495         freqs.append(1.0e9*float(freq))
00496 #
00497 # note: here, when we have the planck results, get a proper
00498 # estimate of the background temperature.
00499 #
00500 # note also that we want to do this here because the integral
00501 # needs to be done on the brightness, not on the brightness
00502 # *temperature*.
00503 #
00504         Tbg = 2.725
00505         fds.append((2.0 * HH * freqs[-1]**3.0 / CC**2.0) * \
00506                     ((1.0 / (exp(HH * freqs[-1] / (KK * Tbs[-1])) - 1.0)) - \
00507                      (1.0 / (exp(HH * freqs[-1] / (KK * Tbg)) - 1.0))))
00508     ff.close()
00509     if (frequency[0] < freqs[0] or frequency[1] > freqs[-1]):
00510         return [ 2, 0.0, 0.0 ]
00511     else:
00512 #
00513 # should we print out the Tb we found?  not sure.  i have a
00514 # vague recollection that crystal requested it, but i'm not
00515 # sure if it's really needed.  we'd have to back out the 
00516 # planck correction (along with the background), so it wouldn't
00517 # be trivial.  and here, we'd have to return a variable and
00518 # work on that.
00519 #
00520         return integrate_Tb (freqs, fds, frequency)
00521 
00522 
00523 def nearest_index (input_list, value):
00524     """
00525     find the index of the list input_list that is closest to value
00526     """
00527 
00528     ind = searchsorted(input_list, value)
00529     ind = min(len(input_list)-1, ind)
00530     ind = max(1, ind)
00531     if value < (input_list[ind-1] + input_list[ind]) / 2.0:
00532         ind = ind - 1
00533     return ind
00534 
00535 
00536 def interpolate_list (freqs, Tbs, frequency):
00537     ind = nearest_index (freqs, frequency)
00538     low = max(0,ind-5)
00539     if (low == 0):
00540         high = 11
00541     else:
00542         high = min(len(freqs),ind+6)
00543         if (high == len(freqs)):
00544             low = high - 11
00545 #
00546 # i wanted to put in a check for tabulated values that change
00547 # derivative, since that confuses the interpolator.  benign cases are
00548 # fine, like radial velocity, but for the model Tbs, where there are
00549 # sharp spectral lines, then the fitting won't be sensible when you're
00550 # right at the center of the line, because the inflection is so severe.
00551 # i thought if i just only took values that had the same derivative as
00552 # the location where the desired value is that would work, but it
00553 # doesn't :/.  i'm either not doing it right or there's something
00554 # deeper.
00555 #
00556 #   if freqs[ind] < frequency:
00557 #       deriv = Tbs[ind+1] - Tbs[ind]
00558 #   else:
00559 #       deriv = Tbs[ind] - Tbs[ind-1]
00560 #   tTbs = []
00561 #   tfreqs = []
00562 #   for ii in range(low,high):
00563 #       nderiv = Tbs[ii+1] - Tbs[ii]
00564 #       if (nderiv >= 0.0 and deriv >= 0.0) or (nderiv < 0.0 and deriv < 0.0):
00565 #           tTbs.append(Tbs[ii])
00566 #           tfreqs.append(freqs[ii])
00567 #   aTbs = array(tTbs)
00568 #   afreqs = array(tfreqs)
00569     aTbs = array(Tbs[low:high])
00570     afreqs = array(freqs[low:high])
00571 #
00572 # cubic interpolation blows up near line centers (see above comment),
00573 # so check that it doesn't fail completely (put it in a try/catch), and
00574 # also that it's not a NaN and within the range of the tabulated values
00575 #
00576     range = max(aTbs) - min(aTbs)
00577     try:
00578         func = interp1d (afreqs, aTbs, kind='cubic')
00579         if isnan(func(frequency)) or func(frequency) < min(aTbs)-range/2 or func(frequency) > max(aTbs)+range/2:            func = interp1d (afreqs, aTbs, kind='linear')
00580     except:
00581         func = interp1d (afreqs, aTbs, kind='linear')
00582 #
00583 # if it still failed, even with the linear interpolation, just take the
00584 # nearest tabulated point.
00585 #
00586     if isnan(func(frequency)) or func(frequency) < min(aTbs)-range/2 or func(frequency) > max(aTbs)+range/2:
00587         brightness = Tbs[ind]
00588     else:
00589         brightness = float(func(frequency))
00590     return [ 0, brightness, 0.0 ]
00591 
00592 
00593 def integrate_Tb (freqs, Tbs, frequency):
00594     [status,low_Tb,low_dTb] = interpolate_list (freqs, Tbs, frequency[0])
00595     low_index = nearest_index (freqs, frequency[0])
00596     if (frequency[0] > freqs[low_index]):
00597         low_index = low_index + 1
00598 
00599     [status,hi_Tb,hi_dTb] = interpolate_list (freqs, Tbs, frequency[1])
00600     hi_index = nearest_index (freqs, frequency[1])
00601     if (frequency[1] < freqs[hi_index]):
00602         hi_index = hi_index - 1
00603 
00604     if (low_index > hi_index):
00605         Tb = (frequency[1] - frequency[0]) * (low_Tb + hi_Tb) / 2
00606     else:
00607         Tb = (freqs[low_index] - frequency[0]) * (low_Tb + Tbs[low_index]) / 2 + \
00608              (frequency[1] - freqs[hi_index]) * (hi_Tb + Tbs[hi_index]) / 2
00609         ii = low_index
00610         while (ii < hi_index):
00611            Tb += (freqs[ii+1] - freqs[ii]) * (Tbs[ii+1] + Tbs[ii]) / 2
00612            ii += 1
00613     Tb /= (frequency[1] - frequency[0])
00614     return [ 0, Tb, 0.0 ]
00615 
00616