Custom distributions and populations

Custom populations can be created either by piecing together existing populations (spatial and luminosity populations) or building them from scratch with distributions.

popsynth comes loaded with many combinations of typical population distributions. However, we demonstrate here how to create your own.

Creating distributions

The population samplers rely on distributions. Each population has an internal spatial and luminosity distribution. For example, lets look at a simple spatial distribution:

[1]:
%matplotlib inline

import numpy as np
import matplotlib.pyplot as plt
from jupyterthemes import jtplot

jtplot.style(context="notebook", fscale=1, grid=False)
purple = "#B833FF"
yellow = "#F6EF5B"


import popsynth

popsynth.update_logging_level("INFO")
import warnings

warnings.simplefilter("ignore")
[2]:
from popsynth.distribution import SpatialDistribution


class MySphericalDistribution(SpatialDistribution):

    # we need this property to register the class

    _distribution_name = "MySphericalDistribution"

    def __init__(
        self,
        seed=1234,
        form=None,
    ):

        # the latex formula for the ditribution
        form = r"4 \pi r2"

        # we do not need a "truth" dict here because
        # there are no parameters

        super(MySphericalDistribution, self).__init__(
            seed=seed,
            name="sphere",
            form=form,
        )

    def differential_volume(self, r):

        # the differential volume of a sphere
        return 4 * np.pi * r * r

    def transform(self, L, r):

        # luminosity to flux
        return L / (4.0 * np.pi * r * r)

    def dNdV(self, r):

        # define some crazy change in the number/volume for fun

        return 10.0 / (r + 1) ** 2

We simply define the differential volume and how luminosity is transformed to flux in the metric. Here, we have a simple sphere out to some r_max. We can of course subclass this object and add a normalization.

Now we define a luminosity distribution.

[3]:
from popsynth.distribution import LuminosityDistribution, DistributionParameter


class MyParetoDistribution(LuminosityDistribution):
    _distribution_name = "MyParetoDistribution"

    Lmin = DistributionParameter(default=1, vmin=0)
    alpha = DistributionParameter(default=2)

    def __init__(self, seed=1234, name="pareto"):

        # the latex formula for the ditribution
        lf_form = r"\frac{\alpha L_{\rm min}^{\alpha}}{L^{\alpha+1}}"

        super(MyParetoDistribution, self).__init__(
            seed=seed,
            name="pareto",
            form=lf_form,
        )

    def phi(self, L):

        # the actual function, only for plotting

        out = np.zeros_like(L)

        idx = L >= self.Lmin

        out[idx] = self.alpha * self.Lmin ** self.alpha / L[idx] ** (self.alpha + 1)

        return out

    def draw_luminosity(self, size=1):
        # how to sample the latent parameters
        return (np.random.pareto(self.alpha, size) + 1) * self.Lmin

Note: If you want to create a cosmological distribution, inherit from from ComologicalDistribution class!

Creating a population synthesizer

Now that we have defined our distributions, we can create a population synthesizer that encapsulated them

[4]:
from popsynth.population_synth import PopulationSynth


class MyPopulation(PopulationSynth):
    def __init__(self, Lmin, alpha, r_max=5, seed=1234):

        # instantiate the distributions
        luminosity_distribution = MyParetoDistribution(seed=seed)

        luminosity_distribution.alpha = alpha
        luminosity_distribution.Lmin = Lmin

        spatial_distribution = MySphericalDistribution(seed=seed)
        spatial_distribution.r_max = r_max

        # pass to the super class
        super(MyPopulation, self).__init__(
            spatial_distribution=spatial_distribution,
            luminosity_distribution=luminosity_distribution,
            seed=seed,
        )
[5]:
my_pop_gen = MyPopulation(Lmin=1, alpha=1, r_max=10)

flux_selector = popsynth.HardFluxSelection()
flux_selector.boundary = 1e-2

my_pop_gen.set_flux_selection(flux_selector)

population = my_pop_gen.draw_survey()
 INFO     |  The volume integral is 768.2199804456435 
 INFO     |  Expecting 741 total objects 
 INFO     |  applying selection to fluxes 
 INFO     |  Detected 258 distances 
 INFO     |  Detected 258 objects out to a distance of 9.92 
[6]:
fig = population.display_obs_fluxes_sphere(cmap="magma", background_color="black" ,s=50)