def __init__( self, theta=power_angles(), \ scatm=ss.Scatmodel(), \ rad=0.1, ener=1.0, nx=1000 ): # Theta automatically sampled in log space. # If I don't do it this way, linear interpolation easily fails # for small angles (between theta[0] and theta[1]). Since # most of my plots are in log-space, it makes more sense to # sample logarithmically. if np.size(theta) < 2: print 'Error: Must give more than one theta value' self.theta = None self.rad = None self.ener = None self.scatm = None self.theta = theta self.rad = rad self.ener = ener self.scatm = scatm dxi = 1.0 / np.float(nx) xi = np.arange( 0, 1.0, dxi ) + dxi itemp = np.array([]) for th in self.theta: thscat = th / xi dsig = ss.Diffscat( theta=thscat, scatm=self.scatm, E=self.ener, a=self.rad ).dsig itemp = np.append( itemp, \ c.intz( xi, dsig/(xi**2) ) ) self.itemp = itemp * c.arcs2rad()**2
def DA( theta, z, cosm=Cosmology(), nz=100 ): """ Calculates the diameter distance [Gpc] for an object of angular size theta and redshift z using DA = theta(radians) * DChi / (1+z) theta : float : angular size [arcsec] z : float : redshift of object cosm : Cosmology nz : int (100) : number of z-values to use in DChi calculation """ dchi = DChi( z, cosm=cosm, nz=nz ) return theta * c.arcs2rad() * dchi / (1+z)
def ScreenIGM(halo, zs=2.0, zg=1.0, md=1.5e-5, cosm=cosmo.Cosmology()): """ FUNCTION ScreenIGM( halo, zs=2.0, zg=1.0, md=1.5e-5, cosm=cosmo.Cosmology() ) MODIFIES halo.htype, halo.dist, halo.intensity, halo.taux -------------------------------------------------------------------- halo : Halo object zs : float : redshift of source zg : float : redshift of screen md : float : mass density of dust to use in screen [g cm^-2] cosm : cosmo.Cosmology """ if zg >= zs: print "%% STOP: zg must be < zs" E0 = halo.energy alpha = halo.alpha scatm = halo.scatm # Store information about this halo calculation halo.htype = CosmHalo(zs=zs, zg=zg, cosm=cosm, igmtype="Screen") halo.dist = dust.Dustspectrum(rad=halo.rad, md=md) X = cosmo.DChi(zs, zp=zg, cosm=cosm) / cosmo.DChi(zs, cosm=cosm) # Single value thscat = alpha / X # Scattering angle required Eg = E0 * (1 + zg) # Photon energy at the screen ## Single grain size case if type(halo.rad) == dust.Grain: dsig = ss.Diffscat(theta=thscat, a=halo.dist.a, E=Eg, scatm=scatm).dsig intensity = halo.dist.nd / np.power(X, 2) * dsig ## Distribution of grain sizes elif type(halo.rad) == dust.Dustdist: avals = halo.dist.a dsig = np.zeros(shape=(np.size(avals), np.size(thscat))) for i in range(np.size(avals)): dsig[i, :] = ss.Diffscat(theta=thscat, a=avals[i], E=Eg, scatm=scatm).dsig intensity = np.array([]) for j in range(np.size(thscat)): itemp = halo.dist.nd * dsig[:, j] / np.power(X, 2) intensity = np.append(intensity, c.intz(avals, itemp)) else: print "%% Must input type dust.Grain or dust.Dustdist" intensity = np.zeros(np.size(zpvals)) halo.intensity = intensity * np.power(c.arcs2rad(), 2) # arcsec^-2 halo.taux = cosmo.CosmTauScreen(zg, E=halo.energy, dist=halo.dist, scatm=halo.scatm)
def DiscreteISM( halo, xg=0.5, NH=1.0e20, d2g=0.009 ): """ FUNCTION DiscreteISM( halo, xg=0.5, NH=1.0e20, d2g=0.009 ) MODIFIES halo.htype, halo.dist, halo.taux, halo.intensity ---------------------------------------------------------------------------- halo : Halo object xg : float : distance FROM source / distance between source and observer NH : float : column density [cm^-2] d2g : float : dust-to-gass mass ratio """ E0 = halo.energy alpha = halo.alpha scatm = halo.scatm md = NH * c.mp() * d2g halo.htype = GalHalo( xg=xg, NH=NH, d2g=d2g, ismtype='Screen' ) halo.dist = dust.Dustspectrum( rad=halo.rad, md=md ) thscat = alpha / xg if type(halo.rad) == dust.Grain: dsig = ss.Diffscat( theta=thscat, a=halo.dist.a, E=E0, scatm=scatm ).dsig intensity = np.power( xg, -2.0 ) * dsig * halo.dist.nd elif type(halo.rad) == dust.Dustdist: avals = halo.dist.a intensity = [] for i in range( len(alpha) ): iatemp = np.zeros( shape=( len(avals),len(alpha) ) ) for j in range( len(avals) ): dsig = ss.Diffscat( theta=thscat, a=avals[j], E=E0, scatm=scatm ).dsig iatemp[j,:] = np.power(xg,-2.0) * dsig intensity.append( c.intz( avals, iatemp[:,i] * halo.dist.nd ) ) intensity = np.array( intensity ) else: print '%% Must input type dust.Grain or dust.Dustdist' intensity = np.zeros( np.size(xvals) ) halo.intensity = intensity * np.power( c.arcs2rad(), 2 ) # arcsec^-2 halo.taux = ss.Kappascat( E=halo.energy, scatm=halo.scatm, dist=halo.dist ).kappa * md
def path_diff( alpha, x ): """ path_diff( alpha, x ) ----------------------- INPUT alpha : scalar : observation angle [arcsec] x : scalar or np.array : position of dust patch (source is at x=0, observer at x=1) ----------------------- OUTPUT path difference associated with a particular alpha and x : alpha^2*(1-x)/(2x) """ if np.size( alpha ) > 1: print 'Error: np.size(alpha) cannot be greater than one.' return if np.max( x ) > 1.0 or np.min( x ) < 0: print 'Error: x must be between 0 and 1' return alpha_rad = alpha * c.arcs2rad() return alpha_rad**2 * (1-x) / (2*x)
def UniformIGM(halo, zs=4.0, cosm=cosmo.Cosmology(), nz=500): """ FUNCTION UniformIGM( halo, zs=4.0, cosm=cosmo.Cosmology(), nz=500 ) MODIFIES halo.htype, halo.dist, halo.intensity, halo.taux -------------------------------------------------------------------- halo : Halo object zs : float : redshift of source cosm : cosmo.Cosmology nz : int : number of z-values to use in integration """ E0 = halo.energy alpha = halo.alpha scatm = halo.scatm halo.htype = CosmHalo(zs=zs, cosm=cosm, igmtype="Uniform") # Stores information about this halo calc halo.dist = dust.Dustspectrum(rad=halo.rad, md=cosmo.Cosmdens(cosm=cosm).md) Dtot = cosmo.DChi(zs, cosm=cosm, nz=nz) zpvals = cosmo.zvalues(zs=zs - zs / nz, z0=0, nz=nz) DP = np.array([]) for zp in zpvals: DP = np.append(DP, cosmo.DChi(zs, zp=zp, cosm=cosm)) X = DP / Dtot c_H0_cm = c.cperh0() * (c.h0() / cosm.h0) # cm hfac = np.sqrt(cosm.m * np.power(1 + zpvals, 3) + cosm.l) Evals = E0 * (1 + zpvals) ## Single grain case if type(halo.rad) == dust.Grain: intensity = np.array([]) f = 0.0 cnt = 0.0 na = np.size(alpha) for al in alpha: cnt += 1 thscat = al / X # np.size(thscat) = nz dsig = ss.Diffscat(theta=thscat, a=halo.dist.a, E=Evals, scatm=scatm).dsig itemp = c_H0_cm / hfac * np.power((1 + zpvals) / X, 2) * halo.dist.nd * dsig intensity = np.append(intensity, c.intz(zpvals, itemp)) ## Dust distribution case elif type(halo.rad) == dust.Dustdist: avals = halo.dist.a intensity = np.array([]) for al in alpha: thscat = al / X # np.size(thscat) = nz iatemp = np.array([]) for aa in avals: dsig = ss.Diffscat(theta=thscat, a=aa, E=Evals, scatm=scatm).dsig dtmp = c_H0_cm / hfac * np.power((1 + zpvals) / X, 2) * dsig iatemp = np.append(iatemp, c.intz(zpvals, dtmp)) intensity = np.append(intensity, c.intz(avals, halo.dist.nd * iatemp)) else: print "%% Must input type dust.Grain or dust.Dustdist" intensity = np.zeros(np.size(zpvals)) # ----- Finally, set the halo intensity -------- halo.intensity = intensity * np.power(c.arcs2rad(), 2) # arcsec^-2 halo.taux = cosmo.CosmTauX(zs, E=halo.energy, dist=halo.rad, scatm=halo.scatm, cosm=halo.htype.cosm)
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 UniformISM( halo, NH=1.0e20, d2g=0.009, nx=1000, usepathdiff=False ): """ FUNCTION UniformISM( halo, NH=1.0e20, d2g=0.009, nx=1000, usepathdiff=False ) MODIFIES halo.htype, halo.dist, halo.taux, halo.intensity ---------------------------------------------------------------------------- halo : Halo object NH : float : column density [cm^-2] d2g : float : dust-to-gass mass ratio nx : int : number of values to use in integration usepathdiff : boolean : True = use extinction due to path difference e^(-tau*path_diff) """ E0 = halo.energy alpha = halo.alpha scatm = halo.scatm md = NH * c.mp() * d2g halo.htype = GalHalo( NH=NH, d2g=d2g, ismtype='Uniform' ) halo.dist = dust.Dustspectrum( rad=halo.rad, md=md ) halo.taux = ss.Kappascat( E=halo.energy, scatm=halo.scatm, dist=halo.dist ).kappa * md dx = 1.0 / nx xvals = np.arange( 0.0, 1.0, dx ) + dx #--- Single grain case --- if type( halo.rad ) == dust.Grain: intensity = np.array([]) for al in alpha: thscat = al / xvals # np.size(thscat) = nx dsig = ss.Diffscat( theta=thscat, a=halo.dist.a, E=E0, scatm=scatm ).dsig delta_tau = 0.0 if usepathdiff: print 'Using path difference' delta_x = path_diff( al, xvals ) delta_tau = halo.taux * delta_x print np.max( delta_x ) itemp = np.power( xvals, -2.0 ) * dsig * halo.dist.nd * np.exp( -delta_tau ) intensity = np.append( intensity, c.intz( xvals, itemp ) ) #--- Dust distribution case --- elif type( halo.rad ) == dust.Dustdist: avals = halo.dist.a intensity = np.array([]) for al in alpha: thscat = al / xvals # np.size(thscat) = nx iatemp = np.array([]) for aa in avals: dsig = ss.Diffscat( theta=thscat, a=aa, E=E0, scatm=scatm ).dsig delta_tau = 0.0 if usepathdiff: print 'Using path difference' delta_x = path_diff( al, xvals ) delta_tau = halo.taux * delta_x print max( delta_x ) dtemp = np.power( xvals, -2.0 ) * dsig * np.exp( -delta_tau ) iatemp = np.append( iatemp, c.intz( xvals, dtemp ) ) intensity = np.append( intensity, c.intz( avals, halo.dist.nd * iatemp ) ) else: print '%% Must input type dust.Grain or dust.Dustdist' intensity = np.zeros( np.size(xvals) ) # Set the halo intensity halo.intensity = intensity * np.power( c.arcs2rad(), 2 ) # arcsec^-2