Package enrichpy :: Module enrichment

Source Code for Module enrichpy.enrichment

  1  """Classes and routines to calculate enrichment and ionization histories. 
  2  """ 
  3  import copy 
  4   
  5  import numpy 
  6  import scipy 
  7  import scipy.constants.codata as scc 
  8   
  9  import cosmolopy.reionization as cr 
 10  import cosmolopy.utils as utils 
 11  from cosmolopy.utils import Saveable 
 12   
 13  from cosmolopy import luminosityfunction 
 14  import padconvolve 
 15   
16 -def tophat_delay_kernel(t, tmin, tmax):
17 """Make a normalized tophat function (nonzero from tmin to tmax). 18 19 t: array-like 20 21 The result is normalized so that the sum of the bins is equal to 22 norm (not the integral), i.e. there's no multiplication by the 23 width of the bins in the normalization. 24 25 Returns the kernel and the cdf. 26 """ 27 28 kernel = numpy.ones(t.shape) 29 kernel[t>tmax] = 0. 30 kernel[t<tmin] = 0. 31 ksum = numpy.sum(kernel) 32 kernel = kernel/ksum 33 cdf = numpy.cumsum(kernel) 34 return kernel, cdf
35
36 -def get_omega_CIV_data(filename):
37 data = numpy.loadtxt(filename, skiprows=1, delimiter=',', 38 usecols=range(5)) 39 return data
40
41 -class LFIonHistory(Saveable):
42 """Calculate an ionization history based on luminosity function evolution. 43 44 LF parameters enter here, e.g. choice of LF, f_esc_gamma, M*-z slope, alpha 45 46 Calculate ionizing emissivity history (from LF, for example) Ndot(z). 47 Integrate ionizing emissivity to find x_tot(z). 48 Integrate recombinations to find x(z). 49 """ 50
51 - def __init__(self, z, lf_params, cosmo, 52 lf_args={}, lf_maglim=None, MStar_z_slope=None, alpha=None, 53 f_esc_gamma=None, clump_fact=None, clump_fact_func=None):
54 """ 55 z - array of redshift values 56 lf_params - dictionary with keys 'z', 'MStar', 'phiStar', 'alpha' 57 f_esc_gamma - ionizing photon escape fraction 58 cosmo - cosmological parameter dict (see cosmolopy.parameters) 59 lf_args - dict of keyword args for luminosityfunction.LFHistory 60 lf_maglim - magnitude limit for interating LF 61 MStar_z_slope - slope of high-z M* extrapolation 62 alpha - faint-end slope of the LF 63 """ 64 65 self.z = z 66 self.lf_params = lf_params 67 self.cosmo = cosmo 68 self.lf_args = lf_args 69 self.lf_maglim = lf_maglim 70 self.MStar_z_slope = MStar_z_slope 71 self.alpha = alpha 72 self.f_esc_gamma = f_esc_gamma 73 self.clump_fact = clump_fact 74 self.clump_fact_func = clump_fact_func 75 76 ### Set up galaxy luminosity function history. ### 77 if alpha is not None: 78 lf_params = copy.deepcopy(lf_params) 79 lf_params['alpha'] = numpy.array(lf_params['alpha']) 80 lf_params['alpha'][:] = alpha 81 self.lf_params = lf_params 82 83 if MStar_z_slope is not None: 84 lf_args = copy.deepcopy(lf_args) 85 if not 'extrap_args' in lf_args: 86 lf_args['extrap_args'] = {} 87 lf_args['extrap_args']['slopes'] = [0, MStar_z_slope] 88 lf_args['extrap_var'] = 'z' 89 self.lf_args = lf_args 90 91 args = copy.deepcopy(self.cosmo) 92 args.update(lf_args) 93 self.lfh = luminosityfunction.LFHistory(params=lf_params,**args) 94 95 # Ionizing photon emissivity. 96 self.NdotTot = self.lfh.iPhotonRateDensity_z(z, lf_maglim) 97 98 # Integrate ionization. 99 self.xTot = self.lfh.ionization(z, lf_maglim) 100 101 if f_esc_gamma is not None: 102 self.xEsc = self.xTot * f_esc_gamma 103 self.integrate_ion_recomb() 104 self.optical_depth() 105 self.optical_depth(use_recomb=False)
106
107 - def make_ion_func(self):
108 self.xEsc = self.xTot * self.f_esc_gamma 109 ion_func = utils.Extrapolate1d(self.z, self.xEsc, 110 bounds_behavior=[0.0, 111 'extrapolate'] 112 ) 113 return ion_func
114
115 - def make_clump_fact_func(self):
116 if self.clump_fact is not None: 117 self.clump_fact_func = lambda z1: self.clump_fact 118 if self.clump_fact_func is None: 119 self.clump_fact_func = cr.clumping_factor_HB 120 return self.clump_fact_func
121
122 - def integrate_ion_recomb(self):
123 """Integrate the recombination rate. 124 125 Returns and stores results. 126 """ 127 self.make_clump_fact_func() 128 ion_func = self.make_ion_func() 129 130 x, w, t = cr.integrate_ion_recomb(self.z, 131 ion_func=ion_func, 132 clump_fact_func = 133 self.clump_fact_func, 134 **self.cosmo) 135 self.xr = x 136 self.w = w 137 self.t = t 138 return x, w, t
139
140 - def optical_depth(self, use_recomb=True, store=True):
141 """Calculate Thompson electron scattering optical depth.""" 142 143 # First calculate the optical depth from the fully-ionized 144 # epoch up to our minimum redshift. 145 tau_z0 = cr.optical_depth_instant(self.z[-1], **self.cosmo) 146 147 # Then the contribution from the partially-ionized epoch. 148 if use_recomb: 149 xPhys = self.xr.copy() 150 else: 151 xPhys = self.xEsc.copy() 152 xPhys[xPhys > 1.0] = 1.0 153 tau_later = (tau_z0 + 154 cr.integrate_optical_depth(xPhys[...,::-1], 155 xPhys[...,::-1], 156 self.z[::-1], 157 **self.cosmo)) 158 tau = tau_later[...,::-1] 159 if store: 160 if use_recomb: 161 self.tau = tau 162 else: 163 self.tauEsc = tau 164 return tau
165
166 - def reionization_redshift(self):
167 """Find the redshift at with xr reaches unity.""" 168 z_ion_func = utils.Extrapolate1d(self.xr, self.z) 169 self.z_reion = z_ion_func(1.0) 170 return self.z_reion
171
172 -class EnrichmentHistory(Saveable):
173 """Calculate CIV histories based on ionization. 174 """ 175
176 - def __init__(self, ionHist, f_x_Z, z_change=None, f_change=None, 177 delay_kernel=None, 178 ):
179 """Calculate instantaneos histories based on ionization. 180 181 Enrichment parameters enter here, e.g. f_x_Z, z_change, f_change. 182 183 Omega_Z is fraction of critical density contributed by metals 184 = nz * mz / rho_crit. 185 186 Z can just as well represent a specific metal element or 187 ionization species given the appropriatly decreased f_x_Z. 188 189 """ 190 self.ionHist = ionHist 191 self.f_x_Z = f_x_Z 192 self.z_change = z_change 193 self.f_change=f_change 194 self.z = ionHist.z 195 self.calculate_Omega_Z() 196 self.delay_kernel = delay_kernel 197 if delay_kernel is not None: 198 self.convolve()
199
200 - def calculate_Omega_Z(self):
201 if self.z_change is not None: 202 print "f_x_Z changes from %.3g to %.3g (f = %.3g) at z<%.2f" % \ 203 (self.f_x_Z, 204 self.f_x_Z * self.f_change, 205 self.f_change, self.z_change) 206 xfunc = scipy.interpolate.interp1d(self.ionHist.z[::-1], 207 self.ionHist.xTot[::-1]) 208 x_change = xfunc(self.z_change) 209 xTot = self.ionHist.xTot 210 211 f_early = self.f_x_Z 212 f_late = self.f_x_Z * self.f_change 213 self.Omega_Z = ((self.z > self.z_change) * f_early * xTot + 214 (self.z <= self.z_change) * 215 (f_late * xTot + (f_early - f_late) * x_change)) 216 else: 217 print "f_x_Z = %.3g" % (self.f_x_Z) 218 self.Omega_Z = self.f_x_Z * self.ionHist.xTot 219 return self.Omega_Z
220
221 - def convolve(self):
222 """Convolve delay curve with metal histories. 223 Convolve Omega_Z with delay curve to produce Omega_Z_convolved. 224 """ 225 self.Omega_Z_convolved = padconvolve.padded_convolve(self.Omega_Z, 226 self.delay_kernel, 227 origin=0, 228 value=0.0) 229 return self.Omega_Z_convolved
230
231 - def binned_Omega_Z(self, zmin, zmax, useConvolved=True):
232 if useConvolved: 233 o = self.Omega_Z_convolved 234 else: 235 if hasattr(self, 'Omega_Z_unconvolved'): 236 o = self.Omega_Z_unconvolved 237 else: 238 o = self.Omega_Z 239 mask = numpy.logical_and(self.z >= zmin, 240 self.z < zmax) 241 return numpy.mean(o[mask])
242
243 - def fit_f_change(self, z_bin, Omega_Z_obs, 244 min_f_change=0.0, 245 max_f_change=None, 246 useConvolved=True):
247 """Fit f_change to an observed value of Omega_Z_obs in redshift bin 248 z_bin[0]--z_bin[1]. 249 """ 250 if not hasattr(self, 'original_f_change'): 251 self.original_f_change = self.f_change 252 def fitfunc(fc): 253 self.f_change = fc 254 self.calculate_Omega_Z() 255 if useConvolved: 256 self.convolve() 257 return (self.binned_Omega_Z(z_bin[0], z_bin[1], useConvolved) - 258 Omega_Z_obs)
259 260 if fitfunc(min_f_change) > Omega_Z_obs: 261 print "Warning: can't match Omega_Z with f_change > %.3g" \ 262 % (min_f_change) 263 self.f_change = min_f_change 264 return min_f_change 265 266 if max_f_change is None: 267 max_f_change = 3. * self.original_f_change 268 ff = fitfunc(max_f_change) 269 while ff < 0: 270 print " max_f_change = %.3g too small (diff = %.3g)..." \ 271 % (max_f_change, ff) 272 max_f_change = 3. * max_f_change 273 ff = fitfunc(max_f_change) 274 print " considering f_change between %.3g and %.3g" % (min_f_change, 275 max_f_change) 276 f_change_fit = scipy.optimize.minpack.bisection(fitfunc, 277 min_f_change, 278 max_f_change, 279 xtol=self.original_f_change*1e-3) 280 if useConvolved: 281 self.f_change = f_change_fit 282 else: 283 self.f_change_unconvolved = f_change_fit 284 self.Omega_Z_unconvolved = self.Omega_Z 285 return f_change_fit
286
287 - def fit_f_x_Z(self, z_bin, Omega_Z_obs, 288 min_f_x_Z=0.0, 289 max_f_x_Z=None, 290 useConvolved=True):
291 """Fit f_x_Z to an observed value of Omega_Z_obs in redshift bin 292 z_bin[0]--z_bin[1]. 293 """ 294 if not hasattr(self, 'original_f_x_Z'): 295 self.original_f_x_Z = self.f_x_Z 296 def fitfunc(fxc): 297 self.f_x_Z = fxc 298 self.calculate_Omega_Z() 299 if useConvolved: 300 self.convolve() 301 return (self.binned_Omega_Z(z_bin[0], z_bin[1], useConvolved) - 302 Omega_Z_obs)
303 304 if fitfunc(min_f_x_Z) > Omega_Z_obs: 305 print "Warning: can't match Omega_Z with f_x_Z > %.3g" \ 306 % (min_f_x_Z) 307 self.f_x_Z = min_f_x_Z 308 return min_f_x_Z 309 310 if max_f_x_Z is None: 311 max_f_x_Z = 3. * self.original_f_x_Z 312 ff = fitfunc(max_f_x_Z) 313 while ff < 0: 314 print " max_f_x_Z = %.3g too small (diff = %.3g)..." \ 315 % (max_f_x_Z, ff) 316 max_f_x_Z = 3. * max_f_x_Z 317 ff = fitfunc(max_f_x_Z) 318 print " considering f_x_Z between %.3g and %.3g" % (min_f_x_Z, 319 max_f_x_Z) 320 f_x_Z_fit = scipy.optimize.minpack.bisection(fitfunc, 321 min_f_x_Z, 322 max_f_x_Z, 323 xtol=self.original_f_x_Z*1e-3) 324 self.f_x_Z = f_x_Z_fit 325 return self.f_x_Z 326
327 -class EnrichmentHistoryCollection(Saveable):
328 - def __init__(self, ionHists, delay_kernels, f_x_Zs, 329 z_changes, f_changes, 330 lf_names=None, dl_names=None, en_names=None):
331 self.ionHists = ionHists 332 self.delay_kernels = delay_kernels 333 self.f_x_Zs = f_x_Zs 334 self.z_changes = z_changes 335 self.f_changes = f_changes 336 self.lf_names=lf_names 337 self.dl_names=dl_names 338 self.en_names=en_names 339 340 self.nLF = len(ionHists) 341 self.nDl = len(delay_kernels) 342 self.nEn = len(f_x_Zs) 343 344 EnHists = [] 345 for iLF in range(self.nLF): 346 if lf_names is not None: 347 print "Using %s." % lf_names[iLF] 348 square = [] 349 for iDl in range(self.nDl): 350 if lf_names is not None: 351 print "Using %s." % dl_names[iDl] 352 line = [] 353 for iEn in range(self.nEn): 354 if en_names is not None: 355 print "Using %s." % en_names[iEn] 356 line.append(EnrichmentHistory(ionHists[iLF], 357 f_x_Zs[iEn], 358 z_changes[iEn], 359 f_changes[iEn], 360 delay_kernels[iDl])) 361 square.append(line) 362 EnHists.append(square) 363 self.EnHists = EnHists
364
365 - def fit_f_change(self, z_bin, Omega_Z_obs, 366 min_f_change=0.0, max_f_change=None):
367 f_changes = [] 368 f_changes_unconvolved = [] 369 for iLF in range(self.nLF): 370 square = [] 371 line_unc = [] 372 for iDl in range(self.nDl): 373 line = [] 374 for iEn in range(self.nEn): 375 if iDl == 0: 376 line_unc.append(\ 377 self.EnHists[iLF][iDl][iEn].fit_f_change(z_bin, 378 Omega_Z_obs, 379 min_f_change, 380 max_f_change, 381 useConvolved=False)) 382 line.append(\ 383 self.EnHists[iLF][iDl][iEn].fit_f_change(z_bin, 384 Omega_Z_obs, 385 min_f_change, 386 max_f_change, 387 useConvolved=True)) 388 389 square.append(line) 390 f_changes.append(square) 391 f_changes_unconvolved.append(line_unc) 392 self.f_changes = numpy.array(f_changes) 393 self.f_changes_unconvolved = numpy.array(f_changes_unconvolved) 394 return f_changes, f_changes_unconvolved
395
396 - def fit_f_x_Z(self, z_bin, Omega_Z_obs, 397 min_f_x_Z=0.0, max_f_x_Z=None, 398 useConvolved=True):
399 f_x_Zs = [] 400 for iLF in range(self.nLF): 401 square = [] 402 for iDl in range(self.nDl): 403 line = [] 404 for iEn in range(self.nEn): 405 line.append(\ 406 self.EnHists[iLF][iDl][iEn].fit_f_x_Z(z_bin, 407 Omega_Z_obs, 408 min_f_x_Z, 409 max_f_x_Z, 410 useConvolved)) 411 square.append(line) 412 f_x_Zs.append(square) 413 self.f_x_Zs = f_x_Zs 414 return f_x_Zs
415
416 - def convolve(self):
417 Omega_Zs_convolved = [] 418 for iLF in range(self.nLF): 419 square = [] 420 for iDl in range(self.nDl): 421 line = [] 422 for iEn in range(self.nEn): 423 line.append(\ 424 self.EnHists[iLF][iDl][iEn].convolve()) 425 square.append(line) 426 Omega_Zs_convolved.append(square) 427 self.Omega_Zs_convolved = Omega_Zs_convolved 428 return self.Omega_Zs_convolved
429