Beispiel #1
0
def logSFR_M_t(logmass,
               t_input,
               t_init=None,
               t_q=None,
               M_q=None,
               dutycycle_prop=None,
               tau_prop=None,
               sfms_prop=None,
               indices=None):
    ''' log(SFR) as a function of M* and t_cosmic.

    Notes
    -----
    * kwarg indices is for dutycycle_prop 
    '''
    # SFR evolution based on solving an ODE of SFR
    logsfr_time = time.time()
    z_init = z_of_t(t_init)  # initial redshift

    # update quenched M* ( this is the stellar mass roughly at the time of quenching)
    just_quenched = np.where((t_input > t_q) & (M_q == -999.))
    if len(just_quenched[0]) > 0:
        M_q[just_quenched] = logmass[just_quenched]

    # average SFR of SFMS at M* and z_init
    quenched = np.where(t_input > t_q)
    tmp_M = logmass.copy()
    tmp_M[quenched] = M_q[quenched]
    avglogsfr = sfr_evol.AverageLogSFR_sfms(tmp_M, z_init, sfms_prop=sfms_prop)

    # log(SFR)_SFMS evolutionfrom t0
    logsfr_sfms = sfr_evol.DeltaLogSFR_sfms(z_init,
                                            z_of_t(t_input),
                                            sfms_prop=sfms_prop)

    # log(SFR)_duty cycle evolution from t0 to tQ
    logsfr_sfduty = sfr_evol.DeltaLogSFR_dutycycle(
        t_init,
        t_input,
        t_q=t_q,
        dutycycle_prop=dutycycle_prop,
        indices=indices)

    logsfr_quench = sfr_evol.DeltaLogSFR_quenching(t_q,
                                                   t_input,
                                                   M_q=M_q,
                                                   tau_prop=tau_prop)

    logsfr_tot = avglogsfr + logsfr_sfms + logsfr_sfduty + logsfr_quench

    return [logsfr_tot, M_q]
