def c2_Zhao09(Mv, t): """ Halo concentration from the mass assembly history, using the Zhao+09 relation. Syntax: c2_Zhao09(Mv,t) where Mv: main-branch virial mass history [M_sun] (array) t: the time series of the main-branch mass history (array of the same size as Mv) Note that we need Mv and t in reverse chronological order, i.e., in decreasing order, such that Mv[0] and t[0] is the instantaneous halo mass and time. Note that Mv is the Bryan and Norman 98 M_vir. Return: halo concentration c R_vir / r_-2 (float) """ idx = aux.FindNearestIndex(Mv, 0.04 * Mv[0]) return 4. * (1. + (t[0] / (3.75 * t[idx]))**8.4)**0.125
#---update tprevious tprevious = t t2 = time.time() print(' time = %.4f'%(t2-t1)) #---a few other quantities for plot Vv = Vcirc(potential,hDekel.rh) # host halo virial velocity #if radius.min() < r_min: # idx = aux.FindNearestIndex(radius,r_min) # tfinal = timesteps[idx] #else: # tfinal = tfinaltdyn0*tdyn0 # idx = aux.FindNearestIndex(timesteps,tfinal) tfinal = tfinaltdyn0*tdyn0 # <<< test idx = aux.FindNearestIndex(timesteps,tfinal) # <<< test mfinal = mass[idx] print(' m(t_final)/m(0)=%.4f'%(mfinal/mv0)) ########################### diagnostic plots ############################ print('>>> plot ...') plt.close('all') # close all previous figure windows #------------------------------------------------------------------------ # set up the figure window fig1 = plt.figure(figsize=(16,9), dpi=80, facecolor='w', edgecolor='k') fig1.subplots_adjust(left=0.06, right=0.95,bottom=0.06, top=0.99, hspace=0.25, wspace=0.47) gs = gridspec.GridSpec(3, 5)
def loop(itree): """ Replaces the loop "for itree in range(Ntree):", for parallelization. """ time_start_tmp = time.time() np.random.seed() # [important!] reseed the random number generator lgM0 = lgM0_lo + np.random.random()*(lgM0_hi-lgM0_lo) cfg.M0 = 10.**lgM0 cfg.z0 = z0 cfg.Mres = 10.**lgMres cfg.Mmin = 0.04*cfg.Mres k = 0 # the level, k, of the branch being considered ik = 0 # how many level-k branches have been finished Nk = 1 # total number of level-k branches Nbranch = 1 # total number of branches in the current tree Mak = [cfg.M0] # accretion masses of level-k branches zak = [cfg.z0] idk = [0] # branch ids of level-k branches ipk = [-1] # parent ids of level-k branches (-1: no parent) Mak_tmp = [] zak_tmp = [] idk_tmp = [] ipk_tmp = [] mass = np.zeros((cfg.Nmax,cfg.Nz)) - 99. order = np.zeros((cfg.Nmax,cfg.Nz),np.int8) - 99 ParentID = np.zeros((cfg.Nmax,cfg.Nz),np.int16) - 99 VirialRadius = np.zeros((cfg.Nmax,cfg.Nz),np.float32) - 99. concentration = np.zeros((cfg.Nmax,cfg.Nz),np.float32) - 99. DekelConcentration = np.zeros((cfg.Nmax,cfg.Nz),np.float32) - 99. DekelSlope = np.zeros((cfg.Nmax,cfg.Nz),np.float32) - 99. StellarMass = np.zeros((cfg.Nmax,cfg.Nz)) - 99. StellarSize = np.zeros((cfg.Nmax,cfg.Nz),np.float32) - 99. coordinates = np.zeros((cfg.Nmax,cfg.Nz,6),np.float32) while True: # loop over branches, until the full tree is completed. # Starting from the main branch, draw progenitor(s) using the # Parkinson+08 algorithm. When there are two progenitors, the less # massive one is the root of a new branch. We draw branches level by # level, i.e., When a new branch occurs, we record its root, but keep # finishing the current branch and all the branches of the same level # as the current branch, before moving on to the next-level branches. M = [Mak[ik]] # mass history of current branch in fine timestep z = [zak[ik]] # the redshifts of the mass history cfg.M0 = Mak[ik]# descendent mass cfg.z0 = zak[ik]# descendent redshift id = idk[ik] # branch id ip = ipk[ik] # parent id while cfg.M0>cfg.Mmin: if cfg.M0>cfg.Mres: zleaf = cfg.z0 # update leaf redshift co.UpdateGlobalVariables(**cfg.cosmo) M1,M2,Np = co.DrawProgenitors(**cfg.cosmo) # update descendent halo mass and descendent redshift cfg.M0 = M1 cfg.z0 = cfg.zW_interp(cfg.W0+cfg.dW) if cfg.z0>cfg.zmax: break if Np>1 and cfg.M0>cfg.Mres: # register next-level branches Nbranch += 1 Mak_tmp.append(M2) zak_tmp.append(cfg.z0) idk_tmp.append(Nbranch) ipk_tmp.append(id) # record the mass history at the original time resolution M.append(cfg.M0) z.append(cfg.z0) # Now that a branch is fully grown, do some book-keeping # convert mass-history list to array M = np.array(M) z = np.array(z) # downsample the fine-step mass history, M(z), onto the # coarser output timesteps, cfg.zsample Msample,zsample = aux.downsample(M,z,cfg.zsample) iz = aux.FindClosestIndices(cfg.zsample,zsample) izleaf = aux.FindNearestIndex(cfg.zsample,zleaf) # compute halo structure throughout time on the coarse grid, up # to the leaf point t = co.t(z,cfg.h,cfg.Om,cfg.OL) c,a,c2,Rv = [],[],[],[] for i in iz: if i > (izleaf+1): break # only compute structure below leaf msk = z>=cfg.zsample[i] if True not in msk: break # safety ci,ai,Msi,c2i,c2DMOi = init.Dekel_fromMAH(M[msk],t[msk], cfg.zsample[i],HaloResponse=HaloResponse) Rvi = init.Rvir(M[msk][0],Delta=200.,z=cfg.zsample[i]) c.append(ci) a.append(ai) c2.append(c2i) Rv.append(Rvi) if i==iz[0]: Ms = Msi #print(' i=%6i,ci=%8.2f,ai=%8.2f,log(Msi)=%8.2f,c2i=%8.2f'%\ # (i,ci,ai,np.log10(Msi),c2i)) # <<< for test if len(c)==0: # <<< safety, dealing with rare cases where the # branch's root z[0] is close to the maximum redshift -- when # this happens, the mass history has only one element, and # z[0] can be slightly above cfg.zsample[i] for the very # first iteration, leaving the lists c,a,c2,Rv never updated ci,ai,Msi,c2i=init.Dekel_fromMAH(M,t,z[0], HaloResponse=HaloResponse) c.append(ci) a.append(ai) c2.append(c2i) Rv.append(Rvi) Ms = Msi # <<< test #print(' branch root near max redshift: z=%7.2f,log(M)=%7.2f,c=%7.2f,a=%7.2f,c2=%7.2f,log(Ms)=%7.2f'%\ # (z[0],np.log10(M[0]),c[0],a[0],c2[0],np.log10(Ms))) c = np.array(c) a = np.array(a) c2 = np.array(c2) Rv = np.array(Rv) Nc = len(c2) # length of a branch over which c2 is computed # compute stellar size at the root of the branch, i.e., at the # accretion epoch (z[0]) Re = init.Reff(Rv[0],c2[0]) # use the redshift id and parent-branch id to access the parent # branch's information at our current branch's accretion epoch, # in order to initialize the orbit if ip==-1: # i.e., if the branch is the main branch xv = np.zeros(6) else: Mp = mass[ip,iz[0]] cp = DekelConcentration[ip,iz[0]] ap = DekelSlope[ip,iz[0]] hp = Dekel(Mp,cp,ap,Delta=200.,z=zsample[0]) eps = 1./np.pi*np.arccos(1.-2.*np.random.random()) xv = init.orbit(hp,xc=1.,eps=eps) # <<< test #print(' id=%6i,k=%2i,z=%7.2f,log(M)=%7.2f,c=%7.2f,a=%7.2f,c2=%7.2f,log(Ms)=%7.2f,Re=%7.2f,xv=%7.2f,%7.2f,%7.2f,%7.2f,%7.2f,%7.2f'%\ # (id,k,z[0],np.log10(M[0]),c[0],a[0],c2[0],np.log10(Ms),Re, xv[0],xv[1],xv[2],xv[3],xv[4],xv[5])) # update the arrays for output mass[id,iz] = Msample order[id,iz] = k ParentID[id,iz] = ip VirialRadius[id,iz[0]:iz[0]+Nc] = Rv concentration[id,iz[0]:iz[0]+Nc] = c2 DekelConcentration[id,iz[0]:iz[0]+Nc] = c DekelSlope[id,iz[0]:iz[0]+Nc] = a StellarMass[id,iz[0]] = Ms StellarSize[id,iz[0]] = Re coordinates[id,iz[0],:] = xv # Check if all the level-k branches have been dealt with: if so, # i.e., if ik==Nk, proceed to the next level. ik += 1 if ik==Nk: # all level-k branches are done! Mak = Mak_tmp zak = zak_tmp idk = idk_tmp ipk = ipk_tmp Nk = len(Mak) ik = 0 Mak_tmp = [] zak_tmp = [] idk_tmp = [] ipk_tmp = [] if Nk==0: break # jump out of "while True" if no next-level branch k += 1 # update level # trim and output mass = mass[:id+1,:] order = order[:id+1,:] ParentID = ParentID[:id+1,:] VirialRadius = VirialRadius[:id+1,:] concentration = concentration[:id+1,:] DekelConcentration = DekelConcentration[:id+1,:] DekelSlope = DekelSlope[:id+1,:] StellarMass = StellarMass[:id+1,:] StellarSize = StellarSize[:id+1,:] coordinates = coordinates[:id+1,:,:] np.savez(outfile1%(itree,lgM0), redshift = cfg.zsample, CosmicTime = cfg.tsample, mass = mass, order = order, ParentID = ParentID, VirialRadius = VirialRadius, concentration = concentration, DekelConcentration = DekelConcentration, DekelSlope = DekelSlope, #VirialOverdensity = VirialOverdensity, # <<< no need in TreeGen StellarMass = StellarMass, StellarSize = StellarSize, coordinates = coordinates, ) time_end_tmp = time.time() print(' Tree %5i: log(M_0)=%6.2f, %6i branches, %2i order, %8.1f sec'\ %(itree,lgM0,Nbranch,k,time_end_tmp-time_start_tmp))
def loop(file): """ Replaces the loop "for file in files:", for parallelization. """ time_start_tmp = time.time() #---load trees f = np.load(file) redshift = f['redshift'] CosmicTime = f['CosmicTime'] mass = f['mass'] order = f['order'] ParentID = f['ParentID'] VirialRadius = f['VirialRadius'] concentration = f['concentration'] DekelConcentration = f['DekelConcentration'] DekelSlope = f['DekelSlope'] StellarMass = f['StellarMass'] StellarSize = f['StellarSize'] coordinates = f['coordinates'] VirialOverdensity = np.zeros(mass.shape, np.float32) + 200. MaxCircularVelocity = np.zeros(mass.shape, np.float32) - 99. #---identify the roots of the branches izroot = mass.argmax(axis=1) # root-redshift ids of all the branches idx = np.arange(mass.shape[0]) # branch ids of all the branches levels = np.unique(order[order > 0]) # all >0 levels in the tree for level in levels: # loop over levels from low to high for id in idx: # loop over branches iza = izroot[id] if order[id, iza] != level: continue # level by level #---initialize # read satellite properties at accretion za = redshift[iza] ta = CosmicTime[iza] ma = mass[id, iza] ka = order[id, iza] ipa = ParentID[id, iza] ca = DekelConcentration[id, iza] aa = DekelSlope[id, iza] c2a = concentration[id, iza] Da = VirialOverdensity[id, iza] msa = StellarMass[id, iza] lea = StellarSize[id, iza] xva = coordinates[id, iza, :] # initialize satellite and orbit s = Dekel(ma, ca, aa, Delta=Da, z=za) o = orbit(xva) # initialize quantities repeatedly used for tidal tracks s001a = s.sh lma = s.rmax vma = s.Vcirc(lma) lealma = lea / lma mma = s.M(lma) MaxCircularVelocity[id, iza] = vma # initialize instantaneous quantities to be updated m = ma r = np.sqrt(xva[0]**2 + xva[2]**2) k = ka ip = ipa trelease = ta # cosmic time at lastest release tprevious = ta # cosmic time at previous timestep #---evolve for iz in np.arange(iza)[::-1]: # loop over time to evolve z = redshift[iz] tcurrent = CosmicTime[iz] #---time since in current host t = tcurrent - trelease #---timestep dt = tcurrent - tprevious #---update host potential Mp = mass[ip, iz] cp = DekelConcentration[ip, iz] ap = DekelSlope[ip, iz] Dp = VirialOverdensity[ip, iz] if (fd > 0.) and (k == 1): c2p = concentration[ip, iz] Rvp = VirialRadius[ip, iz] Rep = gh.Reff(Rvp, c2p) adp = 0.766421 / (1. + 1. / flattening) * Rep bdp = adp / flattening Mdp = fd * Mp p = [ Dekel(Mp, cp, ap, Delta=Dp, z=z), MN(Mdp, adp, bdp), ] else: p = [ Dekel(Mp, cp, ap, Delta=Dp, z=z), ] #---evolve orbit if r > cfg.Rres: o.integrate(t, p, m) xv = o.xv # note that the coordinates are updated # internally in the orbit instance "o" when calling # the ".integrate" method, here we assign them to # a new variable "xv" only for bookkeeping else: # i.e., the satellite has merged to its host, so # no need for orbit integration; to avoid potential # numerical issues, we assign a dummy coordinate that # is almost zero but not exactly zero xv = np.array([cfg.Rres, 0., 0., 0., 0., 0.]) r = np.sqrt(xv[0]**2 + xv[2]**2) #---evolve satellite if m > cfg.Mres: # evolve subhalo properties m, lt = ev.msub(s, p, xv, dt, choice='King62', alpha=StrippingEfficiency) a = s.alphah # assume constant innermost slope c, D = ev.Dekel2(m, ma, lma, vma, aa, s001a, z=z) s = Dekel(m, c, a, Delta=D, z=z) lh = s.rh c2 = s.rh / s.r2 s001 = s.sh vm = s.Vcirc(s.rmax) # evolve baryonic properties mm = s.M(s.rmax) # update m_max g_le, g_ms = ev.g_EPW18(mm / mma, s001a, lealma) le = lea * g_le ms = msa * g_ms ms = min(ms, m) # <<< safety, perhaps rarely triggered else: # we do nothing about disrupted satellite, s.t., # its properties right before disruption would be # stored in the output arrays pass #---if order>1, determine if releasing this high-order # subhalo to its grandparent-host, and if releasing, # update the orbit instance if k > 1: if (r > VirialRadius[ip, iz]) & (iz <= izroot[ip]): # <<< play with the release condition: for now, # release if the instant orbital radius is larger # than the virial radius of the immediate host, # and if the immediate host is already a # satellite of the grandparent-host xv = coordinates[ip, iz, :] # <<< may change later: # for now, the released satellite picks up # the coordinates of its immediate parent o = orbit(xv) ip = ParentID[ip, iz] # update parent id k = k - 1 # update instant order trelease = tcurrent # update release time #---update the arrays for output mass[id, iz] = m order[id, iz] = k ParentID[id, iz] = ip VirialRadius[id, iz] = lh concentration[id, iz] = c2 DekelConcentration[id, iz] = c DekelSlope[id, iz] = a VirialOverdensity[id, iz] = D MaxCircularVelocity[id, iz] = vm StellarMass[id, iz] = ms StellarSize[id, iz] = le coordinates[id, iz, :] = xv #---update tprevious tprevious = tcurrent # <<< test #print(' id=%5i,k=%2i,ip=%5i,z=%6.2f,r=%9.4f,log(m)=%6.2f,D=%7.1f,c2=%6.2f,s001=%5.2f,log(ms)=%6.2f,le=%7.2f,xv=%7.2f,%7.2f,%7.2f,%7.2f,%7.2f,%7.2f'%\ # (id,k,ip,z,r,np.log10(m),D,c2,s001,np.log10(ms),le, xv[0],xv[1],xv[2],xv[3],xv[4],xv[5])) #---output outfile = outdir + file[len(datadir):] np.savez( outfile, redshift=redshift, CosmicTime=CosmicTime, mass=mass, order=order, ParentID=ParentID, VirialRadius=VirialRadius, concentration=concentration, DekelConcentration=DekelConcentration, DekelSlope=DekelSlope, VirialOverdensity=VirialOverdensity, MaxCircularVelocity=MaxCircularVelocity, StellarMass=StellarMass, StellarSize=StellarSize, coordinates=coordinates, ) #---on-screen prints M0 = mass[0, 0] m0 = mass[:, 0][1:] Ms0 = StellarMass[0, 0] ms0 = StellarMass[:, 0][1:] msk = (m0 > 1e-4 * M0) & (m0 < M0) # <<< latter condition to be removed fsub = m0[msk].sum() / M0 fstar = ms0[msk].sum() / Ms0 MAH = mass[0, :] iz50 = aux.FindNearestIndex(MAH, 0.5 * M0) z50 = redshift[iz50] time_end_tmp = time.time() print(' %s: %5.2f min, z50=%5.2f,fsub(>1e-4)=%8.5f,fstar=%8.5f'%\ (outfile,(time_end_tmp-time_start_tmp)/60.,z50,fsub,fstar))