def ionization_from_luminosity(z, ratedensityfunc, xHe=1.0, rate_is_tfunc = False, ratedensityfunc_args = (), method = 'romberg', **cosmo): """Integrate the ionization history given an ionizing luminosity function, ignoring recombinations. Parameters ---------- ratedensityfunc: callable function giving comoving ionizing photon emission rate density, or ionizing emissivity (photons s^-1 Mpc^-3) as a function of redshift (or time). rate_is_tfunc: boolean Set to true if ratedensityfunc is a function of time rather than z. Notes ----- Ignores recombinations. The ionization rate is computed as ratedensity / nn, where nn = nH + xHe * nHe. So if xHe is 1.0, we are assuming that helium becomes singly ionized at proportionally the same rate as hydrogen. If xHe is 2.0, we are assuming helium becomes fully ionizing at proportionally the same rate as hydrogen. The returened x is therefore the ionized fraction of hydrogen, and the ionized fraction of helium is xHe * x. """ cosmo = cd.set_omega_k_0(cosmo) rhoc, rho0, nHe, nH = cden.baryon_densities(**cosmo) nn = (nH + xHe * nHe) if rate_is_tfunc: t = cd.age(z, **cosmo)[0] def dx_dt(t1): return numpy.nan_to_num(ratedensityfunc(t1, *ratedensityfunc_args) / nn) sorti = numpy.argsort(t) x = numpy.empty(t.shape) x[sorti] = cu.integrate_piecewise(dx_dt, t[sorti], method = method) return x else: dt_dz = lambda z1: cd.lookback_integrand(z1, **cosmo) def dx_dz(z1): z1 = numpy.abs(z1) return numpy.nan_to_num(dt_dz(z1) * ratedensityfunc(z1, *ratedensityfunc_args) / nn) sorti = numpy.argsort(-z) x = numpy.empty(z.shape) x[sorti] = cu.integrate_piecewise(dx_dz, -z[sorti], method = method) return x
def params_z(self, z): """Return interp/extrapolated Schechter function parameters.""" if self.extrap_var == 'z': return {'MStar':self._MStarfunc(z), 'phiStar':self._phiStarfunc(z), 'alpha':self._alphafunc(z)} elif self.extrap_var == 't': z = numpy.atleast_1d(z) t = cd.age(z, **self.cosmo)[0] return self.params_t(t)
def integrate_ion_recomb_collapse(z, coeff_ion, temp_min = 1e4, passed_min_mass = False, temp_gas=1e4, alpha_B=None, clump_fact_func = clumping_factor_BKP, **cosmo): """IGM ionization state with recombinations from halo collapse fraction. Integrates an ODE describing IGM ionization and recombination rates. z: array The redshift values at which to calculate the ionized fraction. This array should be in reverse numerical order. The first redshift specified should be early enough that the universe is still completely neutral. coeff_ion: The coefficient converting the collapse fraction to ionized fraction, neglecting recombinations. Equivalent to the product (f_star * f_esc_gamma * N_gamma) in the BKP paper. temp_min: See docs for ionization_from_collapse. Either the minimum virial temperature or minimum mass of halos contributing to reionization. passed_temp_min: See documentation for ionization_from_collapse. temp_gas: Gas temperature used to calculate the recombination coefficient if alpha_b is not specified. alpha_B: Optional recombination coefficient in units of cm^3 s^-1. In alpha_B=None, it is calculated from temp_gas. clump_fact_func: function Function returning the clumping factor when given a redshift. cosmo: dict Dictionary specifying the cosmological parameters. We assume, as is fairly standard, that the ionized fraction is contained in fully ionized bubbles surrounded by a fully neutral IGM. The output is therefore the volume filling factor of ionized regions, not the ionized fraction of a uniformly-ionized IGM. I have also made the standard assumption that all ionized photons are immediately absorbed, which allows the two differential equations (one for ionization-recombination and one for emission-photoionizaion) to be combined into a single ODE. """ # Determine recombination coefficient. if alpha_B is None: alpha_B_cm = recomb_rate_coeff_HG(temp_gas, 'H', 'B') else: alpha_B_cm = alpha_B alpha_B = alpha_B_cm / (cc.Mpc_cm**3.) print ("Recombination rate alpha_B = %.4g (Mpc^3 s^-1) = %.4g (cm^3 s^-1)" % (alpha_B, alpha_B_cm)) # Normalize power spectrum. if 'deltaSqr' not in cosmo: cosmo['deltaSqr'] = cp.norm_power(**cosmo) # Calculate useful densities. rho_crit, rho_0, n_He_0, n_H_0 = cden.baryon_densities(**cosmo) # Function used in the integration. # Units: (Mpc^3 s^-1) * Mpc^-3 = s^-1 coeff_rec_func = lambda z: (clump_fact_func(z)**2. * alpha_B * n_H_0 * (1.+z)**3.) # Generate a function that converts redshift to age of the universe. redfunc = cd.quick_redshift_age_function(zmax = 1.1 * numpy.max(z), zmin = -0.0, **cosmo) # Function used in the integration. ionfunc = quick_ion_col_function(coeff_ion, temp_min, passed_min_mass = passed_min_mass, zmax = 1.1 * numpy.max(z), zmin = -0.0, zstep = 0.1, **cosmo) # Convert specified redshifts to cosmic time (age of the universe). t = cd.age(z, **cosmo) # Integrate to find u(z) = x(z) - w(z), where w is the ionization fraction u = si.odeint(_udot, y0=0.0, t=t, args=(coeff_rec_func, redfunc, ionfunc)) u = u.flatten() w = ionization_from_collapse(z, coeff_ion, temp_min, passed_min_mass = passed_min_mass, **cosmo) x = u + w x[x > 1.0] = 1.0 return x, w, t
def integrate_ion_recomb(z, ion_func, clump_fact_func, xHe=1.0, temp_gas=1e4, alpha_B=None, bubble=True, **cosmo): """Integrate IGM ionization and recombination given an ionization function. Parameters: z: array The redshift values at which to calculate the ionized fraction. This array should be in reverse numerical order. The first redshift specified should be early enough that the universe is still completely neutral. ion_func: A function giving the ratio of the total density of emitted ionizing photons to the density hydrogen atoms (or hydrogen plus helium, if you prefer) as a function of redshift. temp_gas: Gas temperature used to calculate the recombination coefficient if alpha_b is not specified. alpha_B: Optional recombination coefficient in units of cm^3 s^-1. In alpha_B=None, it is calculated from temp_gas. clump_fact_func: function Function returning the clumping factor when given a redshift, defined as <n_HII^2>/<n_HII>^2. cosmo: dict Dictionary specifying the cosmological parameters. Notes: We only track recombination of hydrogen, but if xHe > 0, then the density is boosted by the addition of xHe * nHe. This is eqiuvalent to assuming the the ionized fraction of helium is always proportional to the ionized fraction of hydrogen. If xHe=1.0, then helium is singly ionized in the same proportion as hydrogen. If xHe=2.0, then helium is fully ionized in the same proportion as hydrogen. We assume, as is fairly standard, that the ionized fraction is contained in fully ionized bubbles surrounded by a fully neutral IGM. The output is therefore the volume filling factor of ionized regions, not the ionized fraction of a uniformly-ionized IGM. I have also made the standard assumption that all ionized photons are immediately absorbed, which allows the two differential equations (one for ionization-recombination and one for emission-photoionizaion) to be combined into a single ODE. """ # Determine recombination coefficient. if alpha_B is None: alpha_B_cm = recomb_rate_coeff_HG(temp_gas, 'H', 'B') else: alpha_B_cm = alpha_B alpha_B = alpha_B_cm * cc.Gyr_s / (cc.Mpc_cm**3.) print ("Recombination rate alpha_B = %.4g (Mpc^3 Gyr^-1) = %.4g (cm^3 s^-1)" % (alpha_B, alpha_B_cm)) # Normalize power spectrum. if 'deltaSqr' not in cosmo: cosmo['deltaSqr'] = cp.norm_power(**cosmo) # Calculate useful densities. rho_crit, rho_0, n_He_0, n_H_0 = cden.baryon_densities(**cosmo) # Boost density to approximately account for helium. nn = (n_H_0 + xHe * n_He_0) # Function used in the integration. # Units: (Mpc^3 Gyr^-1) * Mpc^-3 = Gyr^-1 coeff_rec_func = lambda z1: (clump_fact_func(z1) * alpha_B * nn * (1.+z1)**3.) # Generate a function that converts age of the universe to z. red_func = cd.quick_redshift_age_function(zmax = 1.1 * numpy.max(z), zmin = -0.0, dz = 0.01, **cosmo) ref_func_Gyr = lambda t1: red_func(t1 * cc.Gyr_s) # Convert specified redshifts to cosmic time (age of the universe). t = cd.age(z, **cosmo)[0]/cc.Gyr_s # Integrate to find u(z) = x(z) - w(z), where w is the ionization fraction u = si.odeint(_udot, y0=0.0, t=t, args=(coeff_rec_func, ref_func_Gyr, ion_func, bubble)) u = u.flatten() w = ion_func(z) x = u + w #x[x > 1.0] = 1.0 return x, w, t
def plotLFevo(hist=None, params=B2008, extrap_var='t', #maglim = -21.07 - 2.5 * numpy.log10(0.2)): maglim = -21. - 2.5 * numpy.log10(0.2), z_max=20.0, skipIon=True ): """Plot evolution of luminosity function params and total luminsity. Schechter function at each redshift is integrated up to maglim to find total luminsity. """ for (k, v) in params.iteritems(): params[k] = numpy.asarray(v) if hist is None: cosmo = cp.WMAP7_BAO_H0_mean(flat=True) hist = LFHistory(params, extrap_var=extrap_var, **cosmo) else: params = hist.params extrap_var = hist.extrap_var cosmo = hist.cosmo z = params['z'] t = cd.age(z, **cosmo)[0] / cc.yr_s MStar = params['MStar'] phiStar = params['phiStar'] alpha = params['alpha'] if maglim is None: ltot = schechterTotLM(MStar=MStar, phiStar=phiStar, alpha=alpha) else: ltot = schechterCumuLM(magnitudeAB=maglim, MStar=MStar, phiStar=phiStar, alpha=alpha) print (hist._MStarfunc.extrap_string()) zPlot = numpy.arange(z.min()-0.1, z_max, 0.1) tPlot = cd.age(zPlot, **cosmo)[0] / cc.yr_s newparams = hist.params_z(zPlot) MPlot = newparams['MStar'] phiPlot = newparams['phiStar'] alphaPlot = newparams['alpha'] if maglim is None: ltotPlot = hist.schechterTotLM(zPlot) else: ltotPlot = hist.schechterCumuLM(zPlot, magnitudeAB=maglim) # From Table 6 of 2008ApJ...686..230B lB2008 =10.** numpy.array([26.18, 25.85, 25.72, 25.32, 25.14]) iPhot = hist.iPhotonRateDensity_z(zPlot, maglim=maglim) # iPhotFunc = lambda t1: cc.yr_s * hist.iPhotonRateDensity_t(t1, # maglim=maglim) if not skipIon: xH = hist.ionization(zPlot, maglim) import pylab pylab.figure(1) pylab.gcf().set_label('LFion_vs_z') if skipIon: pylab.subplot(111) else: pylab.subplot(211) pylab.plot(zPlot, iPhot) if not skipIon: pylab.subplot(212) pylab.plot(zPlot, xH) pylab.ylim(0.0, 1.5) pylab.figure(2) pylab.gcf().set_label('LFparams_vs_z') pylab.subplot(311) pylab.plot(z, MStar, '-') pylab.plot(z, MStar, 'o') pylab.plot(zPlot, MPlot, ':') pylab.subplot(312) pylab.plot(z, phiStar, '-') pylab.plot(z, phiStar, 'o') pylab.plot(zPlot, phiPlot, ':') pylab.subplot(313) pylab.plot(z, alpha, '-') pylab.plot(z, alpha, 'o') pylab.plot(zPlot, alphaPlot, ':') pylab.figure(3) pylab.gcf().set_label('LFparams_vs_t') pylab.subplot(311) pylab.plot(t, MStar, '-') pylab.plot(t, MStar, 'o') pylab.plot(tPlot, MPlot, ':') pylab.subplot(312) pylab.plot(t, phiStar, '-') pylab.plot(t, phiStar, '.') pylab.plot(tPlot, phiPlot, ':') pylab.subplot(313) pylab.plot(t, alpha, '-') pylab.plot(t, alpha, 'o') pylab.plot(tPlot, alphaPlot, ':') pylab.figure(4) pylab.gcf().set_label('LFlum_vs_z') pylab.subplot(121) pylab.plot(z, ltot, 'o') pylab.plot(z, lB2008, 'x') pylab.plot(zPlot, ltotPlot) pylab.subplot(122) pylab.plot(t, ltot, 'o') pylab.plot(t, lB2008, 'x') pylab.plot(tPlot, ltotPlot)
def __init__(self, params=B2008, MStar_bounds = ['extrapolate', float('NaN')], phiStar_bounds = ['constant', float('NaN')], alpha_bounds = ['constant', float('NaN')], extrap_args = {}, extrap_var = 'z', sedParams = {}, wavelength = 1500., **cosmo): for (k, v) in params.iteritems(): params[k] = numpy.asarray(v) self.params = params self.MStar_bounds = MStar_bounds self.phiStar_bounds = phiStar_bounds self.alpha_bounds = alpha_bounds self.extrap_args = extrap_args self.extrap_var = extrap_var self.sedParams = sedParams self.wavelength = wavelength self.cosmo = cosmo self.zobs = params['z'] self.tobs = cd.age(self.zobs, **cosmo)[0] self.MStar = params['MStar'] self.phiStar = params['phiStar'] self.alpha = params['alpha'] if extrap_var == 't': self.xobs = self.tobs self._iPhotFunc = \ lambda t1, mag: (self.iPhotonRateDensity_t(t1, maglim=mag)) elif extrap_var == 'z': self.xobs = self.zobs MStar_bounds = MStar_bounds[::-1] phiStar_bounds = phiStar_bounds[::-1] alpha_bounds = alpha_bounds[::-1] self._iPhotFunc = \ lambda z1, mag: (self.iPhotonRateDensity_z(z1, maglim=mag)) self._MStarfunc = utils.Extrapolate1d(self.xobs, self.MStar, bounds_behavior=MStar_bounds, **extrap_args ) print ("M*:", self._MStarfunc.extrap_string()) self._phiStarfunc = utils.Extrapolate1d(self.xobs, self.phiStar, bounds_behavior=phiStar_bounds, **extrap_args ) print ("phi*:", self._phiStarfunc.extrap_string()) self._alphafunc = utils.Extrapolate1d(self.xobs, self.alpha, bounds_behavior=alpha_bounds, **extrap_args ) print ("alpha:", self._alphafunc.extrap_string()) self._SED = BrokenPowerlawSED(**sedParams) self._rQL = self._SED.iPhotonRateRatio(wavelength) for name, func in globals().items(): if not name.startswith('schechter'): continue def newfunc(z, _name=name, _func=func, **kwargs): params = self.params_z(z) M = params['MStar'] phi = params['phiStar'] alpha = params['alpha'] return _func(MStar=M, phiStar=phi, alpha=alpha, **kwargs) newfunc.__name__ = func.__name__ newfunc.__doc__ = func.__doc__ self.__dict__[name] = newfunc