def EvolveSatSFR(sg_in, tqdelay_dict=None):
    ''' Evolve the infalling SFR assigned based on central galaxy SFRs
    '''
    if tqdelay_dict == None:
        raise ValueError

    # initalize
    sg_obj = SGPop()
    sg_obj.ilk = sg_in.ilk.copy()
    sg_obj.pos = sg_in.pos.copy()
    sg_obj.snap_index = sg_in.snap_index.copy()
    sg_obj.zsnap = sg_in.zsnap.copy()
    sg_obj.t_cosmic = sg_in.t_cosmic.copy()
    sg_obj.sfms_prop = sg_in.sfms_prop.copy()
    sg_obj.abcrun = sg_in.abcrun
    sg_obj.prior_name = sg_in.prior_name
    sg_obj.mass = sg_in.mass.copy()
    sg_obj.sfr = np.repeat(-999., len(sg_obj.mass))
    sg_obj.ssfr = np.repeat(-999., len(sg_obj.mass))
    sg_obj.halo_mass = sg_in.halo_mass.copy()
    sg_obj.first_infall_nsnap = sg_in.first_infall_nsnap.copy()
    sg_obj.first_infall_z = sg_in.first_infall_z.copy()
    sg_obj.first_infall_t = sg_in.first_infall_t.copy()
    sg_obj.first_infall_sfr = sg_in.first_infall_sfr.copy()
    sg_obj.first_infall_mass = sg_in.first_infall_mass.copy()
    sg_obj.t_qstart = np.repeat(-999., len(sg_obj.mass))
    sg_obj.z_qstart = np.repeat(-999., len(sg_obj.mass))
    sg_obj.q_ssfr = np.repeat(-999., len(sg_obj.mass))

    # First classify the galaxies into SF, Q, and Qing
    # this is so that t_Q,start = t_inf for Qing galaxies,
    # Q galaxies are left alone
    # SF galaxies are affected by the delay time
    infall = np.where((sg_obj.first_infall_mass > 0.)
                      & (sg_obj.first_infall_sfr > -999.))

    fq_obj = Fq()
    sfq_class = fq_obj.Classify(sg_obj.first_infall_mass[infall],
                                sg_obj.first_infall_sfr[infall],
                                sg_obj.first_infall_z[infall],
                                sg_obj.sfms_prop)

    sf_infall = (infall[0])[np.where(
        sfq_class == 'star-forming')]  # starforming @ infall
    q_infall = (infall[0])[np.where(
        sfq_class == 'quiescent')]  # quiescent @ infall

    ssfr_final = sfr_evol.AverageLogSSFR_q_peak(sg_obj.mass[infall]) + \
            sfr_evol.ScatterLogSSFR_q_peak(sg_obj.mass[infall]) * np.random.randn(len(infall[0]))

    # sub-divide the quiescent @ infall population to those that are
    # quenching @ infall + quiescent @ infall based on simple SSFR cut
    q_mass_infall = sg_obj.first_infall_mass[q_infall]
    q_sfr_infall = sg_obj.first_infall_sfr[q_infall]
    q_ssfr_infall = q_sfr_infall - q_mass_infall

    q_cut_SSFR = sfr_evol.AverageLogSSFR_q_peak(q_mass_infall) + \
            1.5 * sfr_evol.ScatterLogSSFR_q_peak(q_mass_infall)

    qing_infall = q_infall[np.where(q_ssfr_infall > q_cut_SSFR)]  # quenching
    qq_infall = q_infall[np.where(q_ssfr_infall <= q_cut_SSFR)]  # quenched

    # Quiescent @ infall-----
    # The SSFR is preserved from infall, so effectively their SFR decreases
    sg_obj.ssfr[qq_infall] = sg_obj.first_infall_sfr[qq_infall] - \
            sg_obj.first_infall_mass[qq_infall]
    sg_obj.sfr[qq_infall] = sg_obj.mass[qq_infall] + sg_obj.ssfr[qq_infall]

    # Quenching @ infall-----
    # Quenching satellite galaxies skip the delay phase and immediately
    # start quenching when the simulation begins.
    sg_obj.t_qstart[qing_infall] = sg_obj.first_infall_t[qing_infall]
    sg_obj.z_qstart[qing_infall] = sg_obj.first_infall_z[qing_infall]
    sg_obj.q_ssfr[qing_infall] = sfr_evol.AverageLogSSFR_q_peak(sg_obj.mass[qing_infall]) + \
            sfr_evol.ScatterLogSSFR_q_peak(sg_obj.mass[qing_infall]) * np.random.randn(len(qing_infall))

    dlogSFR_MS_M = 0.53 * (sg_obj.mass[qing_infall] -
                           sg_obj.first_infall_mass[qing_infall])

    dlogSFR_MS_z = sg_obj.sfms_prop['zslope'] * (
        sg_obj.zsnap - sg_obj.first_infall_z[qing_infall])

    dlogSFR_Q = sfr_evol.DeltaLogSFR_quenching(sg_obj.t_qstart[qing_infall],
                                               sg_obj.t_cosmic,
                                               M_q=sg_obj.mass[qing_infall],
                                               tau_prop={'name': 'satellite'})

    sg_obj.sfr[qing_infall] = sg_obj.first_infall_sfr[qing_infall] + \
            dlogSFR_MS_M + dlogSFR_MS_z + dlogSFR_Q
    sg_obj.ssfr[
        qing_infall] = sg_obj.sfr[qing_infall] - sg_obj.mass[qing_infall]

    # deal with over quenching
    #overquenched = np.where(sg_obj.ssfr[qing_infall] < sg_obj.q_ssfr[qing_infall])
    #sg_obj.ssfr[qing_infall[overquenched]] = sg_obj.q_ssfr[qing_infall[overquenched]]

    # Star-forming @ infall
    if tqdelay_dict['name'] == 'hacked':
        sg_obj.t_qstart[sf_infall] = sg_obj.first_infall_t[sf_infall] + \
                tDelay(sg_obj.mass[sf_infall], **tqdelay_dict)
    else:
        sg_obj.t_qstart[sf_infall] = sg_obj.first_infall_t[sf_infall] + \
                tDelay(sg_obj.first_infall_mass[sf_infall], **tqdelay_dict)
    # if t_qstart > t_cosmic, then it does not quenching during the simualtion
    sf_q = np.where(sg_obj.t_qstart[sf_infall] < sg_obj.t_cosmic)
    sf_noq = np.where(sg_obj.t_qstart[sf_infall] >= sg_obj.t_cosmic)
    sf_infall_q = sf_infall[sf_q]
    sf_infall_noq = sf_infall[sf_noq]

    sg_obj.z_qstart[sf_infall_q] = Util.get_zsnap(sg_obj.t_qstart[sf_infall_q])
    sg_obj.z_qstart[sf_infall_noq] = 999.

    # SF galaxies that quench
    sg_obj.q_ssfr[sf_infall_q] = \
            sfr_evol.AverageLogSSFR_q_peak(sg_obj.mass[sf_infall_q]) + \
            sfr_evol.ScatterLogSSFR_q_peak(sg_obj.mass[sf_infall_q]) * \
            np.random.randn(len(sf_infall_q))

    dlogSFR_MS_M = 0.53 * (sg_obj.mass[sf_infall_q] -
                           sg_obj.first_infall_mass[sf_infall_q])

    dlogSFR_MS_z = sg_obj.sfms_prop['zslope'] * (
        sg_obj.zsnap - sg_obj.first_infall_z[sf_infall_q])
    dlogSFR_Q = sfr_evol.DeltaLogSFR_quenching(sg_obj.t_qstart[sf_infall_q],
                                               sg_obj.t_cosmic,
                                               M_q=sg_obj.mass[sf_infall_q],
                                               tau_prop={'name': 'satellite'})

    sg_obj.sfr[sf_infall_q] = sg_obj.first_infall_sfr[sf_infall_q] + \
            dlogSFR_MS_M + dlogSFR_MS_z + dlogSFR_Q
    sg_obj.ssfr[
        sf_infall_q] = sg_obj.sfr[sf_infall_q] - sg_obj.mass[sf_infall_q]
    # deal with over quenching
    #overquenched = np.where(sg_obj.ssfr[sf_infall_q] < sg_obj.q_ssfr[sf_infall_q])
    #sg_obj.ssfr[sf_infall_q[overquenched]] = sg_obj.q_ssfr[sf_infall_q[overquenched]]

    # SF galaxies that do NOT quench
    dlogSFR_MS_M = 0.53 * (sg_obj.mass[sf_infall_noq] -
                           sg_obj.first_infall_mass[sf_infall_noq])

    dlogSFR_MS_z = sg_obj.sfms_prop['zslope'] * (
        sg_obj.zsnap - sg_obj.first_infall_z[sf_infall_noq])

    sg_obj.sfr[sf_infall_noq] = sg_obj.first_infall_sfr[sf_infall_noq] + \
            dlogSFR_MS_M + dlogSFR_MS_z
    sg_obj.ssfr[
        sf_infall_noq] = sg_obj.sfr[sf_infall_noq] - sg_obj.mass[sf_infall_noq]

    # deal with overquenching all at once
    overquench = np.where(sg_obj.ssfr[infall] < ssfr_final)
    sg_obj.ssfr[infall[0][overquench]] = ssfr_final[overquench]
    sg_obj.sfr[infall[0][overquench]] = ssfr_final[overquench] + sg_obj.mass[
        infall[0][overquench]]

    return sg_obj
