示例#1
0
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
示例#3
0
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)