Simulating catalogs

This tutorial shows how to generate mock catalogs of Galactic binaries

import os
import torch
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.size'] = 14
plt.rcParams['text.usetex'] = True
plt.rcParams['font.family'] = 'serif'

from corner import corner
from fast_lisa_subtraction.utils import latexify

# Specify the device to use for computations
device = 'cuda' if torch.cuda.is_available() else 'cpu'

Simulate a DWD Population

We will make use of the GalacticBinaryPopulation class to draw samples of a DWD Population.

This class uses enables the sampling of the parameters describing a catalog of GBs

In particular:

  • Frequency

  • FrequencyDerivative

  • Amplitude

  • InitialPhase

  • Inclination

  • Polarization

  • EclipticLatitude

  • EclipticLongitude

the the parametrization explained in De Santi F. et al (2026) and is by default initialized to reproduce the best fit to the catalog of Lamberts A. et al. (2019)

Priors

FastLisaSubtraction supports multiple analytical priors, like Uniform, Gaussian, Gamma, LogNormal as described in De Santi F. et al (2026)

import fast_lisa_subtraction.priors as priors

print(priors.__all__)
['Uniform', 'LogNormal', 'Gamma', 'PowerLaw', 'BrokenPowerLaw', 'UniformCosine', 'UniformSine', 'Gaussian', 'Cauchy', 'GalacticBinaryPopulation']

In order to generate a mock catalog we need to instanciate the GalacticBinaryPopulation class.

We first need to define a dictionary with the prior for each set of parameters, as an example:

from fast_lisa_subtraction.priors import *

prior_dict = dict(
                Frequency           = PowerLaw(alpha=-2, minimum=1e-4, maximum=1e-1, name='Frequency', device=device),
                FrequencyDerivative = Gamma(alpha=2, beta=3, offset=-20.0, name='FrequencyDerivative', device=device),
                Amplitude           = LogNormal(mu=-21.5, sigma=0.2, minimum=-24.5, maximum=-20.5, name='Amplitude', device=device),
                InitialPhase        = Uniform(0, 2*np.pi, name='InitialPhase', device=device),
                Inclination         = UniformCosine(0, np.pi, name='Inclination', device=device),
                Polarization        = Uniform(0, 2*np.pi, name='Polarization', device=device),
                EclipticLatitude    = UniformSine(-np.pi/2, np.pi/2, name='EclipticLatitude', device=device),
                EclipticLongitude   = Uniform(0, 2*np.pi, name='EclipticLongitude', device=device)
            )

Then we use the class to sample the marginal and introduce a copula correlation in $f$-$\dot{f}$ as described in De Santi F. et al (2026)

Other available copulas are the Clayton, Frank and Student-T, refer to the documentation page for details

# Instantiate the GalacticBinaryPopulation class
GB_population = GalacticBinaryPopulation(priors=prior_dict, device=device)

# Generate samples from the population
N = int(1e6)
GB_population_samples = GB_population.sample(N, copula=True, kind='gaussian', rho=0.8)

# Convert to a dataframe and inspect the columns
GB_population_df = GB_population_samples.dataframe()

GB_population_df.head()
Frequency FrequencyDerivative Amplitude InitialPhase Inclination Polarization EclipticLatitude EclipticLongitude
0 0.000102 9.838469e-21 1.047984e-23 5.581181 2.062936 2.949006 -0.684814 6.278080
1 0.001257 2.889604e-20 2.848996e-22 5.217888 2.465560 4.257358 -0.258646 4.241662
2 0.000104 5.488845e-21 4.679115e-24 3.888970 0.441004 5.520145 -0.408062 5.351471
3 0.000134 7.041943e-21 4.371890e-24 2.507201 1.304868 4.035791 -0.428621 1.592792
4 0.000255 8.474385e-21 2.026280e-23 1.373542 2.880041 2.436864 0.307664 2.670212
GB_population_samples.numpy().shape
(1000000, 8)

We then can plot the distributions

import copy
from fast_lisa_subtraction.utils import latexify

latex_dict = {
    'Frequency': r'$\log_{10}(f/{\rm Hz})$',
    'FrequencyDerivative': r'$\log_{10}\left(\frac{\dot{f}}{{\rm Hzs^{-1}}}\right)$',
    'Amplitude': r'$\log_{10}\mathcal{A}$',
    'InitialPhase': r'$\phi_0$',
    'Inclination': r'$\iota$',
    'Polarization': r'$\psi$',
    'EclipticLatitude': r'$\beta$',
    'EclipticLongitude': r'$\lambda$',
}

@latexify
def plot_catalogue(pop_samples, **corner_kwargs):
    pop_samples = copy.deepcopy(pop_samples)

    for key in ['Frequency', 'FrequencyDerivative', 'Amplitude']:
        pop_samples[key] = copy.deepcopy(torch.log10(pop_samples[key]))
    
    pop_samples_numpy = pop_samples.numpy()
    latex_labels = [latex_dict[key] for key in pop_samples.keys()]
    
    return corner(pop_samples_numpy, labels=latex_labels, **corner_kwargs)
fig = plot_catalogue(GB_population_samples, color='C0', plot_datapoints=True, show_titles=True, title_kwargs={'fontsize': 13})
../_images/0446ccdcc5d3ce8503b458c0471df5d3ebe744d446e785157d9614a15b0299a5.png

The class is by default initialized to reproduce a fit to the catalog of Lamberts A. et al. (2019).

# Instantiate the GalacticBinaryPopulation class
GB_population = GalacticBinaryPopulation(device=device)

# Generate samples from the population
GB_population_samples = GB_population.sample(N)

# Convert to a dataframe and inspect the columns
GB_population_df = GB_population_samples.dataframe()

fig = plot_catalogue(GB_population_samples, color='C0', plot_datapoints=True, show_titles=True, title_kwargs={'fontsize': 14})
[WARNING] - Too few points to create valid contours
../_images/8be490c8121545749633bd57f30350bbc1d0db4455fc6222b0a0d9cee9717615.png