Skip to content

Foregrounds

Add foreground emission to datacubes.

ForegroundModel

Source code in fastbox/foregrounds.py
class ForegroundModel(object):

    def __init__(self, box):
        """
        An object to manage the addition of foregrounds on top of a realisation 
        of a density field in a box.

        Parameters:
            box (CosmoBox):
                Object containing a simulation box.
        """
        self.box = box


    def realise_foreground_amp(self, amp, beta, monopole, smoothing_scale=None, 
                               redshift=None):
        """
        Create realisation of the matter power spectrum by randomly sampling 
        from Gaussian distributions of variance P(k) for each k mode.

        Parameters:
            amp (float):
                Amplitude of foreground power spectrum, in units of the field 
                squared (e.g. if the field is in units of mK, this should be in 
                [mK]^2).

            beta (float):
                Angular power-law index.

            monopole (float):
                Zero-point offset of the foreground at the reference frequency. 
                Should be in the same units as the field, e.g. mK.

            smoothing_scale (float, optional):
                Additional angular smoothing scale, in degrees. Default: None 
                (no smoothing). 

            redshift (float, optional):
                Redshift to evaluate the centre of the box at. Default: Same value 
                as `self.box.redshift`.
        """
        # Check redshift
        if redshift is None:
            redshift = self.box.redshift
        scale_factor = 1. / (1. + redshift)

        # Calculate comoving distance to box redshift
        r = ccl.comoving_angular_distance(self.box.cosmo, scale_factor)

        # Angular Fourier modes (in 2D)
        k_perp = 2.*np.pi * np.sqrt(  (self.box.Kx[:,:,0]/self.box.Lx)**2. 
                                    + (self.box.Ky[:,:,0]/self.box.Ly)**2.)

        # Foreground angular power spectrum
        # \ell ~ k_perp r / 2
        # Use FG power spectrum model from Santos et al. (2005)
        C_ell = amp * (0.5*k_perp*r / 1000.)**(beta)
        C_ell[np.isinf(C_ell)] = 0. # Remove inf at k=0

        # Normalise the power spectrum properly (factor of area, and norm. 
        # factor of 2D DFT)
        C_ell *= (self.box.N**4.) / (self.box.Lx * self.box.Ly)

        # Generate Gaussian random field with given power spectrum
        re = np.random.normal(0.0, 1.0, np.shape(k_perp))
        im = np.random.normal(0.0, 1.0, np.shape(k_perp))
        fg_k = (re + 1.j*im) * np.sqrt(C_ell) # factor of 2x larger variance
        fg_k[k_perp==0.] = 0. # remove zero mode

        # Transform to real space. Discarding the imag part fixes the extra 
        # factor of 2x in variance from above
        fg_x = fft.ifftn(fg_k).real + monopole

        # Apply angular smoothing
        if smoothing_scale is not None:
            ang_x, ang_y = self.box.pixel_array(redshift=redshift)
            sigma = smoothing_scale / (ang_x[1] - ang_x[0])
            fg_x = scipy.ndimage.gaussian_filter(fg_x, sigma=sigma, mode='wrap')

        return fg_x


    def realise_spectral_index(self, mean_spec_idx, std_spec_idx, 
                               smoothing_scale, redshift=None):
        """
        Generate a Gaussian random realisation of the spectral index and apply 
        a smoothing scale.

        Parameters:
            mean_spec_idx (float):
                Mean value of the spectral index.

            std_spec_idx (float):
                Standard deviation of the spectral index.

            smoothing_scale (float):
                Angular smoothing scale, in degrees.

            redshift (float, optional):
                Redshift to evaluate the centre of the box at. Default: Same value 
                as `self.box.redshift`.
        """
        # Generate uncorrelated Gaussian random field
        alpha = np.random.normal(mean_spec_idx, std_spec_idx, 
                                 self.box.Kx[:,:,0].shape)

        # Smooth with isotropic Gaussian
        ang_x, ang_y = self.box.pixel_array(redshift=redshift)
        sigma = smoothing_scale / (ang_x[1] - ang_x[0])
        alpha = scipy.ndimage.gaussian_filter(alpha, sigma=sigma, mode='wrap')
        return alpha


    def construct_cube(self, amps, spectral_idx, freq_ref=130., redshift=None):
        """
        Construct a foreground datacube from an input 2D amplitude map and 
        spectral index map.

        Parameters:
            amps (array_like):
                2D array of amplitudes.

            spectral_index (array_like):
                2D array of spectral indices, or float.

            freq_ref (float, optional):
                Reference frequency, in MHz.

            redshift (float, optional):
                Redshift to evaluate the centre of the box at. Default: Same value 
                as `self.box.redshift`.
        """
        # Get frequency array and scaling
        freqs = self.box.freq_array(redshift=redshift)
        if isinstance(spectral_idx, float):
            ffac = ((freqs / freq_ref)**spectral_idx)[np.newaxis,np.newaxis,:]
        else:
            ffac = (freqs/freq_ref)[np.newaxis,np.newaxis,:]**spectral_idx[:,:,np.newaxis]

        # Return datacube
        return amps[:,:,np.newaxis] * ffac

__init__(self, box) special

An object to manage the addition of foregrounds on top of a realisation of a density field in a box.

Parameters:

Name Type Description Default
box CosmoBox

Object containing a simulation box.

required
Source code in fastbox/foregrounds.py
def __init__(self, box):
    """
    An object to manage the addition of foregrounds on top of a realisation 
    of a density field in a box.

    Parameters:
        box (CosmoBox):
            Object containing a simulation box.
    """
    self.box = box

construct_cube(self, amps, spectral_idx, freq_ref=130.0, redshift=None)

Construct a foreground datacube from an input 2D amplitude map and spectral index map.

Parameters:

Name Type Description Default
amps array_like

2D array of amplitudes.

required
spectral_index array_like

2D array of spectral indices, or float.

required
freq_ref float

Reference frequency, in MHz.

130.0
redshift float

Redshift to evaluate the centre of the box at. Default: Same value as self.box.redshift.

None
Source code in fastbox/foregrounds.py
def construct_cube(self, amps, spectral_idx, freq_ref=130., redshift=None):
    """
    Construct a foreground datacube from an input 2D amplitude map and 
    spectral index map.

    Parameters:
        amps (array_like):
            2D array of amplitudes.

        spectral_index (array_like):
            2D array of spectral indices, or float.

        freq_ref (float, optional):
            Reference frequency, in MHz.

        redshift (float, optional):
            Redshift to evaluate the centre of the box at. Default: Same value 
            as `self.box.redshift`.
    """
    # Get frequency array and scaling
    freqs = self.box.freq_array(redshift=redshift)
    if isinstance(spectral_idx, float):
        ffac = ((freqs / freq_ref)**spectral_idx)[np.newaxis,np.newaxis,:]
    else:
        ffac = (freqs/freq_ref)[np.newaxis,np.newaxis,:]**spectral_idx[:,:,np.newaxis]

    # Return datacube
    return amps[:,:,np.newaxis] * ffac

realise_foreground_amp(self, amp, beta, monopole, smoothing_scale=None, redshift=None)

Create realisation of the matter power spectrum by randomly sampling from Gaussian distributions of variance P(k) for each k mode.

