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
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()
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