def FieldType(ns): """ Construct and return a `Field`: a tuple of (`DataSource`, `Painter`, `Transfer`) Notes ----- * the default Painter is set to `DefaultPainter` * the default Transfer chain is set to [`NormalizeDC`, `RemoveDC`, `AnisotropicCIC`] Parameters ---------- fields_dict : OrderedDict an ordered dictionary where the keys are Plugin names and the values are instantiated Plugins Returns ------- field : list list of (DataSource, Painter, Transfer) """ # define the default Painter and Transfer default_painter = Painter.create("DefaultPainter") default_transfer = [Transfer.create(x) for x in ['NormalizeDC', 'RemoveDC', 'AnisotropicCIC']] # start with a default option for (DataSource, Painter, Transfer) field = [None, default_painter, default_transfer] # set the DataSource if 'DataSource' not in ns: raise ValueError("exactly one `DataSource` per field must be specified") field[0] = getattr(ns, 'DataSource') # set the Painter if 'Painter' in ns: field[1] = getattr(ns, 'Painter') # set the Transfer if 'Transfer' in ns: field[2] = getattr(ns, 'Transfer') return field
def compute_bianchi_poles(comm, max_ell, catalog, Nmesh, factor_hexadecapole=False, paintbrush='cic'): """ Use the algorithm detailed in Bianchi et al. 2015 to compute and return the 3D power spectrum multipoles (`ell = [0, 2, 4]`) from one input field, which contains non-trivial survey geometry. The estimator uses the FFT algorithm outlined in Bianchi et al. 2015 (http://adsabs.harvard.edu/abs/2015arXiv150505341B) to compute the monopole, quadrupole, and hexadecapole Parameters ---------- comm : MPI.Communicator the communicator to pass to the ParticleMesh object max_ell : int, {0, 2, 4} the maximum multipole number to compute up to (inclusive) catalog : :class:`~nbodykit.fkp.FKPCatalog` the FKP catalog object that manipulates the data and randoms DataSource objects and paints the FKP density Nmesh : int the number of cells (per side) in the gridded mesh factor_hexadecapole : bool, optional if `True`, use the factored expression for the hexadecapole (ell=4) from eq. 27 of Scoccimarro 2015 (1506.02729); default is `False` paintbrush : str, {'cic', 'tsc'} the density assignment kernel to use when painting, either `cic` (2nd order) or `tsc` (3rd order); default is `cic` Returns ------- pm : ParticleMesh the mesh object used to do painting, FFTs, etc result : list of arrays list of 3D complex arrays holding power spectrum multipoles; respectively, if `ell_max=0,2,4`, the list holds the monopole only, monopole and quadrupole, or the monopole, quadrupole, and hexadecapole stats : dict dict holding the statistics of the input fields, as returned by the `FKPPainter` painter References ---------- * Bianchi, Davide et al., `Measuring line-of-sight-dependent Fourier-space clustering using FFTs`, MNRAS, 2015 * Scoccimarro, Roman, `Fast estimators for redshift-space clustering`, Phys. Review D, 2015 """ from pmesh.pm import ParticleMesh, RealField rank = comm.rank bianchi_transfers = [] # the painting kernel transfer if paintbrush == 'cic': transfer = Transfer.create('AnisotropicCIC') elif paintbrush == 'tsc': transfer = Transfer.create('AnisotropicTSC') else: raise ValueError("valid `paintbrush` values are: ['cic', 'tsc']") # determine which multipole values we are computing if max_ell not in [0, 2, 4]: raise ValueError( "valid values for the maximum multipole number are [0, 2, 4]") ells = numpy.arange(0, max_ell + 1, 2) # determine kernels needed to compute quadrupole if max_ell > 0: # the (i,j) index values for each kernel k2 = [(0, 0), (1, 1), (2, 2), (0, 1), (0, 2), (1, 2)] # the amplitude of each kernel term a2 = [1.] * 3 + [2.] * 3 bianchi_transfers.append((a2, k2)) # determine kernels needed to compute hexadecapole if max_ell > 2 and not factor_hexadecapole: # the (i,j,k) index values for each kernel k4 = [(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 1), (0, 0, 2), (1, 1, 0), (1, 1, 2), (2, 2, 0), (2, 2, 1), (0, 1, 1), (0, 2, 2), (1, 2, 2), (0, 1, 2), (1, 0, 2), (2, 0, 1)] # the amplitude of each kernel term a4 = [1.] * 3 + [4.] * 6 + [6.] * 3 + [12.] * 3 bianchi_transfers.append((a4, k4)) # load the data/randoms, setup boxsize, etc from the FKPCatalog with catalog: # the mean coordinate offset of the input data offset = catalog.mean_coordinate_offset # initialize the particle mesh pm = ParticleMesh(BoxSize=catalog.BoxSize, Nmesh=[Nmesh] * 3, dtype='f4', comm=comm) # paint the FKP density field to the mesh (paints: data - randoms, essentially) real, stats = catalog.paint(pm, paintbrush=paintbrush) # save the painted density field for later density = real.copy() if rank == 0: logger.info('%s painting done' % paintbrush) # FFT density field and apply the paintbrush window transfer kernel complex = real.r2c() transfer(pm, complex) if rank == 0: logger.info('ell = 0 done; 1 r2c completed') # monopole A0 is just the FFT of the FKP density field volume = pm.BoxSize.prod() A0 = complex[:] * volume # normalize with a factor of volume # store the A0, A2, A4 arrays here result = [] result.append(A0) # the real-space grid points # the grid is properly offset from [-L/2, L/2] to the original positions in space # this is the grid used when applying Bianchi kernels cell_size = pm.BoxSize / pm.Nmesh xgrid = [(ri + 0.5) * cell_size[i] + offset[i] for i, ri in enumerate(pm.r)] # loop over the higher order multipoles (ell > 0) start = time.time() for iell, ell in enumerate(ells[1:]): # temporary array to hold sum of all of the terms in Fourier space Aell_sum = numpy.zeros_like(complex) # loop over each kernel term for this multipole for amp, integers in zip(*bianchi_transfers[iell]): # reset the realspace mesh to the original FKP density real[:] = density[:] # apply the real-space Bianchi kernel if rank == 0: logger.debug("applying real-space Bianchi transfer for %s..." % str(integers)) apply_bianchi_kernel(real, xgrid, *integers) if rank == 0: logger.debug('...done') # do the real-to-complex FFT if rank == 0: logger.debug("performing r2c...") real.r2c(out=complex) if rank == 0: logger.debug('...done') # apply the Fourier-space Bianchi kernel if rank == 0: logger.debug( "applying Fourier-space Bianchi transfer for %s..." % str(integers)) apply_bianchi_kernel(complex, pm.k, *integers) if rank == 0: logger.debug('...done') # and this contribution to the total sum Aell_sum[:] += amp * complex[:] * volume # apply the paintbrush window transfer function and save transfer(pm, Aell_sum) result.append(Aell_sum) del Aell_sum # delete temp array since appending to list makes copy # log the total number of FFTs computed for each ell if rank == 0: args = (ell, len(bianchi_transfers[iell][0])) logger.info('ell = %d done; %s r2c completed' % args) # density array no longer needed del density # summarize how long it took stop = time.time() if rank == 0: logger.info("higher order multipoles computed in elapsed time %s" % timer(start, stop)) if factor_hexadecapole: logger.info("using factorized hexadecapole estimator for ell=4") # proper normalization: same as equation 49 of Scoccimarro et al. 2015 norm = 1.0 / stats['A_ran'] # reuse memory for output arrays P0 = result[0] if max_ell > 0: P2 = result[1] if max_ell > 2: P4 = numpy.empty_like(P2) if factor_hexadecapole else result[2] # calculate the power spectrum multipoles, slab-by-slab to save memory for islab in range(len(P0)): # save arrays for reuse P0_star = (P0[islab]).conj() if max_ell > 0: P2_star = (P2[islab]).conj() # hexadecapole if max_ell > 2: # see equation 8 of Bianchi et al. 2015 if not factor_hexadecapole: P4[islab, ...] = norm * 9. / 8. * P0[islab] * ( 35. * (P4[islab]).conj() - 30. * P2_star + 3. * P0_star) # see equation 48 of Scoccimarro et al; 2015 else: P4[islab, ...] = norm * 9. / 8. * ( 35. * P2[islab] * P2_star + 3. * P0[islab] * P0_star - 5. / 3. * (11. * P0[islab] * P2_star + 7. * P2[islab] * P0_star)) # quadrupole: equation 7 of Bianchi et al. 2015 if max_ell > 0: P2[islab, ...] = norm * 5. / 2. * P0[islab] * (3. * P2_star - P0_star) # monopole: equation 6 of Bianchi et al. 2015 P0[islab, ...] = norm * P0[islab] * P0_star return pm, result, stats
def compute_bianchi_poles(comm, max_ell, catalog, Nmesh, factor_hexadecapole=False, paintbrush='cic'): """ Use the algorithm detailed in Bianchi et al. 2015 to compute and return the 3D power spectrum multipoles (`ell = [0, 2, 4]`) from one input field, which contains non-trivial survey geometry. The estimator uses the FFT algorithm outlined in Bianchi et al. 2015 (http://adsabs.harvard.edu/abs/2015arXiv150505341B) to compute the monopole, quadrupole, and hexadecapole Parameters ---------- comm : MPI.Communicator the communicator to pass to the ParticleMesh object max_ell : int, {0, 2, 4} the maximum multipole number to compute up to (inclusive) catalog : :class:`~nbodykit.fkp.FKPCatalog` the FKP catalog object that manipulates the data and randoms DataSource objects and paints the FKP density Nmesh : int the number of cells (per side) in the gridded mesh factor_hexadecapole : bool, optional if `True`, use the factored expression for the hexadecapole (ell=4) from eq. 27 of Scoccimarro 2015 (1506.02729); default is `False` paintbrush : str, {'cic', 'tsc'} the density assignment kernel to use when painting, either `cic` (2nd order) or `tsc` (3rd order); default is `cic` Returns ------- pm : ParticleMesh the mesh object used to do painting, FFTs, etc result : list of arrays list of 3D complex arrays holding power spectrum multipoles; respectively, if `ell_max=0,2,4`, the list holds the monopole only, monopole and quadrupole, or the monopole, quadrupole, and hexadecapole stats : dict dict holding the statistics of the input fields, as returned by the `FKPPainter` painter References ---------- * Bianchi, Davide et al., `Measuring line-of-sight-dependent Fourier-space clustering using FFTs`, MNRAS, 2015 * Scoccimarro, Roman, `Fast estimators for redshift-space clustering`, Phys. Review D, 2015 """ from pmesh.pm import ParticleMesh, RealField rank = comm.rank bianchi_transfers = [] # the painting kernel transfer if paintbrush == 'cic': transfer = Transfer.create('AnisotropicCIC') elif paintbrush == 'tsc': transfer = Transfer.create('AnisotropicTSC') else: raise ValueError("valid `paintbrush` values are: ['cic', 'tsc']") # determine which multipole values we are computing if max_ell not in [0, 2, 4]: raise ValueError("valid values for the maximum multipole number are [0, 2, 4]") ells = numpy.arange(0, max_ell+1, 2) # determine kernels needed to compute quadrupole if max_ell > 0: # the (i,j) index values for each kernel k2 = [(0, 0), (1, 1), (2, 2), (0, 1), (0, 2), (1, 2)] # the amplitude of each kernel term a2 = [1.]*3 + [2.]*3 bianchi_transfers.append((a2, k2)) # determine kernels needed to compute hexadecapole if max_ell > 2 and not factor_hexadecapole: # the (i,j,k) index values for each kernel k4 = [(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 0, 1), (0, 0, 2), (1, 1, 0), (1, 1, 2), (2, 2, 0), (2, 2, 1), (0, 1, 1), (0, 2, 2), (1, 2, 2), (0, 1, 2), (1, 0, 2), (2, 0, 1)] # the amplitude of each kernel term a4 = [1.]*3 + [4.]*6 + [6.]*3 + [12.]*3 bianchi_transfers.append((a4, k4)) # load the data/randoms, setup boxsize, etc from the FKPCatalog with catalog: # the mean coordinate offset of the input data offset = catalog.mean_coordinate_offset # initialize the particle mesh pm = ParticleMesh(BoxSize=catalog.BoxSize, Nmesh=[Nmesh]*3, dtype='f4', comm=comm) # paint the FKP density field to the mesh (paints: data - randoms, essentially) real, stats = catalog.paint(pm, paintbrush=paintbrush) # save the painted density field for later density = real.copy() if rank == 0: logger.info('%s painting done' %paintbrush) # FFT density field and apply the paintbrush window transfer kernel complex = real.r2c() transfer(pm, complex) if rank == 0: logger.info('ell = 0 done; 1 r2c completed') # monopole A0 is just the FFT of the FKP density field volume = pm.BoxSize.prod() A0 = complex[:]*volume # normalize with a factor of volume # store the A0, A2, A4 arrays here result = [] result.append(A0) # the real-space grid points # the grid is properly offset from [-L/2, L/2] to the original positions in space # this is the grid used when applying Bianchi kernels cell_size = pm.BoxSize / pm.Nmesh xgrid = [(ri+0.5)*cell_size[i] + offset[i] for i, ri in enumerate(pm.r)] # loop over the higher order multipoles (ell > 0) start = time.time() for iell, ell in enumerate(ells[1:]): # temporary array to hold sum of all of the terms in Fourier space Aell_sum = numpy.zeros_like(complex) # loop over each kernel term for this multipole for amp, integers in zip(*bianchi_transfers[iell]): # reset the realspace mesh to the original FKP density real[:] = density[:] # apply the real-space Bianchi kernel if rank == 0: logger.debug("applying real-space Bianchi transfer for %s..." %str(integers)) apply_bianchi_kernel(real, xgrid, *integers) if rank == 0: logger.debug('...done') # do the real-to-complex FFT if rank == 0: logger.debug("performing r2c...") real.r2c(out=complex) if rank == 0: logger.debug('...done') # apply the Fourier-space Bianchi kernel if rank == 0: logger.debug("applying Fourier-space Bianchi transfer for %s..." %str(integers)) apply_bianchi_kernel(complex, pm.k, *integers) if rank == 0: logger.debug('...done') # and this contribution to the total sum Aell_sum[:] += amp*complex[:]*volume # apply the paintbrush window transfer function and save transfer(pm, Aell_sum) result.append(Aell_sum); del Aell_sum # delete temp array since appending to list makes copy # log the total number of FFTs computed for each ell if rank == 0: args = (ell, len(bianchi_transfers[iell][0])) logger.info('ell = %d done; %s r2c completed' %args) # density array no longer needed del density # summarize how long it took stop = time.time() if rank == 0: logger.info("higher order multipoles computed in elapsed time %s" %timer(start, stop)) if factor_hexadecapole: logger.info("using factorized hexadecapole estimator for ell=4") # proper normalization: same as equation 49 of Scoccimarro et al. 2015 norm = 1.0 / stats['A_ran'] # reuse memory for output arrays P0 = result[0] if max_ell > 0: P2 = result[1] if max_ell > 2: P4 = numpy.empty_like(P2) if factor_hexadecapole else result[2] # calculate the power spectrum multipoles, slab-by-slab to save memory for islab in range(len(P0)): # save arrays for reuse P0_star = (P0[islab]).conj() if max_ell > 0: P2_star = (P2[islab]).conj() # hexadecapole if max_ell > 2: # see equation 8 of Bianchi et al. 2015 if not factor_hexadecapole: P4[islab, ...] = norm * 9./8. * P0[islab] * (35.*(P4[islab]).conj() - 30.*P2_star + 3.*P0_star) # see equation 48 of Scoccimarro et al; 2015 else: P4[islab, ...] = norm * 9./8. * ( 35.*P2[islab]*P2_star + 3.*P0[islab]*P0_star - 5./3.*(11.*P0[islab]*P2_star + 7.*P2[islab]*P0_star) ) # quadrupole: equation 7 of Bianchi et al. 2015 if max_ell > 0: P2[islab, ...] = norm * 5./2. * P0[islab] * (3.*P2_star - P0_star) # monopole: equation 6 of Bianchi et al. 2015 P0[islab, ...] = norm * P0[islab] * P0_star return pm, result, stats