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)