Beispiel #1
0
def getf_excitation(photspec, norm_fac, dt, xe, n, method, cross_check=False):
    if ((method == 'old') or (method == 'helium') or (method == 'ion')):
        # All photons between 11.2eV and 13.6eV are deposited into excitation
        # partial binning
        if not cross_check:
            tot_excite_eng = (photspec.toteng(
                bound_type='eng',
                bound_arr=np.array([phys.lya_eng, phys.rydberg]))[0])
        else:
            tot_excite_eng = np.dot(
                photspec.N[(photspec.eng >= 10.2) & (photspec.eng <= 13.6)],
                photspec.eng[(photspec.eng >= 10.2) & (photspec.eng <= 13.6)])
        f_excite_HI = tot_excite_eng * norm_fac
    else:
        # Only photons in the 10.2eV bin participate in 1s->2p excitation.
        # 1s->2s transition handled more carefully.

        # Convenient variables
        kappa = kappa_DM(photspec, xe)

        # Added this line since rate_2p1s_times_x1s function was removed.
        rate_2p1s_times_x1s = (8 * np.pi * phys.hubble(photspec.rs) /
                               (3 * (phys.nH * photspec.rs**3 *
                                     (phys.c / phys.lya_freq)**3)))

        f_excite_HI = (
            kappa *
            (3 * rate_2p1s_times_x1s * phys.nH + phys.width_2s1s_H * n[0]) *
            phys.lya_eng * (norm_fac / phys.nB / photspec.rs**3 * dt))
    return f_excite_HI
Beispiel #2
0
def Gam_pi(x_be, T_DM, rs, alphaD, m_be, m_bp, xi):
    B_D = get_BD(alphaD, m_be, m_bp)
    T_D = xi * phys.TCMB(rs)
    m_D = m_be + m_bp - B_D
    mu_D = m_be * m_bp / m_D
    n_D = phys.rho_DM / m_D * rs**3
    denom = (3 / 2 * T_DM * n_D * (1 + x_be))

    # Calculate x_2s using Hyrec's steady stateassumption #
    Lya_D = 3 / 4 * B_D
    dark_sobolev = 2**7 * alphaD**3 * B_D * n_D * phys.c**3 * (1 - x_be) / (
        3**7 * 2 * np.pi * phys.hbar * phys.hubble(rs) * Lya_D)
    R_D = 2**9 * alphaD**3 * B_D / 3**8 * (
        1 - np.exp(-dark_sobolev)) / dark_sobolev

    Lam = (alphaD / phys.alpha)**6 * (B_D / phys.rydberg) * phys.width_2s1s_H
    alpha_B = dark_alpha_recomb(T_DM, alphaD, m_be, m_bp, xi)
    beta_e = dark_beta_ion(T_D, alphaD, m_be, m_bp, xi)
    x_2 = (n_D * x_be**2 * alpha_B + (3 * R_D + Lam) *
           (1 - x_be) * np.exp(-B_D / T_D)) / (beta_e + 3 / 4 * R_D +
                                               1 / 4 * Lam)
    x_2s = 1 / 4 * x_2

    pre = alphaD**3 * T_D**2 / (3 * np.pi) * np.exp(-B_D / (4 * T_D))
    convert = phys.hbar
    return pre * x_2s * n_D * F_pi(T_D / B_D) / denom * convert
