"""
============
UCHII Fitter
============
Fit a free-free spectrum to an SED.
.. moduleauthor:: Adam Ginsburg <adam.g.ginsburg@gmail.com>
"""
import pylab
from pylab import *
try:
from scipy import optimize
except ImportError:
print "scipy not installed correctly: UCHIIfitter may fail"
from mpfit import mpfit
import numpy
kb = 1.38e-16
c=3e10
mu = 1.4
mh = 1.67e-24
msun = 1.9889e33
pc = 3.08568e18 # cm
au = 1.496e13 # cm
msun = 1.99e33 # g
unitfactor={'mJy':1e-26,'Jy':1e-23,'cgs':1.0}
freqfactor={'GHz':1e9,'Hz':1.0}
[docs]def tnu(Te,nu,EM):
"""
Te - excitation temperature
nu - frequency in GHz
EM - Emission Measure
Calculates optical depth as a function of temperature, frequency, and
emission measure from Rohlfs and Wilson 2000's eqns 9.33 and 9.34.
"""
# nu0 = .3045 * Te**-.643 * EM**.476
nu0 = Te**1.5 / 1000
answer_highnu = (nu > nu0) * 3.014e-2 * Te**-1.5 * nu**-2 * EM
gff_lownu = ( log(4.955e-2 * nu**-1) + 1.5 * log(Te) ) # <gff> Gaunt factor for free-free
answer_lownu = (nu < nu0) * 3.014e-2 * Te**-1.5 * nu**-2 * EM * gff_lownu
tau = answer_lownu+answer_highnu
return tau
[docs]def Inu(nu,tau,Te,I0=0):
"""
Calculates flux for a given optical depth, frequency, and temperature
assuming Rayleigh-Jeans
nu - frequency in Hz
tau - optical depth
Te - excitation temperature (K)
"""
if I0==0 and isinstance(nu,numpy.ndarray):
whtau1 = argmin(abs(tau-1))
nutau1 = nu[whtau1]
taufactor = 1
else:
nutau1 = nu
taufactor = tau
""" assumes I0 is set"""
I0 = 2 * kb * Te * nutau1**2 / c**2 * taufactor
thin = (tau < 1) * exp(1-tau) * I0
thick = 2 * kb * Te * (nu * (tau > 1))**2 / c**2
return thin+thick
[docs]def inufit(nu,em,normfac,Te=8500,unit='mJy',frequnit='GHz'):
"""
Computes the expected intensity as a function of frequency
for a given emission measure and normalization factor
nu - array of frequencies (array)
em - emission measure (float)
normfac - normalization factor (float)
- 1/solid angle of source. 1000 AU at 1 kpc = 206265.
Units: mJy
"""
_nu = nu*freqfactor[frequnit]
I0 = 2 * kb * Te * _nu[0]**2 / c**2
model_intensity = Inu(_nu,tnu(Te,_nu/1e9,em),Te,I0=I0) # tnu takes GHz
model_norm = normfac * model_intensity / unitfactor[unit]
return model_norm
[docs]def mpfitfun(freq,flux,err=None):
""" wrapper around inufit to be passed into mpfit """
if err == None:
def f(p,fjac=None): return [0,(flux-inufit(freq,*p))]
else:
def f(p,fjac=None): return [0,(flux-inufit(freq,*p))/err]
return f
[docs]def emtau(freq,flux,err=None,EMguess=1e7,Te=8500,normfac=5e-6,quiet=1):
"""
Returns emission measure & optical depth given
radio continuum data points at frequency freq with flux
density flux.
return bestEM,nu(tau=1),chi^2
"""
mp = mpfit(mpfitfun(freq,flux,err),xall=[EMguess,normfac],quiet=quiet)
mpp = mp.params
mpperr = mp.perror
chi2 = mp.fnorm
bestEM = mpp[0]
normfac = mpp[1]
nu_tau = ( Te**1.35 / bestEM / 8.235e-2 )**(-1/2.1)
return bestEM,nu_tau,normfac,chi2
[docs]class HIIregion:
"""
An HII region has properties frequency, flux, and error, which must be
numpy ndarrays of the same length
"""
def __init__(self,nu,flux,fluxerr,fluxunit='mJy',frequnit='GHz',beamsize_as2=0.25,dist_kpc=1.0,
resolved=False,Te=8500,**kwargs):
order = argsort(asarray(nu))
self.nu = asarray(nu)[order]
self.flux = asarray(flux)[order]
self.fluxerr = asarray(fluxerr)[order]
self.frequnit = frequnit
self.fluxunit = fluxunit
self.beamsize_as2 = beamsize_as2
self.dist_kpc = dist_kpc
self.resolved = resolved
self.Te = Te
self.em,self.nutau,self.normfac,self.chi2 = emtau(self.nu,self.flux,self.fluxerr,Te=self.Te,**kwargs)
[docs] def refit(self,**kwargs):
""" refit, presumably using different inputs to emtau """
self.em,self.nutau,self.normfac,self.chi2 = emtau(self.nu,self.flux,self.fluxerr,Te=self.Te,**kwargs)
def loglogplot(self,numin=1.0,numax=10.0,plottitle='',do_annotations=True,**kwargs):
x = linspace(numin,numax,500)
y = inufit(x,self.em,self.normfac)
loglog(x,y)
xlabel('Frequency (GHz)')
ylabel('Flux Density (mJy)')
title(plottitle)
errorbar(self.nu,self.flux,yerr=self.fluxerr,fmt=',',**kwargs)
self.physprops()
if do_annotations:
annotate("size (as): %0.2g" % (self.srcsize/au), [.8, .3],textcoords='axes fraction',xycoords='axes fraction')
annotate("size (au): %0.2g" % (self.srcsize/au), [.8, .3],textcoords='axes fraction',xycoords='axes fraction')
annotate("mass (msun): %0.2g" % self.mass, [.8, .25],textcoords='axes fraction',xycoords='axes fraction')
annotate("EM: %0.2g" % self.em, [.8, .2],textcoords='axes fraction',xycoords='axes fraction')
annotate("Nu(Tau=1): %0.2g" % self.nutau, [.8, .15],textcoords='axes fraction',xycoords='axes fraction')
annotate("N(lyc): %0.2g" % self.Nlyc, [.8,.1],textcoords='axes fraction',xycoords='axes fraction')
annotate("dens: %0.2g" % self.dens, [.8,.05],textcoords='axes fraction',xycoords='axes fraction')
[docs] def physprops(self):
"""
Get the source size (au), density (cm^-3),
mass (msun), and Nlyc of the UCHII
Also return EM and nutau
ERROR IN CURRENT VERSION
"""
if self.resolved:
self.srcsize = self.beamsize_as2 * (self.dist_kpc*1000.0*au)**2
else:
self.srcsize = sqrt(self.flux[0]*unitfactor[self.fluxunit]/(2*kb*self.Te) * \
(c/(self.nu[0]*freqfactor[self.frequnit]))**2 * (self.dist_kpc*1e3*pc)**2 / pi)
self.dens = sqrt(self.em/(self.srcsize/pc))
self.mass = self.dens * 4.0/3.0 * pi * self.srcsize**3 * mu * mh / msun
U = self.dens**(2/3.) * self.srcsize/pc
self.Nlyc = 8.04e46*self.Te**-.85 * U**3
return self.srcsize/au,self.dens,self.mass,self.Nlyc,self.em,self.nutau
# Cara test data:
# nu = array([1.4,5,8.33]); flux=array([4.7,9.2,9.1]); err=array([.52,.24,.07])
# em,nutau,normfac,chi2 = UCHIIfitter.emtau(nu,flux,err)