Parameters:

Name Type Description Default
amp float

Amplitude of foreground power spectrum, in units of the field squared (e.g. if the field is in units of mK, this should be in [mK]^2).

required
beta float

Angular power-law index.

required
monopole float

Zero-point offset of the foreground at the reference frequency. Should be in the same units as the field, e.g. mK.

required
smoothing_scale float

Additional angular smoothing scale, in degrees. Default: None (no smoothing).

None
redshift float

Redshift to evaluate the centre of the box at. Default: Same value as self.box.redshift.

None
Source code in fastbox/foregrounds.py
def realise_foreground_amp(self, amp, beta, monopole, smoothing_scale=None, 
                           redshift=None):
    """
    Create realisation of the matter power spectrum by randomly sampling 
    from Gaussian distributions of variance P(k) for each k mode.

    Parameters:
        amp (float):
            Amplitude of foreground power spectrum, in units of the field 
            squared (e.g. if the field is in units of mK, this should be in 
            [mK]^2).

        beta (float):
            Angular power-law index.

        monopole (float):
            Zero-point offset of the foreground at the reference frequency. 
            Should be in the same units as the field, e.g. mK.

        smoothing_scale (float, optional):
            Additional angular smoothing scale, in degrees. Default: None 
            (no smoothing). 

        redshift (float, optional):
            Redshift to evaluate the centre of the box at. Default: Same value 
            as `self.box.redshift`.
    """
    # Check redshift
    if redshift is None:
        redshift = self.box.redshift
    scale_factor = 1. / (1. + redshift)

    # Calculate comoving distance to box redshift
    r = ccl.comoving_angular_distance(self.box.cosmo, scale_factor)

    # Angular Fourier modes (in 2D)
    k_perp = 2.*np.pi * np.sqrt(  (self.box.Kx[:,:,0]/self.box.Lx)**2. 
                                + (self.box.Ky[:,:,0]/self.box.Ly)**2.)

    # Foreground angular power spectrum
    # \ell ~ k_perp r / 2
    # Use FG power spectrum model from Santos et al. (2005)
    C_ell = amp * (0.5*k_perp*r / 1000.)**(beta)
    C_ell[np.isinf(C_ell)] = 0. # Remove inf at k=0

    # Normalise the power spectrum properly (factor of area, and norm. 
    # factor of 2D DFT)
    C_ell *= (self.box.N**4.) / (self.box.Lx * self.box.Ly)

    # Generate Gaussian random field with given power spectrum
    re = np.random.normal(0.0, 1.0, np.shape(k_perp))
    im = np.random.normal(0.0, 1.0, np.shape(k_perp))
    fg_k = (re + 1.j*im) * np.sqrt(C_ell) # factor of 2x larger variance
    fg_k[k_perp==0.] = 0. # remove zero mode

    # Transform to real space. Discarding the imag part fixes the extra 
    # factor of 2x in variance from above
    fg_x = fft.ifftn(fg_k).real + monopole

    # Apply angular smoothing
    if smoothing_scale is not None:
        ang_x, ang_y = self.box.pixel_array(redshift=redshift)
        sigma = smoothing_scale / (ang_x[1] - ang_x[0])
        fg_x = scipy.ndimage.gaussian_filter(fg_x, sigma=sigma, mode='wrap')

    return fg_x

realise_spectral_index(self, mean_spec_idx, std_spec_idx, smoothing_scale, redshift=None)

Generate a Gaussian random realisation of the spectral index and apply a smoothing scale.

Parameters:

Name Type Description Default
mean_spec_idx float

Mean value of the spectral index.

required
std_spec_idx float

Standard deviation of the spectral index.

required
smoothing_scale float

Angular smoothing scale, in degrees.

required
redshift float

Redshift to evaluate the centre of the box at. Default: Same value as self.box.redshift.

None
Source code in fastbox/foregrounds.py
def realise_spectral_index(self, mean_spec_idx, std_spec_idx, 
                           smoothing_scale, redshift=None):
    """
    Generate a Gaussian random realisation of the spectral index and apply 
    a smoothing scale.

    Parameters:
        mean_spec_idx (float):
            Mean value of the spectral index.

        std_spec_idx (float):
            Standard deviation of the spectral index.

        smoothing_scale (float):
            Angular smoothing scale, in degrees.

        redshift (float, optional):
            Redshift to evaluate the centre of the box at. Default: Same value 
            as `self.box.redshift`.
    """
    # Generate uncorrelated Gaussian random field
    alpha = np.random.normal(mean_spec_idx, std_spec_idx, 
                             self.box.Kx[:,:,0].shape)

    # Smooth with isotropic Gaussian
    ang_x, ang_y = self.box.pixel_array(redshift=redshift)
    sigma = smoothing_scale / (ang_x[1] - ang_x[0])
    alpha = scipy.ndimage.gaussian_filter(alpha, sigma=sigma, mode='wrap')
    return alpha

GlobalSkyModel

Source code in fastbox/foregrounds.py
class GlobalSkyModel(object):

    def __init__(self, box):
        """
        An object to manage the addition of foregrounds from pyGDSM on top of a 
        realisation of a density field in a box. Uses the GlobalSkyModel2016 
        class.

        Parameters:
            box (CosmoBox):
                Object containing a simulation box.
        """
        self.box = box

        # Check for imports specific to this class
        try:
            from pygdsm import GlobalSkyModel2016
        except:
            print("pygdsm is not installed")
            raise

        # Initialise GSM
        self.gsm = GlobalSkyModel2016(freq_unit='MHz')


    def construct_cube(self, lat0=0., lon0=0., redshift=None, loop=True, 
                       verbose=True):
        """
        Construct a foreground datacube from GDSM.

        Parameters:
            lat0, lon0 (float, optional):
                Latitude and longitude of the centre of the field in default pyGDSM 
                coordinates (Galactic), in degrees.

            redshift (float, optional):
                Redshift to evaluate the centre of the box at. Default: Same value 
                as `self.box.redshift`.

            loop : bool, optional
                Whether to fetch the GSM maps for all frequency channels at once 
                (False), or to loop through them one by one (True).

            verbose : bool, optional
                If True, print status messages.
        """
        import healpy as hp

        # Initialise empty cube
        fgcube = np.zeros((self.box.N, self.box.N, self.box.N))

        # Get frequency array and scaling
        freqs = self.box.freq_array(redshift=redshift)
        ang_x, ang_y = self.box.pixel_array(redshift=redshift)
        delta_ang_x = np.max(ang_x) - np.min(ang_x)
        delta_ang_y = np.max(ang_y) - np.min(ang_y)

        # Cartesian projection of maps
        npix = self.box.N
        lonra = [lon0 - 0.5*delta_ang_x, lon0 + 0.5*delta_ang_x]
        latra = [lat0 - 0.5*delta_ang_y, lat0 + 0.5*delta_ang_y]
        proj = hp.projector.CartesianProj(lonra=lonra, latra=latra, coord='G',
                                          xsize=npix, ysize=npix)

        # Fetch maps and perform projection
        if loop:
            for i, freq in enumerate(freqs):
                if verbose and i % 10 == 0:
                    print("    Channel %d / %d" % (i, len(freqs)))

                # Get map and project to Cartesian grid
                m = self.gsm.generate(freq)
                nside = hp.npix2nside(m.size)
                fgcube[:,:,i] = proj.projmap(m, vec2pix_func=partial(hp.vec2pix, nside))
        else:
            # Fetch all maps in one go
            maps = self.gsm.generate(freqs)
            nside = hp.npix2nside(maps[0].size)

            # Do projection one channel at a time
            for i, m in enumerate(maps):
                if verbose and i % 10 == 0:
                    print("    Channel %d / %d" % (i, len(freqs)))
                fgcube[:,:,i] = proj.projmap(m, vec2pix_func=partial(hp.vec2pix, nside))

        # Return datacube
        return fgcube