Beispiel #3
0
def kappa_DM(photspec, xe):
    """ Compute kappa_DM of the modified tla.

    Parameters
    ----------
    photspec : Spectrum object
        spectrum of photons. Assumed to be in dNdE mode. spec.toteng() should return Energy per baryon.

    Returns
    -------
    kappa_DM : float
        The added photoionization rate due to products of DM.
    """
    eng = photspec.eng
    rs = photspec.rs

    rate_2p1s_times_x1s = (8 * np.pi * phys.hubble(rs) /
                           (3 * (phys.nH * rs**3 *
                                 (phys.c / phys.lya_freq)**3)))
    x1s_times_R_Lya = rate_2p1s_times_x1s
    Lambda = phys.width_2s1s_H

    # The bin number containing 10.2eV
    lya_index = spectools.get_indx(eng, phys.lya_eng)

    # The bins between 10.2eV and 13.6eV
    exc_bounds = spectools.get_bounds_between(eng, phys.lya_eng, phys.rydberg)

    # Effect on 2p state due to DM products
    kappa_2p = (photspec.dNdE[lya_index] * phys.nB * rs**3 * np.pi**2 *
                (phys.hbar * phys.c)**3 / phys.lya_eng**2)

    # Effect on 2s state
    kappa_2s = get_kappa_2s(photspec)

    return (kappa_2p * 3 * x1s_times_R_Lya / 4 + kappa_2s *
            (1 - xe) * Lambda / 4) / (3 * x1s_times_R_Lya / 4 +
                                      (1 - xe) * Lambda / 4)
Beispiel #4
0
def dark_peebles_C(x_be, rs, alphaD, m_be, m_bp, xi):
    B_D = get_BD(alphaD, m_be, m_bp)
    m_D = m_be + m_bp - B_D
    n_D = phys.rho_DM / m_D * rs**3
    #n_D = phys.nH*rs**3
    T_D = xi * phys.TCMB(rs)

    beta = dark_beta_ion(T_D, alphaD, m_be, m_bp, xi)

    Lya_D = 3 / 4 * B_D

    dark_sobolev = 2**7 * alphaD**3 * B_D * n_D * phys.c**3 * (1 - x_be) / (
        3**7 * 2 * np.pi * phys.hbar * phys.hubble(rs) * Lya_D)

    R_D_fac = 3 / 4 * 2**9 * alphaD**3 * B_D / 3**8 * (
        1 - np.exp(-dark_sobolev)) / dark_sobolev

    Lam_2s_1s_fac = 1 / 4 * (alphaD / phys.alpha)**6 * (
        B_D / phys.rydberg) * phys.width_2s1s_H

    ssum = R_D_fac + Lam_2s_1s_fac

    return ssum / (ssum + beta)
Beispiel #5
0
def get_normalized_spec(spec, dE_dVdt, rs):
    """
    Normalizes the spectrum to per baryon per dlnz, given dE/(dV dt). 

    Parameters
    ----------
    spec : Spectrum
        Input spectrum to be normalized. 
    dE_dVdt : float
        The injection dE/(dV dt) in eV cm^-3 s^-1. 
    rs : float
        The redshift (1+z). 

    Returns
    -------
    Spectrum
        The normalized spectrum (per baryon per dlnz). 

    """

    dE_dNBdlnz = dE_dVdt / (phys.nB * rs**3) / phys.hubble(rs)

    return spec / spec.toteng() * dEdNBdlnz
Beispiel #6
0
 def norm_fac(rs):
     # Normalization to convert from per injection event to
     # per baryon per dlnz step.
     return rate_func_N(rs) * (dlnz * coarsen_factor / phys.hubble(rs) /
                               (phys.nB * rs**3))
