Source code for skysurvey.target.rates

"""
This module provides utility functions for drawing and computing volumetric rates and redshift distributions.
"""

import numpy as np

from astropy.cosmology import Planck18

[docs] def draw_redshift(size, rate, zmin=0., zmax=2., zstep=1e-4, cosmology=Planck18, rng=None, **kwargs): """Draw random redshift following the given rate. Parameters ---------- size : int Number of target to draw. rate : float or callable If a float is given, it is assumed to be the number of targets per Gpc3. If a callable is given, it is supposed to be a function of z that returns the volumetric rate as a function of redshift. zmin : float, optional Minimum redshift. The default is 0. zmax : float, optional Maximum redshift. The default is 2. zstep : float, optional Sampling of the redshift. The default is 1e-5. cosmology: `astropy.Cosmology`, optional Cosmology to use to compute volume, as the rate are "volumetric rates". rng : None, int, `(Bit)Generator`, optional seed for the random number generator. (doc adapted from numpy's `np.random.default_rng` docstring. See that documentation for details.) If None, an unpredictable entropy will be pulled from the OS. If an ``int``, (>0), it will set the initial `BitGenerator` state. If a `(Bit)Generator`, it will be returned as a `Generator` unaltered. **kwargs Goes to :func:`get_ntargets_per_shell()` -> :func:`get_rate()`. Returns ------- list A list of redshifts. """ # force number of target per redshift shell to be a float to avoid rounding errors. xx, pdf = get_ntargets_per_shell(zmin=zmin, zmax=zmax, zstep=zstep, rate=rate, astype="float", cosmology=cosmology, **kwargs) # sets the random number generator rng = np.random.default_rng(rng) # normal pdf if np.ndim(pdf) == 1: return rng.choice(xx, size=size, p=pdf/pdf.sum()) # 2D rates | this could happend if rates is an array. elif np.ndim(pdf)==2: return [rng.choice(xx, size=size, p=pdf_/pdf_.sum()) for pdf_ in pdf] else: raise ValueError(f"ndim of pdf should be 1 or 2, not {np.ndim(pdf)=}")
[docs] def get_rate(z, rate, **kwargs): """Get the (volumetric) rate as a function of redshift. Parameters ---------- z : array Array of redshifts. rate : float or callable If a float is given, it is assumed to be the number of targets per Gpc3. If a callable is given, it is supposed to be a function of z that returns the volumetric rate as a function of redshift. **kwargs Rate options if rate is a function. ignored otherwise. Returns ------- rate the rate per Gpc, array (if func) or float """ # specified rate function or volumetric rate ? if callable(rate): # function n_per_gpc3 = rate(z, **kwargs) else: # volumetric n_per_gpc3 = rate return n_per_gpc3
[docs] def get_ntargets_per_shell(zmax, rate, zmin=0, zstep=1e-5, cosmology=Planck18, astype="int", **kwargs): """ Get the total number of target expected in the given volume. Parameters ---------- zmax : float outter redshift of the volume. rate : float, array or callable If a float is given, it is assumed to be the number of targets per Gpc3. If a callable is given, it is supposed to be a function of z that returns the volumetric rate as a function of redshift. If an array is given, if array broacasts with shell size, then it multiplies shell, if not than an axes is added and pdf is (rates.shape, nbins). zmin: float inner redshift of the volume. cosmology : `astropy.Cosmology`, optional Cosmology used to get the comoving_volume. The default is `Planck18`. zstep: float binning of the redshift used for the computation. astype: bool type of the returned number of target per shell. **kwargs: goes to :func:`get_rate()` Returns ------- zbins: array mid value of the redshift corresponding to the shell pdf: nd-array 1d array if rate broadcast with shell, else nd-array with n the rate shape. """ # initial binning bins_of_redshift = np.arange(zmin, zmax, step=zstep) # [ndim] # this define the volume of the universe volume = cosmology.comoving_volume( bins_of_redshift ).to("Gpc**3").value # len(input_z) (+ 1 if keepsize) # and this the shell of universe. This is used to compute cases of non-constante rates. shell = np.diff(volume) # [ndim-1] # this are the effective redshift of the shells bins_of_redshift_mid = np.mean([bins_of_redshift[1:], bins_of_redshift[:-1]], axis=0) # [ndim-1] # so this is the rate computed at the effective redshift of the shell # it basically assumes the rate to be constant within one shell. n_per_gpc3_of_shell = get_rate(bins_of_redshift_mid, rate, **kwargs) # [ndim-1] # the total number of target per shell is the volumetric_rate_per_shell * the shell_volume if np.ndim(n_per_gpc3_of_shell) == 0 or (np.ndim(n_per_gpc3_of_shell) == 1 and len(n_per_gpc3_of_shell) == len(shell)): ntargets_per_shell = n_per_gpc3_of_shell * shell else: ntargets_per_shell = np.atleast_1d(n_per_gpc3_of_shell)[:, None] * shell return bins_of_redshift_mid, ntargets_per_shell.astype(astype)
[docs] def get_ntargets(zmax, rate, zmin=0, cosmology=Planck18, zstep=1e-5, force_shell=False, astype="int", **kwargs): """ Get the total number of target expected in the given volume. Parameters ---------- zmax : float outter redshift of the volume. rate : float or callable If a float is given, it is assumed to be the number of targets per Gpc3. If a callable is given, it is supposed to be a function of z that returns the volumetric rate as a function of redshift. zmin: float inner redshift of the volume. cosmology : `astropy.Cosmology`, optional Cosmology used to get the comoving_volume. The default is `Planck18`. zstep: float binning of the redshift used for the computation. force_shell: bool If the input rate is a constant, should this force the use of shell computation ? astype: bool type of the returned value. Returns ------- ntargets: float, array number(s) of target. """ # function or forced, hence shell computation if callable(rate) or force_shell: bins_of_redshift_mid, ntargets_per_shell = get_ntargets_per_shell(zmax, rate, zmin=zmin, zstep=zstep, cosmology=cosmology, astype="float", # request astype comes at "return" **kwargs) ntargets = ntargets_per_shell.sum(axis=-1) # respects rate dimension # simple constant volumetric rate, so "V(zmax)-V(zmin) * Constant" else: volume_zmax = cosmology.comoving_volume( zmax ).to("Gpc**3").value volume_zmin = cosmology.comoving_volume( zmin ).to("Gpc**3").value rate = np.atleast_1d(rate) if np.ndim(rate) == 1: ntargets = (volume_zmax-volume_zmin) * rate else: ntargets = (volume_zmax-volume_zmin) * rate[:,None] # squeeze() will be [float] => float return ntargets.astype(astype).squeeze()