def Qsca( self, E, a=1.0, cm=cmi.CmDrude() ): if np.size(a) != 1: print 'Error: Must specify only 1 value of a' return a_cm = a * c.micron2cm() # cm -- Can only be a single value lam = c.kev2lam() / E # cm -- Can be many values x = 2.0 * np.pi * a_cm / lam mm1 = cm.rp(E) - 1 + 1j * cm.ip(E) return 2.0 * np.power( x, 2 ) * np.power( np.abs(mm1), 2 )
def __init__( self ): self.cmtype = 'Silicate' D03vals = c.restore( os.path.join(CMROOT,'CM_D03.pysav')) # look up file lamvals = D03vals['Sil_lam'] revals = D03vals['Sil_re'] imvals = D03vals['Sil_im'] lamEvals = c.kev2lam() / c.micron2cm() / lamvals # keV self.rp = interp1d( lamEvals, revals ) self.ip = interp1d( lamEvals, imvals )
def __init__( self ): self.cmtype = 'Silicate' D03file = find_cmfile('CM_D03.pysav') D03vals = c.restore(D03file) # look up file lamvals = D03vals['Sil_lam'] revals = D03vals['Sil_re'] imvals = D03vals['Sil_im'] lamEvals = c.kev2lam() / c.micron2cm() / lamvals # keV self.rp = interp1d( lamEvals, revals ) self.ip = interp1d( lamEvals, imvals )
def Diff( self, theta, E=1.0, a=1.0, cm=cmi.CmDrude() ): cgeo = np.pi * np.power( a*c.micron2cm(), 2 ) if np.size(a) != 1: print 'Error: Must specify only 1 value of a' return if np.logical_and( np.size(E) > 1, np.size(E) != np.size(theta) ): print 'Error: E and theta must have same size if np.size(E) > 1' return dQ = self.getQs( a=a, E=E, cm=cm, getQ='diff', theta=theta ) return dQ * cgeo
def __init__( self, E=1.0, scatm=Scatmodel(), dist=dust.Dustspectrum() ): self.scatm = scatm self.E = E self.dist = dist if scatm.stype == 'RG': print 'Rayleigh-Gans cross-section not currently supported for Kappaext' self.kappa = None return cm = scatm.cmodel scat = scatm.smodel cgeo = np.pi * np.power( dist.a * c.micron2cm(), 2 ) qext = np.zeros( shape=( np.size(E),np.size(dist.a) ) ) qext_pe = np.zeros( shape=( np.size(E),np.size(dist.a) ) ) qext_pa = np.zeros( shape=( np.size(E),np.size(dist.a) ) ) # Test for graphite case if cm.cmtype == 'Graphite': cmGraphitePerp = cmi.CmGraphite(size=cm.size, orient='perp') cmGraphitePara = cmi.CmGraphite(size=cm.size, orient='para') if np.size(dist.a) > 1: for i in range( np.size(dist.a) ): qext_pe[:,i] = scat.Qext( E, a=dist.a[i], cm=cmGraphitePerp ) qext_pa[:,i] = scat.Qext( E, a=dist.a[i], cm=cmGraphitePara ) else: qext_pe = scat.Qext( E, a=dist.a, cm=cmGraphitePerp ) qext_pa = scat.Qext( E, a=dist.a, cm=cmGraphitePara ) qext = ( qext_pa + 2.0 * qext_pe ) / 3.0 else: if np.size(dist.a) > 1: for i in range( np.size(dist.a) ): qext[:,i] = scat.Qext( E, a=dist.a[i], cm=cm ) else: qext = scat.Qext( E, a=dist.a, cm=cm ) if np.size(dist.a) == 1: kappa = dist.nd * qext * cgeo / dist.md else: kappa = np.array([]) for j in range( np.size(E) ): kappa = np.append( kappa, \ c.intz( dist.a, dist.nd * qext[j,:] * cgeo ) / dist.md ) self.kappa = kappa
def Diff( self, theta, E=1.0, a=1.0, cm=cmi.CmDrude() ): # cm^2 ster^-1 if np.size(a) != 1: print 'Error: Must specify only 1 value of a' return if np.logical_and( np.size(E) > 1, # If more than 1 energy is specified np.size(E) != np.size(theta) ): # theta must be of the same size print 'Error: If specifying > 1 energy, must have same number of values for theta' return a_cm = a * c.micron2cm() # cm -- Can only be a single value lam = c.kev2lam() / E # cm x = 2.0 * np.pi * a_cm / lam mm1 = cm.rp(E) + 1j * cm.ip(E) - 1 thdep = 2./9. * np.exp( -np.power( theta/self.Char(a=a, E=E) , 2 ) / 2.0 ) dsig = 2.0 * np.power(a_cm,2) * np.power(x,4) * np.power( np.abs(mm1),2 ) return dsig * thdep
def __init__( self, E=1.0, scatm=Scatmodel(), dist=dust.Dustspectrum() ): self.scatm = scatm self.E = E self.dist = dist cm = scatm.cmodel scat = scatm.smodel cgeo = np.pi * np.power( dist.a * c.micron2cm(), 2 ) qsca = np.zeros( shape=( np.size(E),np.size(dist.a) ) ) qsca_pe = np.zeros( shape=( np.size(E),np.size(dist.a) ) ) qsca_pa = np.zeros( shape=( np.size(E),np.size(dist.a) ) ) # Test for graphite case if cm.cmtype == 'Graphite': cmGraphitePerp = cmi.CmGraphite(size=cm.size, orient='perp') cmGraphitePara = cmi.CmGraphite(size=cm.size, orient='para') if np.size(dist.a) > 1: for i in range( np.size(dist.a) ): qsca_pe[:,i] = scat.Qsca( E, a=dist.a[i], cm=cmGraphitePerp ) qsca_pa[:,i] = scat.Qsca( E, a=dist.a[i], cm=cmGraphitePara ) else: qsca_pe = scat.Qsca( E, a=dist.a, cm=cmGraphitePerp ) qsca_pa = scat.Qsca( E, a=dist.a, cm=cmGraphitePara ) qsca = ( qsca_pa + 2.0 * qsca_pe ) / 3.0 else: if np.size(dist.a) > 1: for i in range( np.size(dist.a) ): qsca[:,i] = scat.Qsca( E, a=dist.a[i], cm=cm ) else: qsca = scat.Qsca( E, a=dist.a, cm=cm ) if np.size(dist.a) == 1: kappa = dist.nd * qsca * cgeo / dist.md else: kappa = np.array([]) for j in range( np.size(E) ): kappa = np.append( kappa, \ c.intz( dist.a, dist.nd * qsca[j,:] * cgeo ) / dist.md ) self.kappa = kappa
def __init__( self, scatm=Scatmodel(), E=1.0, a=1.0 ): self.scatm = scatm self.E = E self.a = a cm = scatm.cmodel scat = scatm.smodel cgeo = np.pi * np.power( a*c.micron2cm(), 2 ) if cm.cmtype == 'Graphite': qsca_pe = scat.Qsca( a=a, E=E, cm=cmi.CmGraphite(size=cm.size, orient='perp') ) qsca_pa = scat.Qsca( a=a, E=E, cm=cmi.CmGraphite(size=cm.size, orient='para') ) self.qsca = ( qsca_pa + 2.0*qsca_pe ) / 3.0 else: self.qsca = scat.Qsca( a=a, E=E, cm=cm ) self.sigma = self.qsca * cgeo
def __init__( self, size='big', orient='perp' ): # size : string ('big' or 'small') # : 'big' gives results for 0.1 um sized graphite grains at 20 K [Draine (2003)] # : 'small' gives results for 0.01 um sized grains at 20 K # orient : string ('perp' or 'para') # : 'perp' gives results for E-field perpendicular to c-axis # : 'para' gives results for E-field parallel to c-axis # self.cmtype = 'Graphite' self.size = size self.orient = orient D03file = find_cmfile('CM_D03.pysav') # look up file D03vals = c.restore(D03file) # read in index values if size == 'big': if orient == 'perp': lamvals = D03vals['Cpe_010_lam'] revals = D03vals['Cpe_010_re'] imvals = D03vals['Cpe_010_im'] if orient == 'para': lamvals = D03vals['Cpa_010_lam'] revals = D03vals['Cpa_010_re'] imvals = D03vals['Cpa_010_im'] if size == 'small': if orient == 'perp': lamvals = D03vals['Cpe_001_lam'] revals = D03vals['Cpe_001_re'] imvals = D03vals['Cpe_001_im'] if orient == 'para': lamvals = D03vals['Cpa_001_lam'] revals = D03vals['Cpa_001_re'] imvals = D03vals['Cpa_001_im'] lamEvals = c.kev2lam() / c.micron2cm() / lamvals # keV self.rp = interp1d( lamEvals, revals ) self.ip = interp1d( lamEvals, imvals )
def __call__(self, with_mp=False): cm = self.scatm.cmodel scat = self.scatm.smodel cgeo = np.pi * np.power( self.dist.a * c.micron2cm(), 2 ) # Test for graphite case if cm.cmtype == 'Graphite': if np.size(self.dist.a) > 1: for i in range( np.size(self.dist.a) ): self.qsca_pe[:,i] = scat.Qsca( self.E, a=self.dist.a[i], cm=cmi.CmGraphite(size=cm.size, orient='perp') ) self.qsca_pa[:,i] = scat.Qsca( self.E, a=self.dist.a[i], cm=cmi.CmGraphite(size=cm.size, orient='para') ) else: self.qsca_pe = scat.Qsca( self.E, a=self.dist.a, cm=cmi.CmGraphite(size=cm.size, orient='perp') ) self.qsca_pa = scat.Qsca( self.E, a=self.dist.a, cm=cmi.CmGraphite(size=cm.size, orient='para') ) self.qsca = ( self.qsca_pa + 2.0 * self.qsca_pe ) / 3.0 else: if np.size(self.dist.a) > 1: if with_mp: pool = Pool(processes=2) self.qsca = np.array(pool.map(self._one_scatter,self.dist.a)).T else: for i in range( np.size(self.dist.a) ): self.qsca[:,i] = self._one_scatter(self.dist.a[i]) else: self.qsca = scat.Qsca( self.E, a=self.dist.a, cm=cm ) if np.size(self.dist.a) == 1: kappa = self.dist.nd * self.qsca * cgeo / self.dist.md else: kappa = np.array([]) for j in range( np.size(self.E) ): kappa = np.append( kappa, \ c.intz( self.dist.a, self.dist.nd * self.qsca[j,:] * cgeo ) / self.dist.md ) self.kappa = kappa
def __init__( self, scatm=Scatmodel(), E=1.0, a=1.0 ): self.scatm = scatm self.E = E self.a = a if scatm.stype == 'RG': print 'Rayleigh-Gans cross-section not currently supported for Kappaext' self.sigma = None return cm = scatm.cmodel scat = scatm.smodel cgeo = np.pi * np.power( a*c.micron2cm(), 2 ) if cm.cmtype == 'Graphite': qext_pe = scat.Qext( a=a, E=E, cm=cmi.CmGraphite(size=cm.size, orient='perp') ) qext_pa = scat.Qext( a=a, E=E, cm=cmi.CmGraphite(size=cm.size, orient='para') ) self.qext = ( qext_pa + 2.0*qext_pe ) / 3.0 else: self.qext = scat.Qext( a=a, E=E, cm=cm ) self.sigma = self.qext * cgeo
def getQs( self, a=1.0, E=1.0, cm=cmi.CmDrude(), getQ='sca', theta=None ): # Takes single a and E argument if np.size(a) > 1: print 'Error: Can only specify one value for a' return indl90 = np.array([]) # Empty arrays indicate that there are no theta values set indg90 = np.array([]) # Do not have to check if theta != None throughout calculation s1 = np.array([]) s2 = np.array([]) pi = np.array([]) pi0 = np.array([]) pi1 = np.array([]) tau = np.array([]) amu = np.array([]) if theta != None: if np.size(E) > 1 and np.size(E) != np.size(theta): print 'Error: If more than one E value specified, theta must have same size as E' return if np.size( theta ) == 1: theta = np.array( [theta] ) theta_rad = theta * c.arcs2rad() amu = np.abs( np.cos(theta_rad) ) indl90 = np.where( theta_rad < np.pi/2.0 ) indg90 = np.where( theta_rad >= np.pi/2.0 ) nang = np.size( theta ) s1 = np.zeros( nang, dtype='complex' ) s2 = np.zeros( nang, dtype='complex' ) pi = np.zeros( nang, dtype='complex' ) pi0 = np.zeros( nang, dtype='complex' ) pi1 = np.zeros( nang, dtype='complex' ) + 1.0 tau = np.zeros( nang, dtype='complex' ) refrel = cm.rp(E) + 1j*cm.ip(E) x = ( 2.0 * np.pi * a*c.micron2cm() ) / ( c.kev2lam()/E ) y = x * refrel ymod = np.abs(y) nx = np.size( x ) # *** Series expansion terminated after NSTOP terms # Logarithmic derivatives calculated from NMX on down xstop = x + 4.0 * np.power( x, 0.3333 ) + 2.0 test = np.append( xstop, ymod ) nmx = np.max( test ) + 15 nmx = np.int32(nmx) nstop = xstop # nmxx = 150000 # if (nmx > nmxx): # print 'error: nmx > nmxx=', nmxx, ' for |m|x=', ymod # *** Logarithmic derivative D(J) calculated by downward recurrence # beginning with initial value (0.,0.) at J=NMX d = self.create_d(nx,nmx,y) # *** Riccati-Bessel functions with real argument X # calculated by upward recurrence psi0 = np.cos(x) psi1 = np.sin(x) chi0 = -np.sin(x) chi1 = np.cos(x) xi1 = psi1 - 1j * chi1 qsca = 0.0 # scattering efficiency gsca = 0.0 # <cos(theta)> s1_ext = 0 s2_ext = 0 s1_back = 0 s2_back = 0 pi_ext = 0 pi0_ext = 0 pi1_ext = 1 tau_ext = 0 p = -1.0 for n in np.arange( np.max(nstop) )+1: # for n=1, nstop do begin en = n fn = (2.0*en+1.0)/ (en* (en+1.0)) # for given N, PSI = psi_n CHI = chi_n # PSI1 = psi_{n-1} CHI1 = chi_{n-1} # PSI0 = psi_{n-2} CHI0 = chi_{n-2} # Calculate psi_n and chi_n # *** Compute AN and BN: #*** Store previous values of AN and BN for use # in computation of g=<cos(theta)> if n > 1: an1 = an bn1 = bn if nx > 1: ig = nstop >= n psi = np.zeros( nx ) chi = np.zeros( nx ) psi[ig] = (2.0*en-1.0) * psi1[ig]/x[ig] - psi0[ig] chi[ig] = (2.0*en-1.0) * chi1[ig]/x[ig] - chi0[ig] xi = psi - 1j * chi an = np.zeros( nx, dtype='complex' ) bn = np.zeros( nx, dtype='complex' ) an[ig], bn[ig] = scattering_utils.an_bn(d[ig,n],refrel[ig],en,x[ig],psi[ig],psi1[ig],xi[ig],xi1[ig]) # an[ig], bn[ig] = self.an_bn(d[ig,n],refrel[ig],en,x[ig],psi[ig],psi1[ig],xi[ig],xi1[ig]) else: psi = (2.0*en-1.0) * psi1/x - psi0 chi = (2.0*en-1.0) * chi1/x - chi0 xi = psi - 1j * chi an = ( d[0,n]/refrel + en/x ) * psi - psi1 an = an / ( ( d[0,n]/refrel + en/x ) * xi - xi1 ) bn = ( refrel*d[0,n] + en / x ) * psi - psi1 bn = bn / ( ( refrel*d[0,n] + en/x ) * xi - xi1 ) # *** Augment sums for Qsca and g=<cos(theta)> # NOTE from LIA: In IDL version, bhmie casts double(an) # and double(bn). This disgards the imaginary part. To # avoid type casting errors, I use an.real and bn.real # Because animag and bnimag were intended to isolate the # real from imaginary parts, I replaced all instances of # double( foo * complex(0.d0,-1.d0) ) with foo.imag qsca += self.compute_qsca(en,an,bn) gsca += self.compute_gsca(en,an,bn) if n > 1: gsca += self.update_gsca(en,an,an1,bn,bn1) # *** Now calculate scattering intensity pattern # First do angles from 0 to 90 # LIA : Altered the two loops below so that only the indices where ang # < 90 are used. Replaced (j) with [indl90] # Note also: If theta is specified, and np.size(E) > 1, # the number of E values must match the number of theta # values. Cosmological halo functions will utilize this # Diff this way. pi = pi1 tau = en * amu * pi - (en + 1.0) * pi0 if np.size(indl90) != 0: antmp = an bntmp = bn if nx > 1: antmp = an[indl90] bntmp = bn[indl90] # For case where multiple E and theta are specified s1[indl90] = s1[indl90] + fn* (antmp*pi[indl90]+bntmp*tau[indl90]) s2[indl90] = s2[indl90] + fn* (antmp*tau[indl90]+bntmp*pi[indl90]) #ENDIF pi_ext = pi1_ext tau_ext = en*1.0*pi_ext - (en+1.0)*pi0_ext s1_ext = s1_ext + fn* (an*pi_ext+bn*tau_ext) s2_ext = s2_ext + fn* (bn*pi_ext+an*tau_ext) # *** Now do angles greater than 90 using PI and TAU from # angles less than 90. # P=1 for N=1,3,...; P=-1 for N=2,4,... p = -p # LIA : Previous code used tau(j) from the previous loop. How do I # get around this? if np.size(indg90) != 0: antmp = an bntmp = bn if nx > 1: antmp = an[indg90] bntmp = bn[indg90] # For case where multiple E and theta are specified s1[indg90] = s1[indg90] + fn*p* (antmp*pi[indg90]-bntmp*tau[indg90]) s2[indg90] = s2[indg90] + fn*p* (bntmp*pi[indg90]-antmp*tau[indg90]) #ENDIF s1_back = s1_back + fn*p* (an*pi_ext-bn*tau_ext) s2_back = s2_back + fn*p* (bn*pi_ext-an*tau_ext) psi0 = psi1 psi1 = psi chi0 = chi1 chi1 = chi xi1 = psi1 - 1j*chi1 # *** Compute pi_n for next value of n # For each angle J, compute pi_n+1 # from PI = pi_n , PI0 = pi_n-1 pi1 = ( (2.0*en+1.0)*amu*pi- (en+1.0)*pi0 ) / en pi0 = pi pi1_ext = ( (2.0*en+1.0)*1.0*pi_ext - (en+1.0)*pi0_ext ) / en pi0_ext = pi_ext # ENDFOR # *** Have summed sufficient terms. # Now compute QSCA,QEXT,QBACK,and GSCA gsca = 2.0 * gsca / qsca qsca = ( 2.0 / np.power(x,2) ) * qsca # LIA : Changed qext to use s1(theta=0) instead of s1(1). Why did the # original code use s1(1)? qext = ( 4.0 / np.power(x,2) ) * s1_ext.real qback = np.power( np.abs(s1_back)/x, 2) / np.pi if getQ == 'sca': return qsca if getQ == 'ext': return qext if getQ == 'back': return qback if getQ == 'gsca': return gsca if getQ == 'diff': bad_theta = np.where( theta_rad > np.pi ) #Set to 0 values where theta > !pi s1[bad_theta] = 0 s2[bad_theta] = 0 return 0.5 * ( np.power( np.abs(s1), 2 ) + np.power( np.abs(s2), 2) ) / (np.pi * x*x) else: return 0.0
def make_WD01_Dustspectrum( R_V=3.1, bc=0.0, rad=dust.adist(), type='Graphite', gal='MW' ): """ make_WD01_Dustspectrum( R_V [float], bc [float], rad [np.array : grain sizes (um)], type [string : 'Graphite' or 'Silicate'] ) gal [string : 'MW', 'LMC', or 'SMC'], ------------------------------------------- Returns a dust.Dustspectrum object containing a (grain sizes), nd (dn/da), and md (total mass density of dust) """ if type == 'Graphite': rho_d = 2.2 #g cm^-3 elif type == 'Silicate': rho_d = 3.8 else: print 'Error: Dust type not recognized' return dist = dust.Dustdist( rad=rad, rho=rho_d, p=4 ) result = dust.Dustspectrum( rad=dist ) ANGS2MICRON = 1.e-10 * 1.e6 a = dist.a a_cm = dist.a * c.micron2cm() NA = np.size( a ) (alpha, beta, a_t, a_c, C) = get_dist_params( R_V=R_V, bc=bc, type=type, gal=gal ) if type == 'Graphite': mc = 12. * 1.67e-24 # Mass of carbon atom in grams (12 m_p) rho = 2.24 # g cm^-3 sig = 0.4 a_01 = 3.5*ANGS2MICRON # 3.5 angstroms in units of microns a_01_cm = a_01 * c.micron2cm() bc1 = 0.75 * bc * 1.e-5 B_1 = (3.0/(2*np.pi)**1.5) * np.exp(-4.5 * 0.4**2) / (rho*a_01_cm**3 * 0.4) \ * bc1 * mc / (1 + special.erf( 3*0.4/np.sqrt(2) + np.log(a_01/3.5e-4)/(0.4*np.sqrt(2)) ) ) a_02 = 30.0*ANGS2MICRON # 30 angtroms in units of microns a_02_cm = a_02 * c.micron2cm() bc2 = 0.25 * bc * 1.e-5 B_2 = (3.0/(2*np.pi)**1.5) * np.exp(-4.5 * 0.4**2) / (rho*a_02_cm**3 * 0.4) \ * bc2 * mc / (1 + special.erf( 3*0.4/np.sqrt(2) + np.log(a_02/3.5e-4)/(0.4*np.sqrt(2)) ) ) D = (B_1/a_cm) * np.exp( -0.5*( np.log(a/a_01)/sig )**2 ) + \ (B_2/a_cm) * np.exp( -0.5*( np.log(a/a_02)/sig )**2 ) Case_vsg = np.where( a < 3.5*ANGS2MICRON ) if np.size(Case_vsg) != 0: D[Case_vsg] = 0.0 Case_g = np.zeros( NA ) case1g = np.where( np.logical_and(a > 3.5*ANGS2MICRON, a < a_t ) ) case2g = np.where( a >= a_t ) if np.size(case1g) != 0: Case_g[case1g] = 1.0 if np.size(case2g) != 0: Case_g[case2g] = np.exp( -( (a[case2g]-a_t) / a_c )**3 ) if beta >= 0: F_g = 1 + beta * a / a_t if beta < 0: F_g = 1.0 / (1 - beta * a / a_t) Dist_WD01 = D + C/a_cm * (a/a_t)**alpha * F_g * Case_g #cm^-4 per n_H if type == 'Silicate': Case_s = np.zeros( NA ) case1s = np.where( np.logical_and( a > 3.5*ANGS2MICRON, a < a_t ) ) case2s = np.where( a >= a_t ) if np.size(case1s) != 0: Case_s[case1s] = 1.0 if np.size(case2s) != 0: Case_s[case2s] = np.exp( -( (a[case2s]-a_t)/a_c )**3 ) F_s = np.zeros( NA ) if beta >= 0: F_s = 1 + beta * a / a_t if beta < 0: F_s = 1. / (1 - beta * a / a_t) Dist_WD01 = C/a_cm * (a/a_t)**alpha * F_s * Case_s #cm^-4 per n_H ## Modify result Dustspectrum so we get a proper WD01 dist! mg = 4.0/3.0*np.pi*a_cm**3 * rho_d # mass of each dust grain Md = c.intz( a_cm, Dist_WD01 * mg ) result.nd = Dist_WD01 * c.micron2cm() # cm^-3 per um per n_H result.md = Md return result
def dnda(self, md=1.5e-5): adep = np.power( self.a, -self.p ) # um^-p dmda = adep * 4./3. * np.pi * self.rho * np.power( self.a*c.micron2cm(), 3 ) # g um^-p const = md / c.intz( self.a, dmda ) # cm^-? um^p-1 return const * adep # cm^-? um^-1
def ndens(self, md=1.5e-5 ): gvol = 4./3. * np.pi * np.power( self.a*c.micron2cm(), 3 ) return md / ( gvol*self.rho )