[1]:
import numpy as np
import popstock
import bilby

pretty plots preamble

[2]:
import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (8,6)
mpl.rcParams['xtick.labelsize'] = 20
mpl.rcParams['ytick.labelsize'] = 20
mpl.rcParams['axes.grid'] = True
mpl.rcParams['grid.linestyle'] = ':'
mpl.rcParams['grid.color'] = 'grey'
mpl.rcParams['lines.linewidth'] = 2
mpl.rcParams['axes.labelsize'] = 22
mpl.rcParams['legend.handlelength'] = 3
mpl.rcParams['legend.fontsize'] = 20

from matplotlib import rc
rc('font', **{'family': 'serif', 'serif': ['Computer Modern']})
rc('text', usetex=True)

Models

Instantiate the mass and redshift models; here we use “standard” choices.

[3]:
from gwpopulation.models.mass import SinglePeakSmoothedMassDistribution
from gwpopulation.models.redshift import MadauDickinsonRedshift

mass_obj = SinglePeakSmoothedMassDistribution()
redshift_obj = MadauDickinsonRedshift(z_max=10)

models = {
        'mass_model' : mass_obj,
        'redshift_model' : redshift_obj,
        }

Population \(\Omega_{\rm GW}\) object

Create a PopulationOmegaGW object, which manages the computations.

This requires minimal input for initialisation:

  • The parameter population models (above)

  • A frequency array, used to downsample waveforms consistently

[4]:
from popstock.PopulationOmegaGW import PopulationOmegaGW

freqs = np.arange(10, 2000, 2.5)
newpop = PopulationOmegaGW(models=models, frequency_array=freqs)
Initializing with the following models:
mass: <gwpopulation.models.mass.SinglePeakSmoothedMassDistribution object at 0x1434d5190>
redshift: <gwpopulation.models.redshift.MadauDickinsonRedshift object at 0x1434d5730>

Sampling

An empty PopulationOmegaGW requires a set of event samples to get started. These can be fed in (if pre-sampled), or sampled by the object directly using the draw_and_set_proposal_samples method.

This requires defining:

  • An initial set of population hyperparameters, \(\Lambda_0\) (these must match the formalism used in the models above)

  • The number of event “proposal” samples to use in the calculation

[5]:
Lambda_0 =  {'alpha': 2.5, 'beta': 1, 'delta_m': 3, 'lam': 0.04, 'mmax': 100, 'mmin': 4, 'mpp': 33, 'sigpp':5, 'gamma': 2.7, 'kappa': 3, 'z_peak': 1.9, 'rate': 15}
N_proposal_samples = int(1.e5)

newpop.draw_and_set_proposal_samples(Lambda_0, N_proposal_samples=N_proposal_samples)
Using 10000000, got 55075 out of target 100000 samples after iteration 0
Efficiency: 0.0055075, trying 8157058 samples in next iteration
-----------------------------------------------------------
Using 8157058, got 99730 out of target 100000 samples after iteration 1
Efficiency: 0.005474400206545056, trying 10109783 samples in next iteration
-----------------------------------------------------------
Using 10109783, got 155640 out of target 100000 samples after iteration 2
Efficiency: 0.005530286851854288, trying 7972461 samples in next iteration
-----------------------------------------------------------
Drawing redshift samples
{'gamma': 2.7, 'kappa': 3, 'z_peak': 1.9}

Calculate \(\Omega_{\rm GW}\)

The calculate_omega_gw method does just that; if no \(\Lambda\) parameters are passed, the ones used while sampling will be used. When calculating \(\Omega_{\rm GW}\) for the first time, this may take some time as individual waveforms need to be evaluated. Activating multiprocessing is useful here. If \(\Omega_{\rm GW}\) has already been calculated once by the PopulationOmegaGW object, the stored samples are re-weighted to the desired \(\Lambda\) set.

[6]:
newpop.calculate_omega_gw(Lambda=Lambda_0, multiprocess=True)
11:20 bilby INFO    : Waveform generator initiated with
  frequency_domain_source_model: bilby.gw.source.lal_binary_black_hole
  time_domain_source_model: None
  parameter_conversion: bilby.gw.conversion.convert_to_lal_binary_black_hole_parameters
Using multiprocessing, no status bar currently supported...
[7]:
import matplotlib.pyplot as plt
[8]:
plt.loglog(newpop.frequency_array, newpop.omega_gw, color='orange', linestyle='-.')

plt.xlabel('Frequency [Hz]')
plt.ylabel(r'$\Omega_{\rm GW}$')
[8]:
Text(0, 0.5, '$\\Omega_{\\rm GW}$')
_images/basic_tutorial_12_1.png

