Monday, October 9, 2017

Study the Universe with Python tutorial, part 2 -- power spectrum

In the first part of this series we discussed how to download the galaxy catalogue of the Baryon Oscillation Spectroscopic Survey (BOSS). We also made some plots to show the distribution of galaxies. In this blog post, we will calculate a summary statistic of the dataset, which is the first step to use the data to constrain cosmological parameters, like dark matter and dark energy.

As we saw in the last blog post, the BOSS dataset contains about 1 million galaxies and their distribution in the Universe. The position of these galaxies is what carries the cosmological information. Imagine that there would be more dark matter in the Universe. The additional matter would have additional gravitational force, which would clump together the material. On the other hand, with less dark matter, the galaxies would be more spread out.

We can now compare these distributions with our data from the BOSS survey, and depending on which of the two distributions looks more like the data, we can determine how much dark matter there is in the Universe.

In practice, we do not want to compare the actual distribution of galaxies, but instead we compare a summary statistic, meaning a compression of these galaxies, which carries all the information we need. There are many choices for such a summary statistic, but here we will use the power spectrum.

To calculate the power spectrum, we first need to download two more catalogues
wget -N https://data.sdss.org/sas/dr12/boss/lss/random0_DR12v5_CMASSLOWZTOT_North.fits.gz -P path/to/folder/
wget -N https://data.sdss.org/sas/dr12/boss/lss/random0_DR12v5_CMASSLOWZTOT_South.fits.gz -P path/to/folder/
which contain a random distribution of points. These distributions are needed to calibrate the data catalogues.

To read this file, you can use the read_data() function we defined earlier (the file is significantly larger and it can take a while to read it into memory). Now we can combine the data catalogues and random catalogue, assign the point distributions to a 3D grid and calculate the power spectrum, all using triumvirate.
from astropy import units as u
from astropy.coordinates import Distance
from astropy.cosmology import Planck15

from triumvirate.parameters import fetch_paramset_template
from triumvirate.catalogue import ParticleCatalogue
from triumvirate.twopt import compute_powspec
def measure_pk(cat_data, cat_random, tag=''):
    """
    Calculate the power spectrum monopole P0(k) using Triumvirate.
    Assumes cat_data and cat_random were read with read_data() that:
      - converts columns to native-endian
      - promotes RA/DEC/Z/NZ/WEIGHT_* to float64
    """
    # Parameters
    paramset = fetch_paramset_template('dict')
    paramset['boxsize']['x'] = 5000.0
    paramset['boxsize']['y'] = 10000.0
    paramset['boxsize']['z'] = 5000.0
    paramset['ngrid']['x']   = 512
    paramset['ngrid']['y']   = 1024
    paramset['ngrid']['z']   = 512 
    paramset['tags']['output']  = tag
    paramset['degrees']['ELL']  = 0  
    paramset.update({
        'range': [0.005, 0.295],
        'num_bins': 29,
        'norm_convention': 'particle',
        'statistic_type': 'powspec',
    })

    # Data catalogue: spherical -> Cartesian (Mpc)
    r1_mpc = Planck15.comoving_distance(cat_data['Z']).to_value(u.Mpc)
    r1_mpc_h = r1_mpc * Planck15.h  # now in Mpc/h
    ra1  = np.deg2rad(cat_data['RA'])
    dec1 = np.deg2rad(cat_data['DEC'])

    x1 = r1_mpc_h * np.cos(dec1) * np.cos(ra1)
    y1 = r1_mpc_h * np.cos(dec1) * np.sin(ra1)
    z1 = r1_mpc_h * np.sin(dec1)

    ws1 = cat_data['WEIGHT_SYSTOT'] * (cat_data['WEIGHT_NOZ'] + cat_data['WEIGHT_CP'] - 1.0)
    wc1 = cat_data['WEIGHT_FKP']
    nz1 = cat_data['NZ']

    catalogue_data = ParticleCatalogue(x1, y1, z1, nz=nz1, ws=ws1, wc=wc1)

    # Random catalogue
    r2_mpc = Planck15.comoving_distance(cat_random['Z']).to_value(u.Mpc)
    r2_mpc_h = r2_mpc * Planck15.h  # now in Mpc/h
    ra2  = np.deg2rad(cat_random['RA'])
    dec2 = np.deg2rad(cat_random['DEC'])

    x2 = r2_mpc_h * np.cos(dec2) * np.cos(ra2)
    y2 = r2_mpc_h * np.cos(dec2) * np.sin(ra2)
    z2 = r2_mpc_h * np.sin(dec2)

    wc2 = cat_random['WEIGHT_FKP']
    nz2 = cat_random['NZ']

    catalogue_rand = ParticleCatalogue(x2, y2, z2, nz=nz2, wc=wc2)

    # Compute power spectrum (monopole)
    results = compute_powspec(
        catalogue_data,
        catalogue_rand,
        degree=0,
        paramset=paramset,
        save='.txt'  # or a filename/path you prefer
    )
    return results
Here we use a grid with 512 grid points in two dimensions and 1024 in one dimension. There are also two weightings included in this calculation, the 'Weight' and 'WEIGHT_FKP' columns. The first weight refers to a correction of incompleteness in the data catalogue, while the second weight tries to optimise the signal-to-noise.

If we use this code to measure the BOSS power spectrum for the northern and southern part we get
Figure 3: Power spectrum measurements for the northern (orange)
and southern (blue) parts of the BOSS dataset.

Here we see that the power spectra of the northern and southern parts are slightly different. Whether these differences are significant is not clear though, since we don't have uncertainties on these measurements (yet).

To extract cosmological information from this power spectrum we need to get a model. We use the classy package
from classy import Class
def get_Pk(Omega_m = 0.3):

    #Start by specifying the cosmology
    Omega_b = 0.05
    Omega_cdm = Omega_m - Omega_b
    h = 0.7 #H0/100
    A_s = 2.1e-9
    n_s = 0.96

    #Create a params dictionary
    #Need to specify the max wavenumber
    k_max = 10 #UNITS: 1/Mpc

    params = {
                 'output':'mPk',
                 'non linear':'halofit',
                 'Omega_b':Omega_b,
                 'Omega_cdm':Omega_cdm,
                 'h':h,
                 'A_s':A_s,
                 'n_s':n_s,
                 'P_k_max_1/Mpc':k_max,
                 'z_max_pk':10. #Default value is 10
    }

    #Initialize the cosmology and compute everything
    cosmo = Class()
    cosmo.set(params)
    cosmo.compute()

    #Specify k and z
    k = np.logspace(-5, np.log10(k_max), num=1000) #Mpc^-1
    z = 1.

    #Call these for the nonlinear and linear matter power spectra
    Pnonlin = np.array([cosmo.pk(ki, z) for ki in k])
    Plin = np.array([cosmo.pk_lin(ki, z) for ki in k])

    #NOTE: You will need to convert these to h/Mpc and (Mpc/h)^3
    #to use in the toolkit. To do this you would do:
    k /= h
    Plin *= h**3
    Pnonlin *= h**3
    return k, Plin
We can now plot the power spectrum for different values of cold dark matter density $\Omega_{cdm}$
Figure 4: Power spectrum models with different amounts of dark
matter using the class Boltzmann code.

Comparing the model with the data can allow us to determine the amount of dark matter in the Universe.

However, before we can go ahead and constrain cosmological parameters we have to get an estimate of the uncertainty on the measurements. That will be the subject of the next post.
You can find the code for this project on GitHub.
cheers
Florian

No comments:

Post a Comment