Beispiel #3
0
def Evol_shamMstarSFR(t0,
                      tfs,
                      t_step=0.5,
                      tQ0=None,
                      MQ0=None,
                      SFR0=None,
                      q_Mshams=None,
                      sf_Mshams=None,
                      quiet=True,
                      **kwargs):
    ''' Evolve SFR of the SF galaxies while quenching some of them at the same time. 

    Notes
    -----
    * SF galaxies are quenched based on the following prescription:
        The number of galaxies that start quenching between t0 and t0+tstep 
        is determined by first guessing, 

        N_quenching = N_sf(t0) * (1/( 1 - fQ(t0) )) * dFq/dt(t0 + 0.5 tstep) * tstep
    '''
    dutycycle_prop = kwargs['dutycycle_prop']
    tau_prop = kwargs['tau_prop']
    fq_prop = kwargs['fq_prop']
    sfms_prop = kwargs['sfms_prop']
    massevol_prop = kwargs['massevol_prop']
    fudge_prop = kwargs['fudge_prop']

    tQ = tQ0.copy()
    Mq = MQ0.copy()

    t00 = t0.min()  # earliest starting time (cosmic time of ancestor snapshot)
    t_evol = t_snap[np.abs(t_snap - np.max(tfs)).argmin() -
                    1:np.abs(t_snap - t00).argmin() + 1][::-1]

    qf = Fq()
    Fq_anal = qf.model

    # Mass bins
    M_bins = np.arange(6.0, 13., 0.5)
    M_mid = 0.5 * (M_bins[:-1] + M_bins[1:])

    for i_t, tt in enumerate(t_evol[:-1]):
        if not quiet:
            print 't_cosmic = ', tt
            t_one_tstep = time.time()

        # M_sham of the quiescent galaxies at the closest snapshot.
        closest_t0_snap = np.abs(t_snap - tt).argmin() - 1
        Msham_q0 = q_Mshams[:, closest_t0_snap].copy()
        Msham_sf0 = sf_Mshams[:, closest_t0_snap].copy()

        within = np.where((Msham_sf0 > 0.) & (t0 <= tt))  # tsnap_genesis <= t
        sf_within = np.where((Msham_sf0 > 0.) & (t0 <= tt)
                             & (tQ == 999.))[0]  # Not quenching SF galaxies
        Nsf_0 = len(sf_within)
        M_t0_sample = np.concatenate(
            [Msham_sf0[within], Msham_q0[np.where(Msham_q0 > 0.)]])

        P_sf = np.random.uniform(0., 1., Nsf_0)

        # Initial estimate of quenching probability given by dFq/dt
        # P_q = (1/( 1 - fQ(t0) )) * dFq/dt(t0 + 0.5 tstep) * tstep
        Ng0, dum = np.histogram(M_t0_sample, bins=M_bins)
        Nsf0, dum = np.histogram(Msham_sf0[sf_within], bins=M_bins)
        P_q_arr = Ng0 / Nsf0 * dFqdt(
            M_mid, tt + 0.5 * t_step, lit=fq_prop['name']) * t_step
        P_q_arr[np.where(Nsf0 == 0)] = 0.
        if not quiet:
            print 'M* : ', M_mid[-6:]
            print 'P_q : ', P_q_arr[-6:]
        Pq_M = interpolate.interp1d(M_mid, P_q_arr, kind='linear')
        P_q = Pq_M(Msham_sf0[sf_within])
        q_ing0 = np.where(P_q > P_sf)
        Nqing0 = len(q_ing0[0])

        # M_sham of the quiescent galaxies at the closest snapshot.
        closest_t_snap = np.abs(t_snap - t_evol[i_t + 1]).argmin() - 1
        Msham_q1 = q_Mshams[:, closest_t_snap]
        Msham_sf1 = sf_Mshams[:, closest_t_snap]
        M_t1_sample = np.concatenate([Msham_sf1[within], Msham_q1])

        # dPQ correction to the quenching to account for change in Ng(M*,t)
        Ngp, dum = np.histogram(M_t1_sample, bins=M_bins)
        dPq = (Ngp - Ng0) / Nsf0.astype('float') * Fq_anal(
            M_mid, z_of_t(tt + t_step), lit=fq_prop['name'])
        dPq[np.where(dPq < 0.)] = 0.
        dPq[np.where(Nsf0 == 0)] = 0.
        if not quiet:
            print 'dPq : ', dPq[-6:]
        dPq_M = interpolate.interp1d(M_mid, dPq, kind='linear')
        P_q += dPq_M(Msham_sf0[sf_within])
        # fudge factor for P_Q
        fudge_factor = fudge_prop['slope'] * (
            Msham_sf0[sf_within] -
            fudge_prop['fidmass']) + fudge_prop['offset']
        fudge_factor[np.where(fudge_factor < 1.)] = 1.
        P_q *= fudge_factor

        q_ing = np.where(P_sf < P_q)
        if not quiet:
            print 'Initial guess ', Nqing0, ' final: ', len(
                q_ing[0]), ' SF gal out of ', Nsf_0, '  start quenching'
            print time.time() - t_one_tstep
            print '----------------'
        # assign them quenching times then actually evolve their stellar masses
        tQ[sf_within[q_ing]] = np.random.uniform(low=tt,
                                                 high=tt + t_step,
                                                 size=len(q_ing[0]))

    quenching = np.where((Mq == -999.) & (tQ < 999.) & (tQ > 0))[0]
    Nquenching = len(quenching)
    closest_tQ_index = np.abs(
        np.tile(t_snap, (Nquenching, 1)) -
        np.tile(tQ[quenching].reshape(Nquenching, 1),
                (1, len(t_snap)))).argmin(axis=1) - 1
    Mq[quenching] = sf_Mshams[quenching, closest_tQ_index].copy()

    SFR_list = []
    for tf in tfs:
        closest_tf_snap = np.abs(t_snap - tf).argmin() - 1
        Msham_f = sf_Mshams[:, closest_tf_snap].copy()
        if closest_tf_snap == 0:
            before = Msham_f[:10]

        kwargs_sfr = {
            't_init': t0.copy(),
            't_q': tQ.copy(),
            'M_q': Mq.copy(),
            'dutycycle_prop': dutycycle_prop,
            'tau_prop': tau_prop,
            'sfms_prop': sfms_prop
        }
        SFR, M_qq = logSFR_M_t(Msham_f.copy(), tf, **kwargs_sfr)
        if closest_tf_snap == 0:
            print 'is same? ', np.array_equal(before, Msham_f[:10])
        print tf, SFR.min(), SFR.max()

        # correct for the galaxies that were originally in the green valley.
        # ultimately doesn't make a difference in the outcome, but to be meticulous
        gv0 = np.where(tQ0 == 0.)  # galaxies that were initiall quenching
        SFR[gv0] -= sfr_evol.AverageLogSFR_sfms(Mq[gv0],
                                                z_of_t(t0[gv0]),
                                                sfms_prop=sfms_prop)
        SFR[gv0] -= sfr_evol.DeltaLogSFR_quenching(tQ[gv0],
                                                   t0[gv0],
                                                   M_q=Mq[gv0],
                                                   tau_prop=tau_prop)
        SFR[gv0] += SFR0[gv0].copy()
        SFR_list.append(SFR.copy())
        del SFR

    return SFR_list, tQ.copy()