Beispiel #7
0
def evolve(in_spec_elec=None,
           in_spec_phot=None,
           rate_func_N=None,
           rate_func_eng=None,
           DM_process=None,
           mDM=None,
           sigmav=None,
           lifetime=None,
           primary=None,
           struct_boost=None,
           start_rs=None,
           end_rs=4,
           helium_TLA=False,
           reion_switch=False,
           reion_rs=None,
           photoion_rate_func=None,
           photoheat_rate_func=None,
           xe_reion_func=None,
           init_cond=None,
           coarsen_factor=1,
           backreaction=True,
           compute_fs_method='no_He',
           mxstep=1000,
           rtol=1e-4,
           use_tqdm=True,
           cross_check=False):
    """
    Main function computing histories and spectra. 

    Parameters
    -----------
    in_spec_elec : :class:`.Spectrum`, optional
        Spectrum per injection event into electrons. *in_spec_elec.rs*
        of the :class:`.Spectrum` must be the initial redshift. 
    in_spec_phot : :class:`.Spectrum`, optional
        Spectrum per injection event into photons. *in_spec_phot.rs* 
        of the :class:`.Spectrum` must be the initial redshift. 
    rate_func_N : function, optional
        Function returning number of injection events per volume per time, with redshift :math:`(1+z)` as an input.  
    rate_func_eng : function, optional
        Function returning energy injected per volume per time, with redshift :math:`(1+z)` as an input. 
    DM_process : {'swave', 'decay'}, optional
        Dark matter process to use. 
    sigmav : float, optional
        Thermally averaged cross section for dark matter annihilation. 
    lifetime : float, optional
        Decay lifetime for dark matter decay.
    primary : string, optional
        Primary channel of annihilation/decay. See :func:`.get_pppc_spec` for complete list. Use *'elec_delta'* or *'phot_delta'* for delta function injections of a pair of photons/an electron-positron pair. 
    struct_boost : function, optional
        Energy injection boost factor due to structure formation.
    start_rs : float, optional
        Starting redshift :math:`(1+z)` to evolve from. Default is :math:`(1+z)` = 3000. Specify only for use with *DM_process*. Otherwise, initialize *in_spec_elec.rs* and/or *in_spec_phot.rs* directly. 
    end_rs : float, optional
        Final redshift :math:`(1+z)` to evolve to. Default is 1+z = 4. 
    reion_switch : bool
        Reionization model included if *True*, default is *False*. 
    helium_TLA : bool
        If *True*, the TLA is solved with helium. Default is *False*.
    reion_rs : float, optional
        Redshift :math:`(1+z)` at which reionization effects turn on. 
    photoion_rate_func : tuple of functions, optional
        Functions take redshift :math:`1+z` as input, return the photoionization rate in s\ :sup:`-1` of HI, HeI and HeII respectively. If not specified, defaults to :func:`.photoion_rate`.
    photoheat_rate_func : tuple of functions, optional
        Functions take redshift :math:`1+z` as input, return the photoheating rate in s\ :sup:`-1` of HI, HeI and HeII respectively. If not specified, defaults to :func:`.photoheat_rate`.
    xe_reion_func : function, optional
        Specifies a fixed ionization history after reion_rs.
    init_cond : tuple of floats
        Specifies the initial (xH, xHe, Tm). Defaults to :func:`.Tm_std`, :func:`.xHII_std` and :func:`.xHeII_std` at the *start_rs*. 
    coarsen_factor : int
        Coarsening to apply to the transfer function matrix. Default is 1. 
    backreaction : bool
        If *False*, uses the baseline TLA solution to calculate :math:`f_c(z)`. Default is True.
    compute_fs_method : {'no_He', 'He_recomb', 'He'}

    mxstep : int, optional
        The maximum number of steps allowed for each integration point. See *scipy.integrate.odeint()* for more information. Default is *1000*. 
    rtol : float, optional
        The relative error of the solution. See *scipy.integrate.odeint()* for more information. Default is *1e-4*.
    use_tqdm : bool, optional
        Uses tqdm if *True*. Default is *True*. 
    cross_check : bool, optional
        If *True*, compare against 1604.02457 by using original MEDEA files, turning off partial binning, etc. Default is *False*.

    Examples
    --------

    1. *Dark matter annihilation* -- dark matter mass of 50 GeV, annihilation cross section :math:`2 \\times 10^{-26}` cm\ :sup:`3` s\ :sup:`-1`, annihilating to :math:`b \\bar{b}`, solved without backreaction, a coarsening factor of 32 and the default structure formation boost: ::

        import darkhistory.physics as phys

        out = evolve(
            DM_process='swave', mDM=50e9, sigmav=2e-26, 
            primary='b', start_rs=3000., 
            backreaction=False,
            struct_boost=phys.struct_boost_func()
        )

    2. *Dark matter decay* -- dark matter mass of 100 GeV, decay lifetime :math:`3 \\times 10^{25}` s, decaying to a pair of :math:`e^+e^-`, solved with backreaction, a coarsening factor of 16: ::

        out = evolve(
            DM_process='decay', mDM=1e8, lifetime=3e25,
            primary='elec_delta', start_rs=3000.,
            backreaction=True
        ) 

    See Also
    ---------
    :func:`.get_pppc_spec`

    :func:`.struct_boost_func`

    :func:`.photoion_rate`, :func:`.photoheat_rate`

    :func:`.Tm_std`, :func:`.xHII_std` and :func:`.xHeII_std`


    """

    #########################################################################
    #########################################################################
    # Input                                                                 #
    #########################################################################
    #########################################################################

    #####################################
    # Initialization for DM_process     #
    #####################################

    # Load data.
    binning = load_data('binning')
    photeng = binning['phot']
    eleceng = binning['elec']

    dep_tf_data = load_data('dep_tf')

    highengphot_tf_interp = dep_tf_data['highengphot']
    lowengphot_tf_interp = dep_tf_data['lowengphot']
    lowengelec_tf_interp = dep_tf_data['lowengelec']
    highengdep_interp = dep_tf_data['highengdep']

    ics_tf_data = load_data('ics_tf')

    ics_thomson_ref_tf = ics_tf_data['thomson']
    ics_rel_ref_tf = ics_tf_data['rel']
    engloss_ref_tf = ics_tf_data['engloss']

    # Handle the case where a DM process is specified.
    if DM_process == 'swave':
        if sigmav is None or start_rs is None:
            raise ValueError('sigmav and start_rs must be specified.')

        # Get input spectra from PPPC.
        in_spec_elec = pppc.get_pppc_spec(mDM, eleceng, primary, 'elec')
        in_spec_phot = pppc.get_pppc_spec(mDM, photeng, primary, 'phot')
        # Initialize the input spectrum redshift.
        in_spec_elec.rs = start_rs
        in_spec_phot.rs = start_rs
        # Convert to type 'N'.
        in_spec_elec.switch_spec_type('N')
        in_spec_phot.switch_spec_type('N')

        # If struct_boost is none, just set to 1.
        if struct_boost is None:

            def struct_boost(rs):
                return 1.

        # Define the rate functions.
        def rate_func_N(rs):
            return (phys.inj_rate('swave', rs, mDM=mDM, sigmav=sigmav) *
                    struct_boost(rs) / (2 * mDM))

        def rate_func_eng(rs):
            return (phys.inj_rate('swave', rs, mDM=mDM, sigmav=sigmav) *
                    struct_boost(rs))

    if DM_process == 'decay':
        if lifetime is None or start_rs is None:
            raise ValueError('lifetime and start_rs must be specified.')

        # The decay rate is insensitive to structure formation
        def struct_boost(rs):
            return 1

        # Get spectra from PPPC.
        in_spec_elec = pppc.get_pppc_spec(mDM,
                                          eleceng,
                                          primary,
                                          'elec',
                                          decay=True)
        in_spec_phot = pppc.get_pppc_spec(mDM,
                                          photeng,
                                          primary,
                                          'phot',
                                          decay=True)

        # Initialize the input spectrum redshift.
        in_spec_elec.rs = start_rs
        in_spec_phot.rs = start_rs
        # Convert to type 'N'.
        in_spec_elec.switch_spec_type('N')
        in_spec_phot.switch_spec_type('N')

        # Define the rate functions.
        def rate_func_N(rs):
            return (phys.inj_rate('decay', rs, mDM=mDM, lifetime=lifetime) /
                    mDM)

        def rate_func_eng(rs):
            return phys.inj_rate('decay', rs, mDM=mDM, lifetime=lifetime)

    #####################################
    # Input Checks                      #
    #####################################

    if (not np.array_equal(in_spec_elec.eng, eleceng)
            or not np.array_equal(in_spec_phot.eng, photeng)):
        raise ValueError(
            'in_spec_elec and in_spec_phot must use config.photeng and config.eleceng respectively as abscissa.'
        )

    if (highengphot_tf_interp.dlnz != lowengphot_tf_interp.dlnz
            or highengphot_tf_interp.dlnz != lowengelec_tf_interp.dlnz
            or lowengphot_tf_interp.dlnz != lowengelec_tf_interp.dlnz):
        raise ValueError(
            'TransferFuncInterp objects must all have the same dlnz.')

    if in_spec_elec.rs != in_spec_phot.rs:
        raise ValueError('Input spectra must have the same rs.')

    if cross_check:
        print(
            'cross_check has been set to True -- No longer using all MEDEA files and no longer using partial-binning.'
        )

    #####################################
    # Initialization                    #
    #####################################

    # Initialize start_rs for arbitrary injection.
    start_rs = in_spec_elec.rs

    # Initialize the initial x and Tm.
    if init_cond is None:
        # Default to baseline
        xH_init = phys.xHII_std(start_rs)
        xHe_init = phys.xHeII_std(start_rs)
        Tm_init = phys.Tm_std(start_rs)
    else:
        # User-specified.
        xH_init = init_cond[0]
        xHe_init = init_cond[1]
        Tm_init = init_cond[2]

    # Initialize redshift/timestep related quantities.

    # Default step in the transfer function. Note highengphot_tf_interp.dlnz
    # contains 3 different regimes, and we start with the first.
    dlnz = highengphot_tf_interp.dlnz[-1]

    # The current redshift.
    rs = start_rs

    # The timestep between evaluations of transfer functions, including
    # coarsening.
    dt = dlnz * coarsen_factor / phys.hubble(rs)

    # tqdm set-up.
    if use_tqdm:
        from tqdm import tqdm_notebook as tqdm
        pbar = tqdm(total=np.ceil((np.log(rs) - np.log(end_rs)) / dlnz /
                                  coarsen_factor))

    def norm_fac(rs):
        # Normalization to convert from per injection event to
        # per baryon per dlnz step.
        return rate_func_N(rs) * (dlnz * coarsen_factor / phys.hubble(rs) /
                                  (phys.nB * rs**3))

    def rate_func_eng_unclustered(rs):
        # The rate excluding structure formation for s-wave annihilation.
        # This is the correct normalization for f_c(z).
        if struct_boost is not None:
            return rate_func_eng(rs) / struct_boost(rs)
        else:
            return rate_func_eng(rs)

    # If there are no electrons, we get a speed up by ignoring them.
    elec_processes = False
    if in_spec_elec.totN() > 0:
        elec_processes = True

    if elec_processes:

        #####################################
        # High-Energy Electrons             #
        #####################################

        # Get the data necessary to compute the electron cooling results.
        # coll_ion_sec_elec_specs is \bar{N} for collisional ionization,
        # and coll_exc_sec_elec_specs \bar{N} for collisional excitation.
        # Heating and others are evaluated in get_elec_cooling_tf
        # itself.

        # Contains information that makes converting an energy loss spectrum
        # to a scattered electron spectrum fast.
        (coll_ion_sec_elec_specs, coll_exc_sec_elec_specs,
         ics_engloss_data) = get_elec_cooling_data(eleceng, photeng)

    #########################################################################
    #########################################################################
    # Pre-Loop Preliminaries                                                #
    #########################################################################
    #########################################################################

    # Initialize the arrays that will contain x and Tm results.
    x_arr = np.array([[xH_init, xHe_init]])
    Tm_arr = np.array([Tm_init])

    # Initialize Spectra objects to contain all of the output spectra.

    out_highengphot_specs = Spectra([], spec_type='N')
    out_lowengphot_specs = Spectra([], spec_type='N')
    out_lowengelec_specs = Spectra([], spec_type='N')

    # Define these methods for speed.
    append_highengphot_spec = out_highengphot_specs.append
    append_lowengphot_spec = out_lowengphot_specs.append
    append_lowengelec_spec = out_lowengelec_specs.append

    # Initialize arrays to store f values.
    f_low = np.empty((0, 5))
    f_high = np.empty((0, 5))

    # Initialize array to store high-energy energy deposition rate.
    highengdep_grid = np.empty((0, 4))

    # Object to help us interpolate over MEDEA results.
    MEDEA_interp = make_interpolator(interp_type='2D', cross_check=cross_check)

    #########################################################################
    #########################################################################
    # LOOP! LOOP! LOOP! LOOP!                                               #
    #########################################################################
    #########################################################################

    while rs > end_rs:

        # Update tqdm.
        if use_tqdm:
            pbar.update(1)

        #############################
        # First Step Special Cases  #
        #############################
        if rs == start_rs:
            # Initialize the electron and photon arrays.
            # These will carry the spectra produced by applying the
            # transfer function at rs to high-energy photons.
            highengphot_spec_at_rs = in_spec_phot * 0
            lowengphot_spec_at_rs = in_spec_phot * 0
            lowengelec_spec_at_rs = in_spec_elec * 0
            highengdep_at_rs = np.zeros(4)

        #####################################################################
        #####################################################################
        # Electron Cooling                                                  #
        #####################################################################
        #####################################################################

        # Get the transfer functions corresponding to electron cooling.
        # These are \bar{T}_\gamma, \bar{T}_e and \bar{R}_c.
        if elec_processes:

            if backreaction:
                xHII_elec_cooling = x_arr[-1, 0]
                xHeII_elec_cooling = x_arr[-1, 1]
            else:
                xHII_elec_cooling = phys.xHII_std(rs)
                xHeII_elec_cooling = phys.xHeII_std(rs)

            (ics_sec_phot_tf, elec_processes_lowengelec_tf, deposited_ion_arr,
             deposited_exc_arr, deposited_heat_arr, continuum_loss,
             deposited_ICS_arr) = get_elec_cooling_tf(
                 eleceng,
                 photeng,
                 rs,
                 xHII_elec_cooling,
                 xHeII=xHeII_elec_cooling,
                 raw_thomson_tf=ics_thomson_ref_tf,
                 raw_rel_tf=ics_rel_ref_tf,
                 raw_engloss_tf=engloss_ref_tf,
                 coll_ion_sec_elec_specs=coll_ion_sec_elec_specs,
                 coll_exc_sec_elec_specs=coll_exc_sec_elec_specs,
                 ics_engloss_data=ics_engloss_data)

            # Apply the transfer function to the input electron spectrum.

            # Low energy electrons from electron cooling, per injection event.
            elec_processes_lowengelec_spec = (
                elec_processes_lowengelec_tf.sum_specs(in_spec_elec))

            # Add this to lowengelec_at_rs.
            lowengelec_spec_at_rs += (elec_processes_lowengelec_spec *
                                      norm_fac(rs))

            # High-energy deposition into ionization,
            # *per baryon in this step*.
            deposited_ion = np.dot(deposited_ion_arr,
                                   in_spec_elec.N * norm_fac(rs))
            # High-energy deposition into excitation,
            # *per baryon in this step*.
            deposited_exc = np.dot(deposited_exc_arr,
                                   in_spec_elec.N * norm_fac(rs))
            # High-energy deposition into heating,
            # *per baryon in this step*.
            deposited_heat = np.dot(deposited_heat_arr,
                                    in_spec_elec.N * norm_fac(rs))
            # High-energy deposition numerical error,
            # *per baryon in this step*.
            deposited_ICS = np.dot(deposited_ICS_arr,
                                   in_spec_elec.N * norm_fac(rs))

            #######################################
            # Photons from Injected Electrons     #
            #######################################

            # ICS secondary photon spectrum after electron cooling,
            # per injection event.
            ics_phot_spec = ics_sec_phot_tf.sum_specs(in_spec_elec)

            # Get the spectrum from positron annihilation, per injection event.
            # Only half of in_spec_elec is positrons!
            positronium_phot_spec = pos.weighted_photon_spec(photeng) * (
                in_spec_elec.totN() / 2)
            positronium_phot_spec.switch_spec_type('N')

        # Add injected photons + photons from injected electrons
        # to the photon spectrum that got propagated forward.
        if elec_processes:
            highengphot_spec_at_rs += (in_spec_phot + ics_phot_spec +
                                       positronium_phot_spec) * norm_fac(rs)
        else:
            highengphot_spec_at_rs += in_spec_phot * norm_fac(rs)

        # Set the redshift correctly.
        highengphot_spec_at_rs.rs = rs

        #####################################################################
        #####################################################################
        # Save the Spectra!                                                 #
        #####################################################################
        #####################################################################

        # At this point, highengphot_at_rs, lowengphot_at_rs and
        # lowengelec_at_rs have been computed for this redshift.
        append_highengphot_spec(highengphot_spec_at_rs)
        append_lowengphot_spec(lowengphot_spec_at_rs)
        append_lowengelec_spec(lowengelec_spec_at_rs)

        #####################################################################
        #####################################################################
        # Compute f_c(z)                                                    #
        #####################################################################
        #####################################################################
        if elec_processes:
            # High-energy deposition from input electrons.
            highengdep_at_rs += np.array([
                deposited_ion / dt, deposited_exc / dt, deposited_heat / dt,
                deposited_ICS / dt
            ])

        # Values of (xHI, xHeI, xHeII) to use for computing f.
        if backreaction:
            # Use the previous values with backreaction.
            x_vec_for_f = np.array(
                [1. - x_arr[-1, 0], phys.chi - x_arr[-1, 1], x_arr[-1, 1]])
        else:
            # Use baseline values if no backreaction.
            x_vec_for_f = np.array([
                1. - phys.xHII_std(rs), phys.chi - phys.xHeII_std(rs),
                phys.xHeII_std(rs)
            ])

        f_raw = compute_fs(MEDEA_interp,
                           lowengelec_spec_at_rs,
                           lowengphot_spec_at_rs,
                           x_vec_for_f,
                           rate_func_eng_unclustered(rs),
                           dt,
                           highengdep_at_rs,
                           method=compute_fs_method,
                           cross_check=cross_check)

        # Save the f_c(z) values.
        f_low = np.concatenate((f_low, [f_raw[0]]))
        f_high = np.concatenate((f_high, [f_raw[1]]))

        # print(f_low, f_high)

        # Save CMB upscattered rate and high-energy deposition rate.
        highengdep_grid = np.concatenate((highengdep_grid, [highengdep_at_rs]))

        # Compute f for TLA: sum of low and high.
        f_H_ion = f_raw[0][0] + f_raw[1][0]
        f_exc = f_raw[0][2] + f_raw[1][2]
        f_heat = f_raw[0][3] + f_raw[1][3]

        if compute_fs_method == 'old':
            # The old method neglects helium.
            f_He_ion = 0.
        else:
            f_He_ion = f_raw[0][1] + f_raw[1][1]

        #####################################################################
        #####################################################################
        # ********* AFTER THIS, COMPUTE QUANTITIES FOR NEXT STEP *********  #
        #####################################################################
        #####################################################################

        # Define the next redshift step.
        next_rs = np.exp(np.log(rs) - dlnz * coarsen_factor)

        #####################################################################
        #####################################################################
        # TLA Integration                                                   #
        #####################################################################
        #####################################################################

        # Initial conditions for the TLA, (Tm, xHII, xHeII, xHeIII).
        # This is simply the last set of these variables.
        init_cond_TLA = np.array([Tm_arr[-1], x_arr[-1, 0], x_arr[-1, 1], 0])

        # Solve the TLA for x, Tm for the *next* step.
        new_vals = tla.get_history(np.array([rs, next_rs]),
                                   init_cond=init_cond_TLA,
                                   f_H_ion=f_H_ion,
                                   f_H_exc=f_exc,
                                   f_heating=f_heat,
                                   injection_rate=rate_func_eng_unclustered,
                                   reion_switch=reion_switch,
                                   reion_rs=reion_rs,
                                   photoion_rate_func=photoion_rate_func,
                                   photoheat_rate_func=photoheat_rate_func,
                                   xe_reion_func=xe_reion_func,
                                   helium_TLA=helium_TLA,
                                   f_He_ion=f_He_ion,
                                   mxstep=mxstep,
                                   rtol=rtol)

        #####################################################################
        #####################################################################
        # Photon Cooling Transfer Functions                                 #
        #####################################################################
        #####################################################################

        # Get the transfer functions for this step.
        if not backreaction:
            # Interpolate using the baseline solution.
            xHII_to_interp = phys.xHII_std(rs)
            xHeII_to_interp = phys.xHeII_std(rs)
        else:
            # Interpolate using the current xHII, xHeII values.
            xHII_to_interp = x_arr[-1, 0]
            xHeII_to_interp = x_arr[-1, 1]

        highengphot_tf, lowengphot_tf, lowengelec_tf, highengdep_arr = (get_tf(
            rs,
            xHII_to_interp,
            xHeII_to_interp,
            dlnz,
            coarsen_factor=coarsen_factor))

        # Get the spectra for the next step by applying the
        # transfer functions.
        highengdep_at_rs = np.dot(np.swapaxes(highengdep_arr, 0, 1),
                                  out_highengphot_specs[-1].N)

        highengphot_spec_at_rs = highengphot_tf.sum_specs(
            out_highengphot_specs[-1])

        lowengphot_spec_at_rs = lowengphot_tf.sum_specs(
            out_highengphot_specs[-1])

        lowengelec_spec_at_rs = lowengelec_tf.sum_specs(
            out_highengphot_specs[-1])

        highengphot_spec_at_rs.rs = next_rs
        lowengphot_spec_at_rs.rs = next_rs
        lowengelec_spec_at_rs.rs = next_rs

        if next_rs > end_rs:
            # Only save if next_rs < end_rs, since these are the x, Tm
            # values for the next redshift.

            # Save the x, Tm data for the next step in x_arr and Tm_arr.
            Tm_arr = np.append(Tm_arr, new_vals[-1, 0])

            if helium_TLA:
                # Append the calculated xHe to x_arr.
                x_arr = np.append(x_arr, [[new_vals[-1, 1], new_vals[-1, 2]]],
                                  axis=0)
            else:
                # Append the baseline solution value.
                x_arr = np.append(x_arr,
                                  [[new_vals[-1, 1],
                                    phys.xHeII_std(next_rs)]],
                                  axis=0)

        # Re-define existing variables.
        rs = next_rs
        dt = dlnz * coarsen_factor / phys.hubble(rs)

    #########################################################################
    #########################################################################
    # END OF LOOP! END OF LOOP!                                             #
    #########################################################################
    #########################################################################

    if use_tqdm:
        pbar.close()

    f_to_return = (f_low, f_high)

    # Some processing to get the data into presentable shape.
    f_low_dict = {
        'H ion': f_low[:, 0],
        'He ion': f_low[:, 1],
        'exc': f_low[:, 2],
        'heat': f_low[:, 3],
        'cont': f_low[:, 4]
    }
    f_high_dict = {
        'H ion': f_high[:, 0],
        'He ion': f_high[:, 1],
        'exc': f_high[:, 2],
        'heat': f_high[:, 3],
        'cont': f_high[:, 4]
    }

    f = {'low': f_low_dict, 'high': f_high_dict}

    data = {
        'rs': out_highengphot_specs.rs,
        'x': x_arr,
        'Tm': Tm_arr,
        'highengphot': out_highengphot_specs,
        'lowengphot': out_lowengphot_specs,
        'lowengelec': out_lowengelec_specs,
        'f': f
    }

    return data