__init__(self, box) special

An object to manage the addition of foregrounds from pyGDSM on top of a realisation of a density field in a box. Uses the GlobalSkyModel2016 class.

Parameters:

Name Type Description Default
box CosmoBox

Object containing a simulation box.

required
Source code in fastbox/foregrounds.py
def __init__(self, box):
    """
    An object to manage the addition of foregrounds from pyGDSM on top of a 
    realisation of a density field in a box. Uses the GlobalSkyModel2016 
    class.

    Parameters:
        box (CosmoBox):
            Object containing a simulation box.
    """
    self.box = box

    # Check for imports specific to this class
    try:
        from pygdsm import GlobalSkyModel2016
    except:
        print("pygdsm is not installed")
        raise

    # Initialise GSM
    self.gsm = GlobalSkyModel2016(freq_unit='MHz')

construct_cube(self, lat0=0.0, lon0=0.0, redshift=None, loop=True, verbose=True)

Construct a foreground datacube from GDSM.

Parameters:

Name Type Description Default
lat0, lon0 (float

Latitude and longitude of the centre of the field in default pyGDSM coordinates (Galactic), in degrees.

required
redshift float

Redshift to evaluate the centre of the box at. Default: Same value as self.box.redshift.

None
loop

bool, optional Whether to fetch the GSM maps for all frequency channels at once (False), or to loop through them one by one (True).

True
verbose

bool, optional If True, print status messages.

True
Source code in fastbox/foregrounds.py
def construct_cube(self, lat0=0., lon0=0., redshift=None, loop=True, 
                   verbose=True):
    """
    Construct a foreground datacube from GDSM.

    Parameters:
        lat0, lon0 (float, optional):
            Latitude and longitude of the centre of the field in default pyGDSM 
            coordinates (Galactic), in degrees.

        redshift (float, optional):
            Redshift to evaluate the centre of the box at. Default: Same value 
            as `self.box.redshift`.

        loop : bool, optional
            Whether to fetch the GSM maps for all frequency channels at once 
            (False), or to loop through them one by one (True).

        verbose : bool, optional
            If True, print status messages.
    """
    import healpy as hp

    # Initialise empty cube
    fgcube = np.zeros((self.box.N, self.box.N, self.box.N))

    # Get frequency array and scaling
    freqs = self.box.freq_array(redshift=redshift)
    ang_x, ang_y = self.box.pixel_array(redshift=redshift)
    delta_ang_x = np.max(ang_x) - np.min(ang_x)
    delta_ang_y = np.max(ang_y) - np.min(ang_y)

    # Cartesian projection of maps
    npix = self.box.N
    lonra = [lon0 - 0.5*delta_ang_x, lon0 + 0.5*delta_ang_x]
    latra = [lat0 - 0.5*delta_ang_y, lat0 + 0.5*delta_ang_y]
    proj = hp.projector.CartesianProj(lonra=lonra, latra=latra, coord='G',
                                      xsize=npix, ysize=npix)

    # Fetch maps and perform projection
    if loop:
        for i, freq in enumerate(freqs):
            if verbose and i % 10 == 0:
                print("    Channel %d / %d" % (i, len(freqs)))

            # Get map and project to Cartesian grid
            m = self.gsm.generate(freq)
            nside = hp.npix2nside(m.size)
            fgcube[:,:,i] = proj.projmap(m, vec2pix_func=partial(hp.vec2pix, nside))
    else:
        # Fetch all maps in one go
        maps = self.gsm.generate(freqs)
        nside = hp.npix2nside(maps[0].size)

        # Do projection one channel at a time
        for i, m in enumerate(maps):
            if verbose and i % 10 == 0:
                print("    Channel %d / %d" % (i, len(freqs)))
            fgcube[:,:,i] = proj.projmap(m, vec2pix_func=partial(hp.vec2pix, nside))

    # Return datacube
    return fgcube

PlanckSkyModel

Source code in fastbox/foregrounds.py
class PlanckSkyModel(object):

    def __init__(self, box, free_idx=-2.1, planck_sim_paths=DEFAULT_PLANCK_SIM_PATHS):
        """
        An object to manage the PSM foreground model.

        Parameters:
            box (CosmoBox):
                Object containing a simulation box.

            free_idx (float, optional):
                Spectral index of the free-free component. Default: -2.1.

            planck_sim_paths (dict):
                Dict containing paths to Planck simulations used by the sky model. 
                Must have keys 'ff217', 'sync217', 'sync353', which are paths to 
                .fits files containing the free-free and synchrotron temperature 
                maps at 217 / 353 GHz as appropriate. 
                Default: ``fastbox.foregrounds.DEFAULT_PLANCK_SIM_PATHS``
        """
        # Try to import healpy
        try:
            import healpy as hp
        except:
            print("healpy is not installed")
            raise

        self.box = box

        # Store spectral index parameters
        self.free_idx = free_idx

        # Store filename dict
        for key in ['ff217', 'sync217', 'sync353']:
            # Check that key exists
            assert key in planck_sim_paths.keys(), \
                "planck_sim_paths argument is missing compulsory key '%s'" % key

            # Check that file exists
            f = planck_sim_paths[key]
            if not os.path.exists(f):
                raise ValueError("Could not find file '%s' for key '%s'" % (f, key))
        self.planck_sim_paths = planck_sim_paths


    def planck_corr(self, freq_ghz):
        """Correction factor to convert T_CMB to T_RJ.

        Parameters:
            freq_ghz (float):
                Frequency, in GHz.

        Returns:
            correction (float):
                Correction factor; divide a T_CMB quantity by this to obtain T_RJ.
        """
        freq = freq_ghz * 1e9 # Hz
        factor = H_PLANCK * freq / (KBOLTZ * CMB_TEMP)
        correction = (np.exp(factor)-1.)**2. / (factor**2. * np.exp(factor))
        return correction


    def read_planck_sim_maps(self):
        """Read Planck simulation maps for synchrotron and free-free emission.

        Reads the Planck simulation maps specified in ``self.planck_sim_paths`` 
        as Healpix maps, and converts from T_CMB to T_RJ units.

        Returns:
            free217, sync217, sync353 (array_like):
                Healpix map arrays for each simulation map.
        """
        # Load maps and convert from T_CMB to T_RJ
        free217 = hp.fitsfunc.read_map(self.planck_sim_paths['ff217'], 
                                       field=0, nest=False) \
                / self.planck_corr(217.)
        sync217 = hp.fitsfunc.read_map(self.planck_sim_paths['sync217'], 
                                       field=0, nest=False) \
                / self.planck_corr(217.)
        sync353 = hp.fitsfunc.read_map(self.planck_sim_paths['sync353'], 
                                       field=0, nest=False) \
                / self.planck_corr(353.)
        return free217, sync217, sync353


    def synch_freefree_maps(self, redshift=None, rotation=(0., -62., 0.), 
                            ref_freq=1000., free_idx=None, seed_syncidx=None):
        """Calculate free-free and synchrotron amplitude and spectral index maps.

        Uses a set of Planck simulations to calculate maps of the synchrotron 
        and free-free amplitude, and the synchrotron spectral index, on a 
        square patch of sky.

        On small scales (below 5 degrees), the synchrotron spectral index is 
        generated from a random Gaussian distribution.

        Parameters:
            redshift (float, optional):
                Redshift to evaluate the model at. Default: None (uses box redshift).

            rotation (tuple, optional):
                Rotation of the field from Galactic coordinates, used by healpy 
                ``gnomview`` when projecting the field.

            ref_freq (float, optional):
                Reference frequency to evaluate the amplitudes at, in MHz.

            free_idx (float, optional):
                If set, replace the default free-free index with this value. 

            seed_syncidx (int, optional):
                Random seed to use when generating the small-scale spectral index 
                fluctuations.

        Returns:
            sync_amp, free_amp, sync_idx (array_like): Synchrotron and free-free 
                amplitudes and synchrotron spectral index.

            - ``sync_amp, free_amp (array_like)``:
                Synchrotron and free-free amplitude maps, evaluated at the 
                reference frequency.

            - ``sync_idx (array_like)``:
                Synchrotron spectral index map.
        """
        # Get frequency and angular pixel coords
        ang_x, ang_y = self.box.pixel_array(redshift=redshift)
        xside = len(ang_x)
        yside = len(ang_y)

        # Read Planck simulated foreground maps
        free217, sync217, sync353 = self.read_planck_sim_maps()

        # Get rid of unphysical values
        free217[np.where(free217 < 0.)[0]] = np.percentile(free217, 3)

        # Get free-free spectral index
        if free_idx is None:
            free_idx = self.free_idx

        # Calculate synchrotron spectral index
        sync_idx = np.log(sync353/ sync217) / np.log(353./217.)

        # Calculate component amplitudes (freqs -> GHz)
        sync_amp = sync217 * ((ref_freq/1000.)/217.)**(sync_idx)
        free_amp = free217 * ((ref_freq/1000.)/217.)**(free_idx)

        # Fill-in small-scale structure for synch spectral index map (which is 
        # MAMD's map at 5 deg)
        ells = np.arange(1., 4001.)
        cl5deg = hp.sphtfunc.anafast(
                        np.random.normal(0.0, np.std(sync_idx), 12*2048*2048), 
                        lmax=4000)

        # Synchrotron angular power spectrum
        # Taken from https://arxiv.org/pdf/astro-ph/0408515.pdf
        cls = cl5deg[0] * (1000./ells)**2.4
        np.random.seed(seed_syncidx)
        sync_idx = sync_idx + hp.sphtfunc.synfast(cls, 2048)

        # Map resolution parameters
        nside = hp.get_nside(sync_idx)      
        reso_arcmin = hp.nside2resol(nside, arcmin=True)
        nxpix = np.int(np.ceil(54.1*60./reso_arcmin))
        nypix = np.int(np.ceil(54.1*60./reso_arcmin))

        # Project synchrotron amplitude map from sphere to rectangular grid
        sync_amp= hp.visufunc.gnomview(sync_amp, coord='G', rot=rotation, 
                                       xsize=nxpix, ysize=nypix, 
                                       reso=reso_arcmin, flip='astro', 
                                       return_projected_map=True)
        plt.close()
        sync_amp = sync_amp[::-1] # reverse order

        # Resample synch. amplitudes onto grid with desired resolution
        zoom_param = [xside, yside] / np.array(sync_amp.shape)
        sync_amp = scipy.ndimage.zoom(sync_amp, zoom_param, order=3)

        # Project free-free amplitude map from sphere to rect. grid and resample
        free_amp = hp.visufunc.gnomview(free_amp, coord='G', rot=rotation, 
                                        xsize=nxpix, ysize=nypix, 
                                        reso=reso_arcmin, flip='astro', 
                                        return_projected_map=True)
        plt.close()
        free_amp = free_amp[::-1] # reverse order
        free_amp = scipy.ndimage.zoom(free_amp, zoom_param, order=3)

        # Project synch. spectral idx map from sphere to rect. grid and resample
        sync_idx = hp.visufunc.gnomview(sync_idx, coord='G', rot=rotation, 
                                        xsize=nxpix, ysize=nypix, 
                                        reso=reso_arcmin, flip='astro', 
                                        return_projected_map=True)
        plt.close()
        sync_idx = sync_idx[::-1] # reverse order
        sync_idx = scipy.ndimage.zoom(sync_idx, zoom_param, order=3)

        # Return amplitudes in mK, and synch. spectral index map
        return sync_amp*1e3, free_amp*1e3, sync_idx


    def construct_cube(self, redshift=None, rotation=(0.,-62.,0.), 
                       ref_freq=1000., seed_syncidx=None):
        """Make Planck Sky Model (synchrotron + free-free) data cube for a box.

        Uses the PSM simulations, with power-law synchrotron emission with a 
        spatially-varying spectral index, and free-free emission with a fixed 
        spectral index.

        Parameters:
            redshift (float, optional):
                Redshift to evaluate the model at. Default: None (uses box redshift).

            rotation (tuple, optional):
                Rotation of the field from Galactic coordinates, used by healpy 
                ``gnomview`` when projecting the field.

            ref_freq (float, optional):
                Reference frequency to evaluate the amplitudes at, in MHz. 

            seed_syncidx (int, optional):
                Random seed to use when generating the small-scale spectral index 
                fluctuations.

        Returns:
            fg_cube (array_like):
                Planck Sky Model temperature data cube (in mK).
        """
        # Get frequency array
        freqs = self.box.freq_array(redshift=redshift) # MHz
        x = freqs / ref_freq # dimensionless freq.

        # Calculate synchrotron/free-free amplitude and spectral idx maps
        sync_amp, free_amp, sync_idx \
                = self.synch_freefree_maps(redshift=redshift, 
                                           rotation=rotation, 
                                           ref_freq=ref_freq, 
                                           seed_syncidx=seed_syncidx)

        # Construct component datacubes and return
        fg_map = sync_amp[:,:,np.newaxis] \
                 * x[np.newaxis,np.newaxis,:]**sync_idx[:,:,np.newaxis] \
               + free_amp[:,:,np.newaxis] \
                 * x[np.newaxis,np.newaxis,:]**self.free_idx
        return fg_map

__init__(self, box, free_idx=-2.1, planck_sim_paths={'ff217': 'fastbox/COM_SimMap_freefree-ffp10-skyinbands-217_2048_R3.00_full.fits', 'sync217': 'fastbox/COM_SimMap_synchrotron-ffp10-skyinbands-217_2048_R3.00_full.fits', 'sync353': 'fastbox/COM_SimMap_synchrotron-ffp10-skyinbands-353_2048_R3.00_full.fits'}) special

An object to manage the PSM foreground model.

Parameters:

Name Type Description Default
box CosmoBox

Object containing a simulation box.

required
free_idx float

Spectral index of the free-free component. Default: -2.1.

-2.1
planck_sim_paths dict

Dict containing paths to Planck simulations used by the sky model. Must have keys 'ff217', 'sync217', 'sync353', which are paths to .fits files containing the free-free and synchrotron temperature maps at 217 / 353 GHz as appropriate. Default: fastbox.foregrounds.DEFAULT_PLANCK_SIM_PATHS

{'ff217': 'fastbox/COM_SimMap_freefree-ffp10-skyinbands-217_2048_R3.00_full.fits', 'sync217': 'fastbox/COM_SimMap_synchrotron-ffp10-skyinbands-217_2048_R3.00_full.fits', 'sync353': 'fastbox/COM_SimMap_synchrotron-ffp10-skyinbands-353_2048_R3.00_full.fits'}
Source code in fastbox/foregrounds.py
def __init__(self, box, free_idx=-2.1, planck_sim_paths=DEFAULT_PLANCK_SIM_PATHS):
    """
    An object to manage the PSM foreground model.

    Parameters:
        box (CosmoBox):
            Object containing a simulation box.

        free_idx (float, optional):
            Spectral index of the free-free component. Default: -2.1.

        planck_sim_paths (dict):
            Dict containing paths to Planck simulations used by the sky model. 
            Must have keys 'ff217', 'sync217', 'sync353', which are paths to 
            .fits files containing the free-free and synchrotron temperature 
            maps at 217 / 353 GHz as appropriate. 
            Default: ``fastbox.foregrounds.DEFAULT_PLANCK_SIM_PATHS``
    """
    # Try to import healpy
    try:
        import healpy as hp
    except:
        print("healpy is not installed")
        raise

    self.box = box

    # Store spectral index parameters
    self.free_idx = free_idx

    # Store filename dict
    for key in ['ff217', 'sync217', 'sync353']:
        # Check that key exists
        assert key in planck_sim_paths.keys(), \
            "planck_sim_paths argument is missing compulsory key '%s'" % key

        # Check that file exists
        f = planck_sim_paths[key]
        if not os.path.exists(f):
            raise ValueError("Could not find file '%s' for key '%s'" % (f, key))
    self.planck_sim_paths = planck_sim_paths

construct_cube(self, redshift=None, rotation=(0.0, -62.0, 0.0), ref_freq=1000.0, seed_syncidx=None)

Make Planck Sky Model (synchrotron + free-free) data cube for a box.

Uses the PSM simulations, with power-law synchrotron emission with a spatially-varying spectral index, and free-free emission with a fixed spectral index.

Parameters:

Name Type Description Default
redshift float

Redshift to evaluate the model at. Default: None (uses box redshift).

None
rotation tuple

Rotation of the field from Galactic coordinates, used by healpy gnomview when projecting the field.

(0.0, -62.0, 0.0)
ref_freq float

Reference frequency to evaluate the amplitudes at, in MHz.

1000.0
seed_syncidx int

Random seed to use when generating the small-scale spectral index fluctuations.

None

Returns:

Type Description
fg_cube (array_like)

Planck Sky Model temperature data cube (in mK).

Source code in fastbox/foregrounds.py
def construct_cube(self, redshift=None, rotation=(0.,-62.,0.), 
                   ref_freq=1000., seed_syncidx=None):
    """Make Planck Sky Model (synchrotron + free-free) data cube for a box.

    Uses the PSM simulations, with power-law synchrotron emission with a 
    spatially-varying spectral index, and free-free emission with a fixed 
    spectral index.

    Parameters:
        redshift (float, optional):
            Redshift to evaluate the model at. Default: None (uses box redshift).

        rotation (tuple, optional):
            Rotation of the field from Galactic coordinates, used by healpy 
            ``gnomview`` when projecting the field.

        ref_freq (float, optional):
            Reference frequency to evaluate the amplitudes at, in MHz. 

        seed_syncidx (int, optional):
            Random seed to use when generating the small-scale spectral index 
            fluctuations.

    Returns:
        fg_cube (array_like):
            Planck Sky Model temperature data cube (in mK).
    """
    # Get frequency array
    freqs = self.box.freq_array(redshift=redshift) # MHz
    x = freqs / ref_freq # dimensionless freq.

    # Calculate synchrotron/free-free amplitude and spectral idx maps
    sync_amp, free_amp, sync_idx \
            = self.synch_freefree_maps(redshift=redshift, 
                                       rotation=rotation, 
                                       ref_freq=ref_freq, 
                                       seed_syncidx=seed_syncidx)

    # Construct component datacubes and return
    fg_map = sync_amp[:,:,np.newaxis] \
             * x[np.newaxis,np.newaxis,:]**sync_idx[:,:,np.newaxis] \
           + free_amp[:,:,np.newaxis] \
             * x[np.newaxis,np.newaxis,:]**self.free_idx
    return fg_map

planck_corr(self, freq_ghz)

Correction factor to convert T_CMB to T_RJ.

Parameters:

Name Type Description Default
freq_ghz float

Frequency, in GHz.

required

Returns:

Type Description
correction (float)

Correction factor; divide a T_CMB quantity by this to obtain T_RJ.

Source code in fastbox/foregrounds.py
def planck_corr(self, freq_ghz):
    """Correction factor to convert T_CMB to T_RJ.

    Parameters:
        freq_ghz (float):
            Frequency, in GHz.

    Returns:
        correction (float):
            Correction factor; divide a T_CMB quantity by this to obtain T_RJ.
    """
    freq = freq_ghz * 1e9 # Hz
    factor = H_PLANCK * freq / (KBOLTZ * CMB_TEMP)
    correction = (np.exp(factor)-1.)**2. / (factor**2. * np.exp(factor))
    return correction

read_planck_sim_maps(self)

Read Planck simulation maps for synchrotron and free-free emission.

Reads the Planck simulation maps specified in self.planck_sim_paths as Healpix maps, and converts from T_CMB to T_RJ units.

Returns:

Type Description
free217, sync217, sync353 (array_like)

Healpix map arrays for each simulation map.

Source code in fastbox/foregrounds.py
def read_planck_sim_maps(self):
    """Read Planck simulation maps for synchrotron and free-free emission.

    Reads the Planck simulation maps specified in ``self.planck_sim_paths`` 
    as Healpix maps, and converts from T_CMB to T_RJ units.

    Returns:
        free217, sync217, sync353 (array_like):
            Healpix map arrays for each simulation map.
    """
    # Load maps and convert from T_CMB to T_RJ
    free217 = hp.fitsfunc.read_map(self.planck_sim_paths['ff217'], 
                                   field=0, nest=False) \
            / self.planck_corr(217.)
    sync217 = hp.fitsfunc.read_map(self.planck_sim_paths['sync217'], 
                                   field=0, nest=False) \
            / self.planck_corr(217.)
    sync353 = hp.fitsfunc.read_map(self.planck_sim_paths['sync353'], 
                                   field=0, nest=False) \
            / self.planck_corr(353.)
    return free217, sync217, sync353

synch_freefree_maps(self, redshift=None, rotation=(0.0, -62.0, 0.0), ref_freq=1000.0, free_idx=None, seed_syncidx=None)

Calculate free-free and synchrotron amplitude and spectral index maps.

Uses a set of Planck simulations to calculate maps of the synchrotron and free-free amplitude, and the synchrotron spectral index, on a square patch of sky.

On small scales (below 5 degrees), the synchrotron spectral index is generated from a random Gaussian distribution.

Parameters:

Name Type Description Default
redshift float

Redshift to evaluate the model at. Default: None (uses box redshift).

None
rotation tuple

Rotation of the field from Galactic coordinates, used by healpy gnomview when projecting the field.

(0.0, -62.0, 0.0)
ref_freq float

Reference frequency to evaluate the amplitudes at, in MHz.

1000.0
free_idx float

If set, replace the default free-free index with this value.

None
seed_syncidx int

Random seed to use when generating the small-scale spectral index fluctuations.

None

Returns:

Type Description
sync_amp, free_amp, sync_idx (array_like)

Synchrotron and free-free amplitudes and synchrotron spectral index.

  • sync_amp, free_amp (array_like): Synchrotron and free-free amplitude maps, evaluated at the reference frequency.

  • sync_idx (array_like): Synchrotron spectral index map.

Source code in fastbox/foregrounds.py
def synch_freefree_maps(self, redshift=None, rotation=(0., -62., 0.), 
                        ref_freq=1000., free_idx=None, seed_syncidx=None):
    """Calculate free-free and synchrotron amplitude and spectral index maps.

    Uses a set of Planck simulations to calculate maps of the synchrotron 
    and free-free amplitude, and the synchrotron spectral index, on a 
    square patch of sky.

    On small scales (below 5 degrees), the synchrotron spectral index is 
    generated from a random Gaussian distribution.

    Parameters:
        redshift (float, optional):
            Redshift to evaluate the model at. Default: None (uses box redshift).

        rotation (tuple, optional):
            Rotation of the field from Galactic coordinates, used by healpy 
            ``gnomview`` when projecting the field.

        ref_freq (float, optional):
            Reference frequency to evaluate the amplitudes at, in MHz.

        free_idx (float, optional):
            If set, replace the default free-free index with this value. 

        seed_syncidx (int, optional):
            Random seed to use when generating the small-scale spectral index 
            fluctuations.

    Returns:
        sync_amp, free_amp, sync_idx (array_like): Synchrotron and free-free 
            amplitudes and synchrotron spectral index.

        - ``sync_amp, free_amp (array_like)``:
            Synchrotron and free-free amplitude maps, evaluated at the 
            reference frequency.

        - ``sync_idx (array_like)``:
            Synchrotron spectral index map.
    """
    # Get frequency and angular pixel coords
    ang_x, ang_y = self.box.pixel_array(redshift=redshift)
    xside = len(ang_x)
    yside = len(ang_y)

    # Read Planck simulated foreground maps
    free217, sync217, sync353 = self.read_planck_sim_maps()

    # Get rid of unphysical values
    free217[np.where(free217 < 0.)[0]] = np.percentile(free217, 3)

    # Get free-free spectral index
    if free_idx is None:
        free_idx = self.free_idx

    # Calculate synchrotron spectral index
    sync_idx = np.log(sync353/ sync217) / np.log(353./217.)

    # Calculate component amplitudes (freqs -> GHz)
    sync_amp = sync217 * ((ref_freq/1000.)/217.)**(sync_idx)
    free_amp = free217 * ((ref_freq/1000.)/217.)**(free_idx)

    # Fill-in small-scale structure for synch spectral index map (which is 
    # MAMD's map at 5 deg)
    ells = np.arange(1., 4001.)
    cl5deg = hp.sphtfunc.anafast(
                    np.random.normal(0.0, np.std(sync_idx), 12*2048*2048), 
                    lmax=4000)

    # Synchrotron angular power spectrum
    # Taken from https://arxiv.org/pdf/astro-ph/0408515.pdf
    cls = cl5deg[0] * (1000./ells)**2.4
    np.random.seed(seed_syncidx)
    sync_idx = sync_idx + hp.sphtfunc.synfast(cls, 2048)

    # Map resolution parameters
    nside = hp.get_nside(sync_idx)      
    reso_arcmin = hp.nside2resol(nside, arcmin=True)
    nxpix = np.int(np.ceil(54.1*60./reso_arcmin))
    nypix = np.int(np.ceil(54.1*60./reso_arcmin))

    # Project synchrotron amplitude map from sphere to rectangular grid
    sync_amp= hp.visufunc.gnomview(sync_amp, coord='G', rot=rotation, 
                                   xsize=nxpix, ysize=nypix, 
                                   reso=reso_arcmin, flip='astro', 
                                   return_projected_map=True)
    plt.close()
    sync_amp = sync_amp[::-1] # reverse order

    # Resample synch. amplitudes onto grid with desired resolution
    zoom_param = [xside, yside] / np.array(sync_amp.shape)
    sync_amp = scipy.ndimage.zoom(sync_amp, zoom_param, order=3)

    # Project free-free amplitude map from sphere to rect. grid and resample
    free_amp = hp.visufunc.gnomview(free_amp, coord='G', rot=rotation, 
                                    xsize=nxpix, ysize=nypix, 
                                    reso=reso_arcmin, flip='astro', 
                                    return_projected_map=True)
    plt.close()
    free_amp = free_amp[::-1] # reverse order
    free_amp = scipy.ndimage.zoom(free_amp, zoom_param, order=3)

    # Project synch. spectral idx map from sphere to rect. grid and resample
    sync_idx = hp.visufunc.gnomview(sync_idx, coord='G', rot=rotation, 
                                    xsize=nxpix, ysize=nypix, 
                                    reso=reso_arcmin, flip='astro', 
                                    return_projected_map=True)
    plt.close()
    sync_idx = sync_idx[::-1] # reverse order
    sync_idx = scipy.ndimage.zoom(sync_idx, zoom_param, order=3)

    # Return amplitudes in mK, and synch. spectral index map
    return sync_amp*1e3, free_amp*1e3, sync_idx

PointSourceModel

Make point source maps according to the Battye et al. 2013 recipe.

Source code in fastbox/foregrounds.py
class PointSourceModel(object):
    """
    Make point source maps according to the Battye et al. 2013 recipe.
    """

    def __init__(self, box):
        """
        Make point source maps according to the Battye et al. 2013 recipe.
        Based on Eq. 36 of https://arxiv.org/pdf/1209.0343.pdf; see also p99 of
        https://www.research.manchester.ac.uk/portal/files/67403180/FULL_TEXT.PDF

        Parameters:
            box (CosmoBox):
                Object containing a simulation box.
        """ 
        self.box = box


    def flux_amplitude(self, sjy):
        """Amplitude factor for the flux scaling of the model."""
        logS = np.log10(sjy)
        gamma = 2.593 \
              + 9.333e-2 * logS \
              - 4.839e-4 * logS**2. \
              + 2.488e-1 * logS**3. \
              + 8.995e-2 * logS**4. \
              + 8.506e-3 * logS**5.
        return 10.**gamma


    def integ_flux(self, sjy):
        """Empirical point source model for integrated flux."""
        return self.flux_amplitude(sjy) * sjy**(-2.5) * sjy


    def poisson_pspec(self, sjy):
        """Empirical point source model for Poisson power spectrum."""
        return self.flux_amplitude(sjy) * sjy**(-2.5) * sjy**(2.0)


    def number_count(self, sjy):
        """Empirical point source model for source count."""
        return self.flux_amplitude(sjy) * sjy**(-2.5)


    def construct_cube(self, flux_cutoff, beta, delta_beta, 
                       redshift=None, nside=256, rotation=(0., -62., 0.),
                       seed_clustering=None, seed_poisson=None):
        """Make point source emission data cube for a box.

        Parameters:
            flux_cutoff (float):
                Maximum flux (cutoff) for the point source model, in Jy.

            beta (float):
                Mean spectral index value, beta.

            delta_beta (float):
                RMS spectral index fluctuation, delta beta.

            redshift (float, optional):
                Redshift to evaluate the model at. Default: None (uses box redshift).

            nside (int, optional):
                Healpix NSIDE map resolution parameter; must be a power of 2. 
                Default: 256.

            rotation (tuple, optional):
                Rotation of the field from Galactic coordinates, used by healpy 
                ``gnomview`` when projecting the field.

            seed_clustering, seed_poisson (int, optional):
                Random seed used by the clustering and Poisson models.

        Returns:
            fg_cube, T_ps_mean (array_like):
                Point source temperature data cube and mean temperature.

                - ``fg_cube (array_like)``: Point source temperature data cube (in mK).

                - ``T_ps_mean (array_like)``: Mean temperature (in mK) of point 
                  source component at each freq.
        """
        # Frequency and angular pixel coordinates for the box
        freqs = self.box.freq_array(redshift=redshift) # MHz
        ang_x, ang_y = self.box.pixel_array(redshift=redshift) # deg
        xside = ang_x.size
        yside = ang_y.size

        # Resolution parameters
        ell = np.arange(nside*3) + 1.0
        npix = 12 * nside * nside
        pixarea = (np.degrees(4.*np.pi) * 60.) / (npix)
        nfreq = freqs.size
        cfact = C_LIGHT**2 / (2 * KBOLTZ * (1.4e9)**2) * 10.**-26

        # Make point source map at reference freq. (1.4 GHz)
        # Get the mean temperature
        intvals = scipy.integrate.quad(lambda sjy: self.integ_flux(sjy), 0., flux_cutoff)
        T_ps0 = cfact * (intvals[0] - intvals[1])

        # Get the clustering contribution
        np.random.seed(seed_clustering)
        clclust = 1.8e-4 * ell**-1.2 * T_ps0**2
        clustmap = hp.sphtfunc.synfast(clclust, nside, new=True)

        # Get the poisson contribution
        # Under 0.01 Jy, Poisson contributions behave as Gaussians
        cl_poisson_low = np.zeros((len(ell)))
        vals = np.arange(1e-6, 0.01, (0.01 - 1e-6)/len(ell))
        for j, ival in enumerate(vals):
            # FIXME: Use cumtrapz here instead
            intvals = scipy.integrate.quad(lambda sjy: self.poisson_pspec(sjy), 0., ival)
            cl_poisson_low[j] = cfact**2 * (intvals[0] - intvals[1])

        np.random.seed(seed_poisson)
        poisson_low_map = hp.sphtfunc.synfast(cl_poisson_low, nside, new=True)

        # Over 0.01 Jy, need to inject sources into the sky
        shotmap = np.zeros((npix))
        if flux_cutoff > 0.01:
            for ival in np.arange(0.01, flux_cutoff, (flux_cutoff - 0.01)/10.):
                # N is number of sources per steradian per Jansky
                numbster = scipy.integrate.quad(
                                            lambda sjy: self.number_count(sjy), 
                                            ival - 1e-3, 
                                            ival + 1e-3)[0]
                numbsky = int(4 * np.pi * numbster * ival)
                tempval = cfact * scipy.integrate.quad(
                                            lambda sjy: self.integ_flux(sjy), 
                                            0.01, 
                                            ival)[0] / pixarea
                randind = np.random.choice(range(npix), numbsky)
                shotmap[randind] = tempval

        # Sum contributions together to get full template map at reference freq.
        map0 = T_ps0 + poisson_low_map + clustmap + shotmap

        # Extract npix by npix projected map (at 1.4 GHz) by using gnomview
        reso_arcmin = hp.nside2resol(nside, arcmin=True)
        map0 = hp.visufunc.gnomview(map0, coord='G', rot=rotation, 
                                    xsize=xside, ysize=yside, 
                                    reso=reso_arcmin, flip='astro', 
                                    return_projected_map=True)
        plt.close()
        map0 = map0[::-1] # reverse order

        # Generate random realisation of spectral index
        spec_idx_map = np.random.normal(beta, scale=delta_beta**2, size=npix)

        # Use gnomview to get projected spectral index map
        spidxs = hp.visufunc.gnomview(spec_idx_map, coord='G', rot=rotation, 
                                      xsize=xside, ysize=yside, 
                                      reso=reso_arcmin, flip='astro', 
                                      return_projected_map=True)
        plt.close()
        spidxs = spidxs[::-1] # reverse order

        # Scale-up maps to different frequencies
        maps = np.zeros((xside, yside, nfreq))
        maps[:,:,:] = map0[:,:,np.newaxis] \
             * (freqs[np.newaxis,np.newaxis,:]/1400.)**(spidxs[:,:,np.newaxis])

        # Mean temperature vs freq.
        T_ps_mean = (T_ps0 * (freqs/1400.)**beta).reshape(nfreq, 1)

        return maps*1e3, T_ps_mean*1e3 # mK

__init__(self, box) special

Make point source maps according to the Battye et al. 2013 recipe. Based on Eq. 36 of https://arxiv.org/pdf/1209.0343.pdf; see also p99 of https://www.research.manchester.ac.uk/portal/files/67403180/FULL_TEXT.PDF

Parameters:

Name Type Description Default
box CosmoBox

Object containing a simulation box.

required
Source code in fastbox/foregrounds.py
def __init__(self, box):
    """
    Make point source maps according to the Battye et al. 2013 recipe.
    Based on Eq. 36 of https://arxiv.org/pdf/1209.0343.pdf; see also p99 of
    https://www.research.manchester.ac.uk/portal/files/67403180/FULL_TEXT.PDF

    Parameters:
        box (CosmoBox):
            Object containing a simulation box.
    """ 
    self.box = box

construct_cube(self, flux_cutoff, beta, delta_beta, redshift=None, nside=256, rotation=(0.0, -62.0, 0.0), seed_clustering=None, seed_poisson=None)

Make point source emission data cube for a box.

Parameters:

Name Type Description Default
flux_cutoff float

Maximum flux (cutoff) for the point source model, in Jy.

required
beta float

Mean spectral index value, beta.

required
delta_beta float

RMS spectral index fluctuation, delta beta.

required
redshift float

Redshift to evaluate the model at. Default: None (uses box redshift).

None
nside int

Healpix NSIDE map resolution parameter; must be a power of 2. Default: 256.

256
rotation tuple

Rotation of the field from Galactic coordinates, used by healpy gnomview when projecting the field.

(0.0, -62.0, 0.0)
seed_clustering, seed_poisson (int

Random seed used by the clustering and Poisson models.

required

Returns:

Type Description
fg_cube, T_ps_mean (array_like)

Point source temperature data cube and mean temperature.

- ``fg_cube (array_like)``: Point source temperature data cube (in mK).

- ``T_ps_mean (array_like)``: Mean temperature (in mK) of point 
  source component at each freq.
Source code in fastbox/foregrounds.py
def construct_cube(self, flux_cutoff, beta, delta_beta, 
                   redshift=None, nside=256, rotation=(0., -62., 0.),
                   seed_clustering=None, seed_poisson=None):
    """Make point source emission data cube for a box.

    Parameters:
        flux_cutoff (float):
            Maximum flux (cutoff) for the point source model, in Jy.

        beta (float):
            Mean spectral index value, beta.

        delta_beta (float):
            RMS spectral index fluctuation, delta beta.

        redshift (float, optional):
            Redshift to evaluate the model at. Default: None (uses box redshift).

        nside (int, optional):
            Healpix NSIDE map resolution parameter; must be a power of 2. 
            Default: 256.

        rotation (tuple, optional):
            Rotation of the field from Galactic coordinates, used by healpy 
            ``gnomview`` when projecting the field.

        seed_clustering, seed_poisson (int, optional):
            Random seed used by the clustering and Poisson models.

    Returns:
        fg_cube, T_ps_mean (array_like):
            Point source temperature data cube and mean temperature.

            - ``fg_cube (array_like)``: Point source temperature data cube (in mK).

            - ``T_ps_mean (array_like)``: Mean temperature (in mK) of point 
              source component at each freq.
    """
    # Frequency and angular pixel coordinates for the box
    freqs = self.box.freq_array(redshift=redshift) # MHz
    ang_x, ang_y = self.box.pixel_array(redshift=redshift) # deg
    xside = ang_x.size
    yside = ang_y.size

    # Resolution parameters
    ell = np.arange(nside*3) + 1.0
    npix = 12 * nside * nside
    pixarea = (np.degrees(4.*np.pi) * 60.) / (npix)
    nfreq = freqs.size
    cfact = C_LIGHT**2 / (2 * KBOLTZ * (1.4e9)**2) * 10.**-26

    # Make point source map at reference freq. (1.4 GHz)
    # Get the mean temperature
    intvals = scipy.integrate.quad(lambda sjy: self.integ_flux(sjy), 0., flux_cutoff)
    T_ps0 = cfact * (intvals[0] - intvals[1])

    # Get the clustering contribution
    np.random.seed(seed_clustering)
    clclust = 1.8e-4 * ell**-1.2 * T_ps0**2
    clustmap = hp.sphtfunc.synfast(clclust, nside, new=True)

    # Get the poisson contribution
    # Under 0.01 Jy, Poisson contributions behave as Gaussians
    cl_poisson_low = np.zeros((len(ell)))
    vals = np.arange(1e-6, 0.01, (0.01 - 1e-6)/len(ell))
    for j, ival in enumerate(vals):
        # FIXME: Use cumtrapz here instead
        intvals = scipy.integrate.quad(lambda sjy: self.poisson_pspec(sjy), 0., ival)
        cl_poisson_low[j] = cfact**2 * (intvals[0] - intvals[1])

    np.random.seed(seed_poisson)
    poisson_low_map = hp.sphtfunc.synfast(cl_poisson_low, nside, new=True)

    # Over 0.01 Jy, need to inject sources into the sky
    shotmap = np.zeros((npix))
    if flux_cutoff > 0.01:
        for ival in np.arange(0.01, flux_cutoff, (flux_cutoff - 0.01)/10.):
            # N is number of sources per steradian per Jansky
            numbster = scipy.integrate.quad(
                                        lambda sjy: self.number_count(sjy), 
                                        ival - 1e-3, 
                                        ival + 1e-3)[0]
            numbsky = int(4 * np.pi * numbster * ival)
            tempval = cfact * scipy.integrate.quad(
                                        lambda sjy: self.integ_flux(sjy), 
                                        0.01, 
                                        ival)[0] / pixarea
            randind = np.random.choice(range(npix), numbsky)
            shotmap[randind] = tempval

    # Sum contributions together to get full template map at reference freq.
    map0 = T_ps0 + poisson_low_map + clustmap + shotmap

    # Extract npix by npix projected map (at 1.4 GHz) by using gnomview
    reso_arcmin = hp.nside2resol(nside, arcmin=True)
    map0 = hp.visufunc.gnomview(map0, coord='G', rot=rotation, 
                                xsize=xside, ysize=yside, 
                                reso=reso_arcmin, flip='astro', 
                                return_projected_map=True)
    plt.close()
    map0 = map0[::-1] # reverse order

    # Generate random realisation of spectral index
    spec_idx_map = np.random.normal(beta, scale=delta_beta**2, size=npix)

    # Use gnomview to get projected spectral index map
    spidxs = hp.visufunc.gnomview(spec_idx_map, coord='G', rot=rotation, 
                                  xsize=xside, ysize=yside, 
                                  reso=reso_arcmin, flip='astro', 
                                  return_projected_map=True)
    plt.close()
    spidxs = spidxs[::-1] # reverse order

    # Scale-up maps to different frequencies
    maps = np.zeros((xside, yside, nfreq))
    maps[:,:,:] = map0[:,:,np.newaxis] \
         * (freqs[np.newaxis,np.newaxis,:]/1400.)**(spidxs[:,:,np.newaxis])

    # Mean temperature vs freq.
    T_ps_mean = (T_ps0 * (freqs/1400.)**beta).reshape(nfreq, 1)

    return maps*1e3, T_ps_mean*1e3 # mK

flux_amplitude(self, sjy)

Amplitude factor for the flux scaling of the model.

Source code in fastbox/foregrounds.py
def flux_amplitude(self, sjy):
    """Amplitude factor for the flux scaling of the model."""
    logS = np.log10(sjy)
    gamma = 2.593 \
          + 9.333e-2 * logS \
          - 4.839e-4 * logS**2. \
          + 2.488e-1 * logS**3. \
          + 8.995e-2 * logS**4. \
          + 8.506e-3 * logS**5.
    return 10.**gamma

integ_flux(self, sjy)

Empirical point source model for integrated flux.

Source code in fastbox/foregrounds.py
def integ_flux(self, sjy):
    """Empirical point source model for integrated flux."""
    return self.flux_amplitude(sjy) * sjy**(-2.5) * sjy

number_count(self, sjy)

Empirical point source model for source count.

Source code in fastbox/foregrounds.py
def number_count(self, sjy):
    """Empirical point source model for source count."""
    return self.flux_amplitude(sjy) * sjy**(-2.5)

poisson_pspec(self, sjy)

Empirical point source model for Poisson power spectrum.

Source code in fastbox/foregrounds.py
def poisson_pspec(self, sjy):
    """Empirical point source model for Poisson power spectrum."""
    return self.flux_amplitude(sjy) * sjy**(-2.5) * sjy**(2.0)