def compute_fs(MEDEA_interp, elec_spec, phot_spec, x, dE_dVdt_inj, dt, highengdep, cmbloss=0, method='no_He', separate_higheng=True): """ Compute f(z) fractions for continuum photons, photoexcitation of HI, and photoionization of HI, HeI, HeII Given a spectrum of deposited electrons and photons, resolve their energy into H ionization, and ionization, H excitation, heating, and continuum photons in that order. Parameters ---------- phot_spec : Spectrum object spectrum of photons. Assumed to be in dNdE mode. spec.totN() should return number *per baryon*. elec_spec : Spectrum object spectrum of electrons. Assumed to be in dNdE mode. spec.totN() should return number *per baryon*. x : list of floats number of (HI, HeI, HeII) divided by nH at redshift photon_spectrum.rs dE_dVdt_inj : float DM energy injection rate, dE/dVdt injected. This is for unclustered DM (i.e. without structure formation). dt : float time in seconds over which these spectra were deposited. highengdep : list of floats total amount of energy deposited by high energy particles into {H_ionization, H_excitation, heating, continuum} per baryon per time, in that order. cmbloss : float Total amount of energy in upscattered photons that came from the CMB, per baryon per time, (1/n_B)dE/dVdt. Default is zero. method : {'no_He', 'He_recomb', 'He'} Method for evaluating helium ionization. * *'no_He'* -- all ionization assigned to hydrogen; * *'He_recomb'* -- all photoionized helium atoms recombine; and * *'He'* -- all photoionized helium atoms do not recombine. separate_higheng : bool, optional If True, returns separate high energy deposition. Returns ------- ndarray or tuple of ndarray f_c(z) for z within spec.rs +/- dt/2 The order of the channels is {H Ionization, He Ionization, H Excitation, Heating and Continuum} Notes ----- The CMB component hasn't been subtracted from the continuum photons yet Think about the exceptions that should be thrown (elec_spec.rs should equal phot_spec.rs) """ # np.array syntax below needed so that a fresh copy of eng and N are passed to the # constructor, instead of simply a reference. if method == 'no_He': ion_bounds = spectools.get_bounds_between( phot_spec.eng, phys.rydberg ) ion_engs = np.exp((np.log(ion_bounds[1:])+np.log(ion_bounds[:-1]))/2) ionized_elec = Spectrum( ion_engs, phot_spec.totN(bound_type="eng", bound_arr=ion_bounds), rs=phot_spec.rs, spec_type='N' ) new_eng = ion_engs - phys.rydberg ionized_elec.shift_eng(new_eng) # rebin so that ionized_elec may be added to elec_spec ionized_elec.rebin(elec_spec.eng) tmp_elec_spec = Spectrum( np.array(elec_spec.eng), np.array(elec_spec.N), rs=elec_spec.rs, spec_type='N' ) tmp_elec_spec.N += ionized_elec.N f_phot = lowE_photons.compute_fs( phot_spec, x, dE_dVdt_inj, dt, 'old' ) #print(phot_spec.rs, f_phot[0], phot_spec.toteng(), cmbloss, dE_dVdt_inj) f_elec = lowE_electrons.compute_fs( MEDEA_interp, tmp_elec_spec, 1-x[0], dE_dVdt_inj, dt ) # print('photons:', f_phot[2], f_phot[3]+f_phot[4], f_phot[1], 0, f_phot[0]) # print('electrons:', f_elec[2], f_elec[3], f_elec[1], f_elec[4], f_elec[0]) # f_low is {H ion, He ion, Lya Excitation, Heating, Continuum} f_low = np.array([ f_phot[2]+f_elec[2], f_phot[3]+f_phot[4]+f_elec[3], f_phot[1]+f_elec[1], f_elec[4], f_phot[0]+f_elec[0] - cmbloss*phys.nB*phot_spec.rs**3 / dE_dVdt_inj ]) f_high = np.array([ highengdep[0], 0, highengdep[1], highengdep[2], highengdep[3] ]) * phys.nB * phot_spec.rs**3 / dE_dVdt_inj if separate_higheng: return (f_low, f_high) else: return f_low + f_high elif method == 'He': # Neglect HeII photoionization. Photoionization rates. n = phys.nH*phot_spec.rs**3*x rates = np.array([ n[i]*phys.photo_ion_xsec(phot_spec.eng, chan) for i,chan in enumerate(['HI', 'HeI']) ]) norm_prob = np.sum(rates, axis=0) # Probability of photoionizing HI vs. HeI. prob = np.array([ np.divide( rate, norm_prob, out = np.zeros_like(phot_spec.eng), where=(phot_spec.eng > phys.rydberg) ) for rate in rates ]) # Spectra weighted by prob. phot_spec_HI = phot_spec*prob[0] phot_spec_HeI = phot_spec*prob[1] # Bin boundaries, including the lowest (13.6, 24.6) eV bin. ion_bounds_HI = spectools.get_bounds_between( phot_spec.eng, phys.rydberg ) ion_bounds_HeI = spectools.get_bounds_between( phot_spec.eng, phys.He_ion_eng ) # Bin centers. ion_engs_HI = np.exp( (np.log(ion_bounds_HI[1:]) + np.log(ion_bounds_HI[:-1]))/2 ) ion_engs_HeI = np.exp( (np.log(ion_bounds_HeI[1:]) + np.log(ion_bounds_HeI[:-1]))/2 ) # Spectrum object containing secondary electron # from ionization. ionized_elec_HI = Spectrum( ion_engs_HI, phot_spec_HI.totN(bound_type='eng', bound_arr=ion_bounds_HI), rs=phot_spec.rs, spec_type='N' ) ionized_elec_HeI = Spectrum( ion_engs_HeI, phot_spec_HeI.totN(bound_type='eng', bound_arr=ion_bounds_HeI), rs=phot_spec.rs, spec_type='N' ) # electron energy (photon energy - ionizing potential). new_eng_HI = ion_engs_HI - phys.rydberg new_eng_HeI = ion_engs_HeI - phys.He_ion_eng # change the Spectrum abscissa to the correct electron energy. ionized_elec_HI.shift_eng(new_eng_HI) ionized_elec_HeI.shift_eng(new_eng_HeI) # rebin so that ionized_elec may be added to elec_spec. ionized_elec_HI.rebin(elec_spec.eng) ionized_elec_HeI.rebin(elec_spec.eng) tmp_elec_spec = Spectrum( np.array(elec_spec.eng), np.array(elec_spec.N), rs=elec_spec.rs, spec_type='N' ) tmp_elec_spec.N += (ionized_elec_HI.N + ionized_elec_HeI.N) f_phot = lowE_photons.compute_fs( phot_spec, x, dE_dVdt_inj, dt, 'helium' ) f_elec = lowE_electrons.compute_fs( MEDEA_interp, tmp_elec_spec, 1-x[0], dE_dVdt_inj, dt ) # f_low is {H ion, He ion, Lya Excitation, Heating, Continuum} f_low = np.array([ f_phot[2]+f_elec[2], f_phot[3]+f_phot[4]+f_elec[3], f_phot[1]+f_elec[1], f_elec[4], f_phot[0]+f_elec[0] - cmbloss*phys.nB*phot_spec.rs**3 / dE_dVdt_inj ]) f_high = np.array([ highengdep[0], 0, highengdep[1], highengdep[2], highengdep[3] ]) * phys.nB * phot_spec.rs**3 / dE_dVdt_inj if separate_higheng: return (f_low, f_high) else: return f_low + f_high elif method == 'He_recomb': # Neglect HeII photoionization. Photoionization rates. n = phys.nH*phot_spec.rs**3*x rates = np.array([ n[i]*phys.photo_ion_xsec(phot_spec.eng, chan) for i,chan in enumerate(['HI', 'HeI']) ]) norm_prob = np.sum(rates, axis=0) # Probability of photoionizing HI vs. HeI. prob = np.array([ np.divide( rate, norm_prob, out = np.zeros_like(phot_spec.eng), where=(phot_spec.eng > phys.rydberg) ) for rate in rates ]) # Spectra weighted by prob. phot_spec_HI = phot_spec*prob[0] phot_spec_HeI = phot_spec*prob[1] # Bin boundaries, including the lowest (13.6, 24.6) eV bin. ion_bounds_HI = spectools.get_bounds_between( phot_spec.eng, phys.rydberg ) ion_bounds_HeI = spectools.get_bounds_between( phot_spec.eng, phys.He_ion_eng ) # Bin centers. ion_engs_HI = np.exp( (np.log(ion_bounds_HI[1:]) + np.log(ion_bounds_HI[:-1]))/2 ) ion_engs_HeI = np.exp( (np.log(ion_bounds_HeI[1:]) + np.log(ion_bounds_HeI[:-1]))/2 ) # Spectrum object containing secondary electron # from ionization. ionized_elec_HI = Spectrum( ion_engs_HI, phot_spec_HI.totN(bound_type='eng', bound_arr=ion_bounds_HI), rs=phot_spec.rs, spec_type='N' ) ionized_elec_HeI = Spectrum( ion_engs_HeI, phot_spec_HeI.totN(bound_type='eng', bound_arr=ion_bounds_HeI), rs=phot_spec.rs, spec_type='N' ) # electron energy (photon energy - ionizing potential). new_eng_HI = ion_engs_HI - phys.rydberg new_eng_HeI = ion_engs_HeI - phys.He_ion_eng # change the Spectrum abscissa to the correct electron energy. ionized_elec_HI.shift_eng(new_eng_HI) ionized_elec_HeI.shift_eng(new_eng_HeI) # rebin so that ionized_elec may be added to elec_spec. ionized_elec_HI.rebin(elec_spec.eng) ionized_elec_HeI.rebin(elec_spec.eng) tmp_elec_spec = Spectrum( np.array(elec_spec.eng), np.array(elec_spec.N), rs=elec_spec.rs, spec_type='N' ) tmp_elec_spec.N += (ionized_elec_HI.N + ionized_elec_HeI.N) # Every ionized helium recombines to produce an 11 eV electron. recomb_elec = spectools.rebin_N_arr( np.array([phot_spec_HeI.totN()]), np.array([phys.He_ion_eng - phys.rydberg]), elec_spec.eng ) tmp_elec_spec.N += recomb_elec.N # Every photon that photoionizes goes into hydrogen ionization now. # We can just use 'old' to do this computation. f_phot = lowE_photons.compute_fs( phot_spec, x, dE_dVdt_inj, dt, 'old' ) f_elec = lowE_electrons.compute_fs( MEDEA_interp, tmp_elec_spec, 1-x[0], dE_dVdt_inj, dt ) # f_low is {H ion, He ion, Lya Excitation, Heating, Continuum} f_low = np.array([ f_phot[2]+f_elec[2], f_phot[3]+f_phot[4]+f_elec[3], f_phot[1]+f_elec[1], f_elec[4], f_phot[0]+f_elec[0] - cmbloss*phys.nB*phot_spec.rs**3 / dE_dVdt_inj ]) f_high = np.array([ highengdep[0], 0, highengdep[1], highengdep[2], highengdep[3] ]) * phys.nB * phot_spec.rs**3 / dE_dVdt_inj if separate_higheng: return (f_low, f_high) else: return f_low + f_high else: raise TypeError('invalid method.')
def compute_dep_inj_ionization_ratio(photon_spectrum, n, tot_inj, method='old'): """ Given a spectrum of deposited photons, resolve its energy into continuum photons, HI excitation, and HI, HeI, HeII ionization in that order. The spectrum must provide the energy density of photons per unit time within each bin, not just the total energy within each bin. Q: can photons heat the IGM? Should this method keep track of the fact that x_e, xHII, etc. are changing? Parameters ---------- photon_spectrum : Spectrum object spectrum of photons n : list of floats density of (HI, HeI, HeII). tot_inj : float total energy injected by DM method : {'old','ion','new'} 'old': All photons >= 13.6eV ionize hydrogen, within [10.2, 13.6)eV excite hydrogen, < 10.2eV are labelled continuum. 'ion': Same as 'old', but now photons >= 13.6 can ionize HeI and HeII also. 'new': Same as 'ion', but now [10.2, 13.6)eV photons treated more carefully Returns ------- tuple of floats Ratio of deposited energy to a given channel over energy deposited by DM. The order of the channels is HI excitation and HI, HeI, HeII ionization """ continuum, excite_HI, f_HI, f_HeI, f_HeII = 0, 0, 0, 0, 0 continuum = photon_spectrum.toteng(bound_type='eng', bound_arr=np.array([ photon_spectrum.eng[0], phys.lya_eng ]))[0] / tot_inj ion_index = np.searchsorted(photon_spectrum.eng, phys.rydberg) if (method != 'new'): excite_HI = photon_spectrum.toteng( bound_type='eng', bound_arr=np.array([phys.lya_eng, phys.rydberg ]))[0] / tot_inj if (method == 'old'): f_HI = photon_spectrum.toteng(bound_type='eng', bound_arr=np.array([ phys.rydberg, photon_spectrum.eng[-1] ]))[0] / tot_inj elif (method == 'ion'): # probability of being absorbed within time step dt in channel a = \sigma(E)_a n_a c*dt # First convert from probability of being absorbed in channel 'a' to conditional probability given that these are deposited photons # TODO: could be improved to include the missing [13.6,ion_bin] ionHI, ionHeI, ionHeII = [ phys.photo_ion_xsec(photon_spectrum.eng[ion_index:], channel) * n[i] for i, channel in enumerate(['H0', 'He0', 'He1']) ] totList = ionHI + ionHeI + ionHeII ion_bin = spectools.get_bin_bound(photon_spectrum.eng)[ion_index] print(ion_bin) print((sum(photon_spectrum.eng[ion_index:] * photon_spectrum.N[ion_index:]) + photon_spectrum.toteng( bound_type='eng', bound_arr=np.array( [photon_spectrum.eng[0], phys.rydberg]))[0] + photon_spectrum.toteng( bound_type='eng', bound_arr=np.array([phys.rydberg, ion_bin]))[0]) / tot_inj) temp = np.array(photon_spectrum.eng[ion_index:]) np.insert(temp, 0, phys.rydberg) print((photon_spectrum.toteng( bound_type='eng', bound_arr=np.array([photon_spectrum.eng[0], phys.rydberg]))[0] + sum(photon_spectrum.toteng(bound_type='eng', bound_arr=temp))) / tot_inj) print( sum( photon_spectrum.toteng(bound_type='eng', bound_arr=photon_spectrum.eng)) / tot_inj) f_HI, f_HeI, f_HeII = [ sum(photon_spectrum.eng[ion_index:] * photon_spectrum.N[ion_index:] * llist / totList) / tot_inj for llist in [ionHI, ionHeI, ionHeII] ] #f_HI, f_HeI, f_HeII = [sum(photon_spectrum.toteng(bound_type='eng',bound_arr=np.arange(ion_index-1,len(photon_spectrum.eng)))*llist/totList)/tot_inj for llist in [ionHI, ionHeI, ionHeII]] #There's an extra piece of energy between 13.6 amd the energy at ion_index #print(photon_spectrum.toteng(bound_type='eng', bound_arr=np.array([phys.rydberg,photon_spectrum.eng[ion_index]]))[0]/tot_inj) #f_HI = f_HI + photon_spectrum.toteng(bound_type='eng', bound_arr=np.array([phys.rydberg,photon_spectrum.eng[ion_index]]))[0]/tot_inj return continuum, excite_HI, f_HI, f_HeI, f_HeII
def getf_ion(photspec, norm_fac, n, method, cross_check=False): # The bin number containing 10.2eV lya_index = spectools.get_indx(photspec.eng, phys.lya_eng) # The bin number containing 13.6eV ryd_index = spectools.get_indx(photspec.eng, phys.rydberg) if method == 'old': # All photons above 13.6 eV deposit their 13.6eV into HI ionization #!!! The factor of 10 is probably unecessary if not cross_check: tot_ion_eng = phys.rydberg * photspec.totN( bound_type='eng', bound_arr=np.array([phys.rydberg, 10 * photspec.eng[-1]]))[0] else: tot_ion_eng = phys.rydberg * np.sum( photspec.N[photspec.eng > 13.6]) f_HI = tot_ion_eng * norm_fac f_HeI = 0 f_HeII = 0 elif method == 'helium': # Neglect HeII photoionization # !!! Not utilizing partial binning! rates = np.array([ n[i] * phys.photo_ion_xsec(photspec.eng, chan) for i, chan in enumerate(['HI', 'HeI']) ]) norm_prob = np.sum(rates, axis=0) prob = np.array([ np.divide(rate, norm_prob, out=np.zeros_like(photspec.eng), where=(photspec.eng > phys.rydberg)) for rate in rates ]) ion_eng_H = phys.rydberg * np.sum(prob[0] * photspec.N) ion_eng_He = phys.He_ion_eng * np.sum(prob[1] * photspec.N) f_HI = ion_eng_H * norm_fac f_HeI = ion_eng_He * norm_fac f_HeII = 0 else: # HL: Not sure if this code is right....... # Photons may also deposit their energy into HeI and HeII single ionization # Bin boundaries of photon spectrum capable of photoionization, and number of photons in those bounds. ion_bounds = spectools.get_bounds_between(photspec.eng, phys.rydberg) ion_Ns = photspec.totN(bound_type='eng', bound_arr=ion_bounds) # Probability of being absorbed within time step dt in channel a is P_a = \sigma(E)_a n_a c*dt ionHI, ionHeI, ionHeII = [ phys.photo_ion_xsec(photspec.eng[ryd_index:], channel) * n[i] for i, channel in enumerate(['HI', 'HeI', 'HeII']) ] # The first energy might be less than 13.6, meaning no photo-ionization. # The photons in this box are hopefully all between 13.6 and 24.6, so they can only ionize H if photspec.eng[ryd_index] < phys.rydberg: ionHI[0] = 1 # Relative likelihood of photoionization of HI is then P_HI/sum(P_a) totList = ionHI + ionHeI + ionHeII + 1e-12 ionHI, ionHeI, ionHeII = [ llist / totList for llist in [ionHI, ionHeI, ionHeII] ] f_HI, f_HeI, f_HeII = [ np.sum(ion_Ns * llist * norm_fac) for llist in [ phys.rydberg * ionHI, phys.He_ion_eng * ionHeI, 4 * phys.rydberg * ionHeII ] ] return (f_HI, f_HeI, f_HeII)