# The goal of this program is to read in the WebSky cluster catalog # and produce an enmap for the given geometry, frequency and beam. # We want to reproduce what Nemo does, but with my cluster profile # evaluation code import argparse parser = argparse.ArgumentParser() parser.add_argument("halos", help="Raw WebSky catalog pkcs file") parser.add_argument("geometry",help="Geometry template. A file containing (at least) a fits header specifying the map shape and projection to use") parser.add_argument("ofile", help="Output map") parser.add_argument("-f", "--freq", type=float, default=98, help="Frequency to simulate at") parser.add_argument("-b", "--beam", type=str, default="2.1", help="The beam to use. Either a number, which is interpreted as a FWHM in arcmin, or a file name, which should be to a beam transform file with the format [l,b(l)]") parser.add_argument("-n", "--nhalo", type=int, default=0, help="Number of halos to use. 0 for unlimited") parser.add_argument("-B", "--bsize", type=int, default=100000) parser.add_argument("-V", "--vmin", type=float, default=0.001) args = parser.parse_args() import numpy as np, pyccl, time from astropy.io import fits from pixell import enmap, utils, bunch, pointsrcs from enlib import mpi, clusters dtype = np.float32 # only float32 supported by fast srcsim comm = mpi.COMM_WORLD bsize = args.bsize margin = 100 beta_range = [-14,-3] # This is the profile cutoff in µK. It defaults to 1e-3, i.e. 1 nK. This # should be much lower than necessary, but I set it this low because the # profile building step is the bottleneck for most objects anyway. If that # step could be sped up, then this program could be made much faster by increasing # this number. But for now it's practically free to keep it this low. vmin = args.vmin nhalo = clusters.websky_pkcs_nhalo(args.halos) if args.nhalo: nhalo = min(nhalo, args.nhalo) cosmology = pyccl.Cosmology(Omega_c=0.2589, Omega_b=0.0486, h=0.6774, sigma8=0.8159, n_s=0.9667, transfer_function="boltzmann_camb") shape, wcs = enmap.read_map_geometry(args.geometry) freq = args.freq*1e9 omap = enmap.zeros(shape[-2:], wcs, dtype) rht = utils.RadialFourierTransform() # Read the beam from one of the two formats try: sigma = float(args.beam)*utils.fwhm*utils.arcmin lbeam = np.exp(-0.5*rht.l**2*sigma**2) except ValueError: l, bl = np.loadtxt(args.beam, usecols=(0,1), ndmin=2).T bl /= np.max(bl) lbeam = np.interp(rht.l, l, bl) prof_builder= clusters.ProfileBattagliaFast(cosmology=cosmology, beta_range=beta_range) mass_interp = clusters.MdeltaTranslator(cosmology) # We use this to decide if it's worth it to prune # objects outide our patch or now. This pruning takes # some extra calculations which aren't necessary if we're # fullsky or close to it fullsky = enmap.area(shape, wcs)/(4*np.pi) > 0.8 # Loop over halos nblock = (nhalo+bsize-1)//bsize tget = 0 tprof = 0 tpaint = 0 ntot = 0 for bi in range(comm.rank, nblock, comm.size): i1 = bi*bsize i2 = min((bi+1)*bsize, nhalo) t1 = time.time() data = clusters.websky_pkcs_read(args.halos, num=i2-i1, offset=i1) # Prune the ones outside our area if not fullsky: pixs = enmap.sky2pix(shape, wcs, utils.rect2ang(data.T[:3])[::-1]) good = np.all((pixs >= -margin) & (pixs < np.array(shape[-2:])[:,None]+margin), 0) data = data[good] ngood = len(data) if ngood == 0: continue # Compute physical quantities cat = clusters.websky_decode(data, cosmology, mass_interp); del data t2 = time.time(); tget = t2-t1 # Evaluate the y profile rprofs = prof_builder.y(cat.m200[:,None], cat.z[:,None], rht.r) # convolve with beam lprofs = rht.real2harm(rprofs) lprofs *= lbeam rprofs = rht.harm2real(lprofs) r, rprofs = rht.unpad(rht.r, rprofs) # and factor out peak value yamps = rprofs[:,0].copy() rprofs /= yamps[:,None] # Prepare for painting amps = (yamps * utils.tsz_spectrum(freq) / utils.dplanck(freq) * 1e6).astype(dtype) poss = np.array([cat.dec,cat.ra]).astype(dtype) profiles = [np.array([r,prof]).astype(dtype) for prof in rprofs]; del rprofs prof_ids = np.arange(len(profiles)).astype(np.int32) # And paint ntot += ngood t3 = time.time(); tprof = t3-t2 pointsrcs.sim_objects(shape, wcs, poss, amps, profiles, prof_ids=prof_ids, omap=omap, vmin=vmin) t4 = time.time(); tpaint = t4-t3 # Print our status print("%3d %4d/%d ndone %6.1fk get %6.3f ms prof %6.3f ms draw %6.3f tot %6.3f each maxamp %7.2f" % ( comm.rank, bi+1, nblock, ntot/1e3, tget/ngood*1e3, tprof/ngood*1e3, tpaint/ngood*1e3, (tget+tprof+tpaint)/ngood*1e3, np.max(np.abs(amps)))) print("%4d Reducing" % comm.rank) if comm.size > 1: comm.Barrier() if comm.rank == 0: omap_full = np.zeros_like(omap) comm.Reduce(omap, omap_full) omap = omap_full else: comm.Reduce(omap, None) del omap comm.Barrier() if comm.rank == 0: enmap.write_map(args.ofile, omap) print("Done")