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   
 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   
 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           
 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           
 96          self.NdotTot = self.lfh.iPhotonRateDensity_z(z, lf_maglim) 
 97   
 98           
 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   
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       
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           
144           
145          tau_z0 = cr.optical_depth_instant(self.z[-1], **self.cosmo) 
146   
147           
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   
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   
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   
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