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() ): # 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 sample_extinction(sample, lam, isample, NA=20, d2g=0.009, scatm=ss.makeScatmodel("RG", "Drude")): energy = c.kev2lam() / lam # lam must be in cm to get keV logMD = sample_logMD(sample) MD = np.power(10.0, logMD) result = [] for i in isample: logNH, amax, p = sample[i] print "logNH =", logNH, "\tamax =", amax, "\tp =", p da = (amax - AMIN) / np.float(NA) dist = dust.Dustdist(rad=np.arange(AMIN, amax + da, da), p=p) spec = dust.Dustspectrum(rad=dist, md=MD[i]) kappa = ss.Kappascat(E=energy, dist=spec, scatm=scatm).kappa result.append(1.086 * MD[i] * kappa) return result
def get_Q( self, E, qtype, a ): try : data = parse_PAH( self.type ) except : print 'ERROR: Cannot find PAH type', self.type return try : qvals = np.array( data[a][qtype] ) wavel = np.array( data[a]['w(micron)'] ) except : print 'ERROR: Cannot get grain size', a, 'for', self.stype return # Wavelengths were listed in reverse order q_interp = interp1d( wavel[::-1], qvals[::-1] ) E_um = ( c.kev2lam() / E ) * 1.e4 # cm to um return q_interp( E_um )
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 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 rp( self, E ): mm1 = self.rho / ( 2.0*c.mp() ) * c.re()/(2.0*np.pi) * np.power( c.kev2lam()/E , 2 ) return mm1+1