Beispiel #4
0
    def _Evol_MshamSFR(self, nsnap_d, allwill, sf_ancestor, q_ancestor):
        ''' Evolve SFR of the SF galaxies while quenching some of them at the same time. 

        Notes
        -----
        * SF galaxies are quenched based on the following prescription:
            The number of galaxies that start quenching between t0 and t0+tstep 
            is determined by first guessing, 

            N_quenching = N_sf(t0) * (1/( 1 - fQ(t0) )) * dFq/dt(t0 + 0.5 tstep) * tstep
        '''
        tQ0 = self.ancestor.tQ[allwill[sf_ancestor]].copy()
        tQ = self.ancestor.tQ[allwill[sf_ancestor]].copy()
        Mq = self.ancestor.MQ[allwill[sf_ancestor]].copy()

        # earliest starting time (cosmic time of ancestor snapshot)
        t0 = self.ancestor.tsnap_genesis[allwill[sf_ancestor]].copy()
        t00 = t0.min()

        i_low = np.abs(t_snap - np.max(self.tsnap_descendants)).argmin() - 1
        i_high = np.abs(t_snap - t00).argmin() + 1
        t_evol = t_snap[i_low:i_high][::-1]

        qf = Fq()
        Fq_anal = qf.model
        M_bins = np.arange(5.5, 13., 0.5)
        M_mid = 0.5 * (M_bins[:-1] + M_bins[1:])

        q_Mshams = self.MshamEvol[allwill[q_ancestor], :].copy()
        sf_Mshams = self.MshamEvol[allwill[sf_ancestor], :].copy()

        for i_t, tt in enumerate(t_evol[:-1]):
            t_step = t_evol[i_t + 1] - tt
            if not self.quiet:
                print 't_cosmic = ', tt
                t_one_tstep = time.time()
            # M_sham of the quiescent galaxies at the closest snapshot.
            closest_t0_snap = np.abs(t_snap - tt).argmin() - 1
            Msham_q0 = q_Mshams[:, closest_t0_snap]
            Msham_sf0 = sf_Mshams[:, closest_t0_snap]

            within = np.where(
                (Msham_sf0 > 0.) & (t0 <= tt))  # tsnap_genesis <= t
            sf_within = np.where((Msham_sf0 > 0.) & (t0 <= tt) & (tQ == 999.))[
                0]  # Not quenching SF galaxies
            Nsf_0 = len(sf_within)
            M_t0_sample = np.concatenate(
                [Msham_sf0[within], Msham_q0[np.where(Msham_q0 > 0.)]])
            P_sf = np.random.uniform(0., 1., Nsf_0)

            # Initial estimate of quenching probability given by dFq/dt
            # P_q = (1/( 1 - fQ(t0) )) * dFq/dt(t0 + 0.5 tstep) * tstep
            Ng0, dum = np.histogram(M_t0_sample, bins=M_bins)
            Nsf0, dum = np.histogram(Msham_sf0[sf_within], bins=M_bins)
            P_q_arr = Ng0/Nsf0 * \
                    dFqdt(M_mid, tt + 0.5 * t_step, lit=self.fq_prop['name']) * t_step
            P_q_arr[np.where(Nsf0 == 0)] = 0.
            if not self.quiet:
                print 'M* : ', M_mid[-6:]
                print 'P_q : ', P_q_arr[-6:]
            Pq_M = interpolate.interp1d(M_mid, P_q_arr, kind='linear')
            P_q = Pq_M(Msham_sf0[sf_within])
            q_ing0 = np.where(P_q > P_sf)
            Nqing0 = len(q_ing0[0])

            # M_sham of the quiescent galaxies at the closest snapshot.
            closest_t1_snap = np.abs(t_snap - t_evol[i_t + 1]).argmin() - 1
            Msham_q1 = q_Mshams[:, closest_t1_snap]
            Msham_sf1 = sf_Mshams[:, closest_t1_snap]
            M_t1_sample = np.concatenate([Msham_sf1[within], Msham_q1])

            # dPQ correction to the quenching to account for change in Ng(M*,t)
            Ngp, dum = np.histogram(M_t1_sample, bins=M_bins)
            dPq = (Ngp - Ng0)/Nsf0.astype('float') * \
                    Fq_anal(M_mid, z_of_t(tt + t_step), lit=self.fq_prop['name'])
            dPq[np.where(dPq < 0.)] = 0.
            dPq[np.where(Nsf0 == 0)] = 0.
            if not self.quiet:
                print 'dPq : ', dPq[-6:]
            dPq_M = interpolate.interp1d(M_mid, dPq, kind='linear')
            P_q += dPq_M(Msham_sf0[sf_within])
            # fudge factor for P_Q
            fudge_factor = self.fudge_prop['slope'] * (
                Msham_sf0[sf_within] -
                self.fudge_prop['fidmass']) + self.fudge_prop['offset']
            fudge_factor[np.where(fudge_factor < 1.)] = 1.
            P_q *= fudge_factor

            if self.printPQ:  # store quenching probabilities

                Pq_out = {}
                Pq_out['mass'] = M_mid

                fPQ = self.fudge_prop['slope'] * (M_mid - self.fudge_prop['fidmass']) + \
                        self.fudge_prop['offset']
                fPQ[np.where(fPQ < 1.)] = 1.
                Pq_out['Pq'] = fPQ * (P_q_arr + dPq)
                Pq_out['Pq_fid'] = P_q_arr + dPq

                if self.PQs is None:
                    self.PQs = {}
                self.PQs[str(z_of_t(tt))] = Pq_out

            q_ing = np.where(P_sf < P_q)
            if not self.quiet:
                print 'Initial guess ', Nqing0, ' final: ', len(
                    q_ing[0]), ' SF gal out of ', Nsf_0, '  start quenching'
                print time.time() - t_one_tstep
                print '----------------'
            # assign them quenching times then actually evolve their stellar masses
            tQ[sf_within[q_ing]] = np.random.uniform(low=tt,
                                                     high=tt + t_step,
                                                     size=len(q_ing[0]))

        quenching = np.where((Mq == -999.) & (tQ < 999.) & (tQ > 0.))[0]
        Nquenching = len(quenching)
        closest_tQ_index = np.abs(
            np.tile(t_snap, (Nquenching, 1)) -
            np.tile(tQ[quenching].reshape(Nquenching, 1),
                    (1, len(t_snap)))).argmin(axis=1) - 1
        Mq[quenching] = sf_Mshams[quenching, closest_tQ_index]

        z0 = z_of_t(t0)  # initial redshift
        gv0 = np.where(tQ0 == 0.)[0]  # galaxies that were initial quenching

        t_f = self.descendant_dict[str(nsnap_d)].t_cosmic
        z_f = self.descendant_dict[str(nsnap_d)].zsnap

        closest_tf_snap = np.abs(t_snap - t_f).argmin() - 1
        Msham_f = sf_Mshams[:, closest_tf_snap].copy()

        q_ed = np.where(t_f > tQ)
        tmp_M = Msham_f.copy()
        tmp_M[q_ed] = Mq[q_ed].copy()

        # log(SFR)_SFMS evolution from t0
        logsfr_sfms = sfr_evol.AverageLogSFR_sfms(tmp_M, z0, sfms_prop=self.sfms_prop) + \
                sfr_evol.DeltaLogSFR_sfms(z0, z_f, sfms_prop=self.sfms_prop)

        # log(SFR)_duty cycle evolution from t0 to tQ
        logsfr_sfduty = sfr_evol.DeltaLogSFR_dutycycle(
            t0, t_f, t_q=tQ, dutycycle_prop=self.dutycycle_prop)

        logsfr_quench = sfr_evol.DeltaLogSFR_quenching(tQ,
                                                       t_f,
                                                       M_q=Mq,
                                                       tau_prop=self.tau_prop)

        logSFR = logsfr_sfms + logsfr_quench + logsfr_sfduty

        # correct for the galaxies that were originally in the green valley.
        # ultimately doesn't make a difference in the outcome, but to be meticulous
        logSFR[gv0] = (self.ancestor.sfr[allwill[sf_ancestor]])[gv0] + \
                sfr_evol.DeltaLogSFR_sfms(z0[gv0], z_f, sfms_prop=self.sfms_prop) + \
                sfr_evol.DeltaLogSFR_quenching(
                        np.repeat(self.ancestor.t_cosmic, len(gv0)),
                        t_f, M_q=Mq[gv0], tau_prop=self.tau_prop)

        return logSFR, tQ