Example use: recalculating \(\Omega_{\rm GW}\) for a different choice of hyperparameters \(\Lambda\)

[9]:
# store the old omega for comparison

import copy
omega_0 = copy.copy(newpop.omega_gw)

when re-calling the calculate_omega_gw method with a new set of \(\Lambda\), the samples are re-weighted to satisfy the new population model configuration.

[11]:
# create a new set of Lambdas, with a different value of alpha

Lambda_new =  {'alpha': 4.5, 'beta': 1, 'delta_m': 3, 'lam': 0.04, 'mmax': 100, 'mmin': 4, 'mpp': 33, 'sigpp':5, 'gamma': 2.7, 'kappa': 3, 'z_peak': 1.9, 'rate': 15}

newpop.calculate_omega_gw(Lambda=Lambda_new)
[14]:
plt.loglog(newpop.frequency_array, newpop.omega_gw, color='blue', linestyle='--', label=r'new $\Omega_{\rm GW}$: $\alpha=4.5$')
plt.loglog(newpop.frequency_array, omega_0, color='orange', linestyle='-.', label=r'old $\Omega_{\rm GW}$: $\alpha=2.5$')

plt.xlabel('Frequency [Hz]')
plt.ylabel(r'$\Omega_{\rm GW}$')

plt.legend()
[14]:
<matplotlib.legend.Legend at 0x152ccfbe0>
_images/basic_tutorial_17_1.png

With popstock, these calculations are really fast, we can sample over different values of the hyper-parameters efficiently! Below an easy set of examples

  • VARYING \(\alpha\)

[15]:
# draw alpha from a Gaussian
alphas = np.random.normal(loc=2.5, scale=0.8, size=1000)
omegas_alphas = []

# re-calculate omega for each alpha
for alpha in alphas:
    Lambda_new['alpha'] = alpha
    newpop.calculate_omega_gw(Lambda=Lambda_new)
    omegas_alphas.append(newpop.omega_gw)
[19]:
fig, ax = plt.subplots(1, 2, figsize=(20, 8) )

ax[0].hist(alphas, bins=50, color='turquoise')
ax[0].scatter(alphas[np.where(alphas<0.2)[0][0]], 0.5, color='darkblue', marker='v')
ax[0].set_xlabel(r'$\alpha$')

for om in omegas_alphas:
    ax[1].loglog(newpop.frequency_array, om, color='turquoise', linestyle='-', alpha=0.1)

ax[1].loglog(newpop.frequency_array, omegas_alphas[np.where(alphas<0.2)[0][0]], color='darkblue', linestyle=':', linewidth=2, label=r'low $\alpha$ draw')

ax[1].loglog(newpop.frequency_array, omega_0, color='k', linestyle='--', label=r'$\Omega_{\rm GW}$ at $\alpha=2.5$')

ax[1].set_xlabel('Frequency [Hz]')
ax[1].set_ylabel(r'$\Omega_{\rm GW}$')
ax[1].legend()
[19]:
<matplotlib.legend.Legend at 0x2d4444430>
_images/basic_tutorial_21_1.png
  • VARYING \(\beta\)

[21]:
betas = np.random.normal(loc=1, scale=2.0, size=1000)
omegas_betas = []

# reset alpha to some sensible value
Lambda_new['alpha'] = 2.5

for beta in betas:
    Lambda_new['beta'] = beta
    newpop.calculate_omega_gw(Lambda=Lambda_new)
    omegas_betas.append(newpop.omega_gw)
[24]:
fig, ax = plt.subplots(1, 2, figsize=(20, 8) )


ax[0].hist(betas, bins=50, color='coral')
ax[0].scatter(betas[np.where(betas<-4)[0][0]], 0.5, color='darkred', marker='v')
ax[0].set_xlabel(r'$\beta$')

for om in omegas_betas:
    ax[1].loglog(newpop.frequency_array, om, color='coral', linestyle='-', alpha=0.1)

ax[1].loglog(newpop.frequency_array, omegas_betas[np.where(betas<-4)[0][0]], color='darkred', linestyle=':', linewidth=2, label=r'low $\beta$ draw')

ax[1].loglog(newpop.frequency_array, omega_0, color='k', linestyle='--', label=r'$\Omega_{\rm GW}$ at $\beta=1$')
ax[1].set_xlabel('Frequency [Hz]')
ax[1].set_ylabel(r'$\Omega_{\rm GW}$')
[24]:
Text(0, 0.5, '$\\Omega_{\\rm GW}$')
_images/basic_tutorial_24_1.png
[ ]: