diff options
Diffstat (limited to 'fg21sim/extragalactic')
-rw-r--r-- | fg21sim/extragalactic/__init__.py | 5 | ||||
-rw-r--r-- | fg21sim/extragalactic/pointsources/__init__.py | 4 | ||||
-rw-r--r-- | fg21sim/extragalactic/pointsources/base.py | 203 | ||||
-rw-r--r-- | fg21sim/extragalactic/pointsources/fr1.py | 355 | ||||
-rw-r--r-- | fg21sim/extragalactic/pointsources/fr2.py | 380 | ||||
-rw-r--r-- | fg21sim/extragalactic/pointsources/pointsources.py | 126 | ||||
-rw-r--r-- | fg21sim/extragalactic/pointsources/psparams.py | 77 | ||||
-rw-r--r-- | fg21sim/extragalactic/pointsources/radioquiet.py | 189 | ||||
-rw-r--r-- | fg21sim/extragalactic/pointsources/starbursting.py | 238 | ||||
-rw-r--r-- | fg21sim/extragalactic/pointsources/starforming.py | 257 |
10 files changed, 1834 insertions, 0 deletions
diff --git a/fg21sim/extragalactic/__init__.py b/fg21sim/extragalactic/__init__.py index af976ca..f57928f 100644 --- a/fg21sim/extragalactic/__init__.py +++ b/fg21sim/extragalactic/__init__.py @@ -2,3 +2,8 @@ # MIT license from .clusters import GalaxyClusters + +# Copyright (c) 2016 Zhixian MA <zxma_sjtu@qq.com> +# MIT license + +from .pointsources import PointSources diff --git a/fg21sim/extragalactic/pointsources/__init__.py b/fg21sim/extragalactic/pointsources/__init__.py new file mode 100644 index 0000000..4c64c98 --- /dev/null +++ b/fg21sim/extragalactic/pointsources/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2016 Zhixian MA <zxma_sjtu@qq.com>
+# MIT license
+
+from .pointsources import PointSources
diff --git a/fg21sim/extragalactic/pointsources/base.py b/fg21sim/extragalactic/pointsources/base.py new file mode 100644 index 0000000..1544f5b --- /dev/null +++ b/fg21sim/extragalactic/pointsources/base.py @@ -0,0 +1,203 @@ +# Copyright (c) 2016 Zhixian MA <zxma_sjtu@qq.com> +# MIT license + +""" +Simulation of point sources for 21cm signal detection + +Point sources types +------------------- +1. Star forming (SF) galaxies +2. Star bursting (SB) galaxies +3. Radio quiet AGN (RQ_AGN) +4. Faranoff-Riley I (FRI) +5. Faranoff-Riley II (FRII) + +References +---------- +[1] Wilman et al., + "A semi-empirical simulation of the extragalactic radio continuum + sky for next generation radio telescopes", + 2008, MNRAS, 388, 1335-1348. + http://adsabs.harvard.edu/abs/2008MNRAS.388.1335W +[2] Jelic et al., + "Foreground simulations for the LOFAR-Epoch of Reionization + Experiment", + 2008, MNRAS, 389, 1319-1335. + http://adsabs.harvard.edu/abs/2008MNRAS.389.1319W +[3] Spherical uniform distribution + https://www.jasondavies.com/maps/random-points/ +""" +import os +import numpy as np +import pandas as pd +import healpy as hp + +from .psparams import PixelParams + + +class BasePointSource: + """ + The basic class of point sources + + Parameters + ---------- + z: float; + Redshift, z ~ U(0,20) + dA: au.Mpc; + Angular diameter distance, which is calculated according to the + cosmology constants. In this work, it is calculated by module + basic_params + lumo: au.Jy; + Luminosity at the reference frequency. + lat: au.deg; + The colatitude angle in the spherical coordinate system + lon: au.deg; + The longtitude angle in the spherical coordinate system + area: au.sr; + Area of the point sources, sr = rad^2 + + """ + # Init + + def __init__(self, configs): + # configures + self.configs = configs + # PS_list information + self.columns = ['z', 'dA (Mpc)', 'luminosity (Jy)', 'Lat (deg)', + 'Lon (deg)', 'Area (sr)'] + self.nCols = len(self.columns) + self._set_configs() + + def _set_configs(self): + """ + Load the configs and set the corresponding class attributes. + """ + comp = "extragalactic/pointsources/" + # common + self.nside = self.configs.getn("common/nside") + # resolution + self.resolution = self.configs.getn(comp+"resolution") + # save flag + self.save = self.configs.getn(comp+"save") + # Output_dir + self.output_dir = self.configs.get_path(comp+"output_dir") + + def calc_number_density(self): + pass + + def calc_cdf(self): + """ + Calculate cumulative distribution functions for simulating of + samples with corresponding reshift and luminosity. + + Parameter + ----------- + rho_mat: np.ndarray rho(lumo,z) + The number density matrix (joint-distribution of z and flux) + of this type of PS. + + Returns + ------- + cdf_z, cdf_lumo: np.ndarray + Cumulative distribution functions of redshift and flux. + """ + # Normalization + rho_mat = self.rho_mat + rho_sum = np.sum(rho_mat) + rho_norm = rho_mat / rho_sum + # probability distribution of redshift + pdf_z = np.sum(rho_norm, axis=0) + pdf_lumo = np.sum(rho_norm, axis=1) + # Cumulative function + cdf_z = np.zeros(pdf_z.shape) + cdf_lumo = np.zeros(pdf_lumo.shape) + for i in range(len(pdf_z)): + cdf_z[i] = np.sum(pdf_z[:i]) + for i in range(len(pdf_lumo)): + cdf_lumo[i] = np.sum(pdf_lumo[:i]) + + return cdf_z, cdf_lumo + + def get_lumo_redshift(self): + """ + Randomly generate redshif and luminosity at ref frequency using + the CDF functions. + + Paramaters + ---------- + df_z, cdf_lumo: np.ndarray + Cumulative distribution functions of redshift and flux. + zbin,lumobin: np.ndarray + Bins of redshif and luminosity. + + Returns + ------- + z: float + Redshift. + lumo: au.W/Hz/sr + Luminosity. + """ + # Uniformlly generate random number in interval [0,1] + rnd_z = np.random.uniform(0, 1) + rnd_lumo = np.random.uniform(0, 1) + # Get redshift + dist_z = np.abs(self.cdf_z - rnd_z) + idx_z = np.where(dist_z == dist_z.min()) + z = self.zbin[idx_z[0]] + # Get luminosity + dist_lumo = np.abs(self.cdf_lumo - rnd_lumo) + idx_lumo = np.where(dist_lumo == dist_lumo.min()) + lumo = 10 ** self.lumobin[idx_lumo[0]] + + return float(z), float(lumo) + + def gen_single_ps(self): + """ + Generate single point source, and return its data as a list. + """ + # Redshift and luminosity + self.z, self.lumo = self.get_lumo_redshift() + # angular diameter distance + self.param = PixelParams(self.z) + self.dA = self.param.dA + # W/Hz/Sr to Jy + dA = self.dA * 3.0856775814671917E+22 # Mpc to meter + self.lumo = self.lumo / dA**2 / (10.0**-24) # [Jy] + # Position + x = np.random.uniform(0, 1) + self.lat = (np.arccos(2 * x - 1) / np.pi * 180 - 90) # [deg] + self.lon = np.random.uniform(0, np.pi * 2) / np.pi * 180 # [deg] + # Area + npix = hp.nside2npix(self.nside) + self.area = 4 * np.pi / npix # [sr] + + ps_list = [self.z, self.dA, self.lumo, self.lat, self.lon, self.area] + return ps_list + + def gen_catalog(self): + """ + Generate num_ps of point sources and save them into a csv file. + """ + # Init + ps_table = np.zeros((self.num_ps, self.nCols)) + for x in range(self.num_ps): + ps_table[x, :] = self.gen_single_ps() + + # Transform into Dataframe + self.ps_catalog = pd.DataFrame(ps_table, columns=self.columns, + index=list(range(self.num_ps))) + + def save_as_csv(self): + """Save the catalog""" + if not os.path.exists(self.output_dir): + os.mkdir(self.output_dir) + + pattern = "{prefix}.csv" + filename = pattern.format(prefix=self.prefix) + + # save to csv + if self.save: + file_name = os.path.join(self.output_dir, filename) + self.ps_catalog.to_csv(file_name) + + return file_name diff --git a/fg21sim/extragalactic/pointsources/fr1.py b/fg21sim/extragalactic/pointsources/fr1.py new file mode 100644 index 0000000..0e052cd --- /dev/null +++ b/fg21sim/extragalactic/pointsources/fr1.py @@ -0,0 +1,355 @@ +# Copyright (c) 2016 Zhixian MA <zxma_sjtu@qq.com> +# MIT license + +import numpy as np +import healpy as hp + +from .psparams import PixelParams +from .base import BasePointSource +from ...utils import grid +from ...utils import convert + + +class FRI(BasePointSource): + """ + Generate Faranoff-Riley I (FRI) AGN + + Parameters + ---------- + lobe_maj: float + The major half axis of the lobe + lobe_min: float + The minor half axis of the lobe + lobe_ang: float + The rotation angle of the lobe with respect to line of sight + + Reference + ---------- + [1] Wang J et al., + "How to Identify and Separate Bright Galaxy Clusters from the + Low-frequency Radio Sky?", + 2010, ApJ, 723, 620-633. + http://adsabs.harvard.edu/abs/2010ApJ...723..620W + [2] Fast cirles drawing + https://github.com/liweitianux/fg21sim/fg21sim/utils/draw.py + https://github.com/liweitianux/fg21sim/fg21sim/utils/grid.py + """ + + def __init__(self, configs): + super().__init__(configs) + self.columns.extend( + ['lobe_maj (rad)', 'lobe_min (rad)', 'lobe_ang (deg)']) + self.nCols = len(self.columns) + self._set_configs() + # Paramters for core/lobe ratio + # Willman et al. 2008 Sec2.5.(iii)-(iv) + self.xmed = -2.6 + # Lorentz factor of the jet + self.gamma = 6 + # Number density matrix + self.rho_mat = self.calc_number_density() + # Cumulative distribution of z and lumo + self.cdf_z, self.cdf_lumo = self.calc_cdf() + + def _set_configs(self): + """Load the configs and set the corresponding class attributes""" + super()._set_configs() + pscomp = "extragalactic/pointsources/FRI/" + # point sources amount + self.num_ps = self.configs.getn(pscomp+"numps") + # prefix + self.prefix = self.configs.getn(pscomp+"prefix") + # redshift bin + z_type = self.configs.getn(pscomp+"z_type") + if z_type == 'custom': + start = self.configs.getn(pscomp+"z_start") + stop = self.configs.getn(pscomp+"z_stop") + step = self.configs.getn(pscomp+"z_step") + self.zbin = np.arange(start, stop + step, step) + else: + self.zbin = np.arange(0.1, 10, 0.05) + # luminosity bin + lumo_type = self.configs.getn(pscomp+"lumo_type") + if lumo_type == 'custom': + start = self.configs.getn(pscomp+"lumo_start") + stop = self.configs.getn(pscomp+"lumo_stop") + step = self.configs.getn(pscomp+"lumo_step") + self.lumobin = np.arange(start, stop + step, step) + else: + self.lumobin = np.arange(20, 28, 0.1) # [W/Hz/sr] + + def calc_number_density(self): + """ + Calculate number density rho(lumo,z) of FRI + + References + ---------- + [1] Wilman et al., + "A semi-empirical simulation of the extragalactic radio continuum + sky for next generation radio telescopes", + 2008, MNRAS, 388, 1335-1348. + http://adsabs.harvard.edu/abs/2008MNRAS.388.1335W + [2] Willott et al., + "The radio luminosity function from the low-frequency 3CRR, + 6CE and 7CRS complete samples", + 2001, MNRAS, 322, 536-552. + http://adsabs.harvard.edu/abs/2001MNRAS.322..536W + + Returns + ------- + rho_mat: np.ndarray + Number density matris (joint-distribution of luminosity and + reshift). + """ + # Init + rho_mat = np.zeros((len(self.lumobin), len(self.zbin))) + # Parameters + # Refer to [2] Table. 1 model C and Willman's section 2.4 + alpha = 0.539 # spectral index + lumo_star = 10.0**26.1 # critical luminosity + rho_l0 = 10.0**(-7.120) # normalization constant + z1 = 0.706 # cut-off redshift + z2 = 2.5 # cut-off redshift adviced by Willman + k1 = 4.30 # index of space density revolution + # Calculation + for i, z in enumerate(self.zbin): + if z <= z1: + rho_mat[:, i] = ((rho_l0 * (10**self.lumobin / lumo_star) ** + (-alpha) * + np.exp(-10**self.lumobin / lumo_star)) * + (1 + z)**k1) + elif z <= z2: + rho_mat[:, i] = ((rho_l0 * (10**self.lumobin / lumo_star) ** + (-alpha) * + np.exp(-10**self.lumobin / lumo_star)) * + (1 + z1)**k1) + else: + rho_mat[:, i] = ((rho_l0 * (10**self.lumobin / lumo_star) ** + (-alpha) * + np.exp(-10**self.lumobin / lumo_star)) * + (1 + z)**-z2) + + return rho_mat + + def gen_lobe(self): + """ + Calculate lobe parameters + + References + ---------- + [1] Wilman et al., + "A semi-empirical simulation of the extragalactic radio continuum + sky for next generation radio telescopes", + 2008, MNRAS, 388, 1335-1348. + http://adsabs.harvard.edu/abs/2008MNRAS.388.1335W + + Return + ------ + lobe: list + lobe = [lobe_maj, lobe_min, lobe_ang], which represent the major + and minor axes and the rotation angle. + """ + D0 = 1 # [Mpc] + self.lobe_maj = 0.5 * np.random.uniform(0, D0 * (1 + self.z)**(-1.4)) + self.lobe_min = self.lobe_maj * np.random.uniform(0.2, 1) + self.lobe_ang = np.random.uniform(0, np.pi) / np.pi * 180 + + # Transform to pixel + self.lobe_maj = self.param.get_angle(self.lobe_maj) + self.lobe_min = self.param.get_angle(self.lobe_min) + lobe = [self.lobe_maj, self.lobe_min, self.lobe_ang] + + return lobe + + def gen_single_ps(self): + """ + Generate single point source, and return its data as a list. + """ + # Redshift and luminosity + self.z, self.lumo = self.get_lumo_redshift() + # angular diameter distance + self.param = PixelParams(self.z) + self.dA = self.param.dA + # W/Hz/Sr to Jy + dA = self.dA * 3.0856775814671917E+22 # Mpc to meter + self.lumo = self.lumo / dA**2 / (10.0**-24) # [Jy] + # Position + x = np.random.uniform(0, 1) + self.lat = (np.arccos(2 * x - 1) / np.pi * 180 - 90) # [deg] + self.lon = np.random.uniform(0, np.pi * 2) / np.pi * 180 # [deg] + # lobe + lobe = self.gen_lobe() + # Area + self.area = np.pi * self.lobe_maj * self.lobe_min + + ps_list = [self.z, self.dA, self.lumo, self.lat, self.lon, self.area] + ps_list.extend(lobe) + + return ps_list + + def draw_single_ps(self, freq): + """ + Designed to draw the elliptical lobes of FRI and FRII + + Prameters + --------- + nside: int and dyadic + self.ps_catalog: pandas.core.frame.DataFrame + Data of the point sources + ps_type: int + Class type of the point soruces + freq: float + frequency + """ + # Init + resolution = self.resolution / 60 # [degree] + npix = hp.nside2npix(self.nside) + hpmap = np.zeros((npix,)) + num_ps = self.ps_catalog.shape[0] + # Gen flux list + Tb_list = self.calc_Tb(freq) + ps_lobe = Tb_list[:, 1] + # Iteratively draw ps + for i in range(num_ps): + # Parameters + c_lat = self.ps_catalog['Lat (deg)'][i] # core lat [au.deg] + c_lon = self.ps_catalog['Lon (deg)'][i] # core lon [au.deg] + lobe_maj = self.ps_catalog['lobe_maj (rad)'][ + i] * 180 / np.pi # [deg] + lobe_min = self.ps_catalog['lobe_min (rad)'][ + i] * 180 / np.pi # [deg] + lobe_ang = self.ps_catalog['lobe_ang (deg)'][ + i] / 180 * np.pi # [rad] + + # Lobe1 + lobe1_lat = (lobe_maj / 2) * np.cos(lobe_ang) + lobe1_lat = c_lat + lobe1_lat + lobe1_lon = (lobe_maj / 2) * np.sin(lobe_ang) + lobe1_lon = c_lon + lobe1_lon + # draw + # Fill with ellipse + lon, lat, gridmap = grid.make_grid_ellipse( + (lobe1_lon, lobe1_lat), (lobe_maj, lobe_min), + resolution, lobe_ang / np.pi * 180) + indices, values = grid.map_grid_to_healpix( + (lon, lat, gridmap), self.nside) + hpmap[indices] += ps_lobe[i] + + # Lobe2 + lobe2_lat = (lobe_maj / 2) * np.cos(lobe_ang + np.pi) + lobe2_lat = c_lat + lobe2_lat + lobe2_lon = (lobe_maj / 2) * np.sin(lobe_ang + np.pi) + lobe2_lon = c_lon + lobe2_lon + # draw + # Fill with ellipse + lon, lat, gridmap = grid.make_grid_ellipse( + (lobe2_lon, lobe2_lat), (lobe_maj, lobe_min), + resolution, lobe_ang / np.pi * 180) + indices, values = grid.map_grid_to_healpix( + (lon, lat, gridmap), self.nside) + hpmap[indices] += ps_lobe[i] + + # Core + pix_tmp = hp.ang2pix(self.nside, + (self.ps_catalog['Lat (deg)'] + 90) / + 180 * np.pi, self.ps_catalog['Lon (deg)'] / + 180 * np.pi) + ps_core = Tb_list[:, 0] + hpmap[pix_tmp] += ps_core + + return hpmap + + def draw_ps(self, freq): + """ + Read csv ps list file, and generate the healpix structure vector + with the respect frequency. + """ + # Init + num_freq = len(freq) + npix = hp.nside2npix(self.nside) + hpmaps = np.zeros((npix, num_freq)) + + # Gen ps_catalog + self.gen_catalog() + # get hpmaps + for i in range(num_freq): + hpmaps[:, i] = self.draw_single_ps(freq[i]) + + return hpmaps + + def calc_single_Tb(self, area, freq): + """ + Calculate brightness temperatur of a single ps + + Parameters + ------------ + area: `~astropy.units.Quantity` + Area of the PS, e.g., `1.0*au.sr` + freq: `~astropy.units.Quantity` + Frequency, e.g., `1.0*au.MHz` + + Return + ------- + Tb:`~astropy.units.Quantity` + Average brightness temperature, e.g., `1.0*au.K` + """ + # Init + freq_ref = 151 # [MHz] + freq = freq # [MHz] + # Luminosity at 151MHz + lumo_151 = self.lumo # [Jy] + # Calc flux + # core-to-extend ratio + ang = self.lobe_ang / 180 * np.pi + x = np.random.normal(self.xmed, 0.5) + beta = np.sqrt((self.gamma**2 - 1) / self.gamma) + B_theta = 0.5 * ((1 - beta * np.cos(ang))**-2 + + (1 + beta * np.cos(ang))**-2) + ratio_obs = 10**x * B_theta + # Core + lumo_core = ratio_obs / (1 + ratio_obs) * lumo_151 + a0 = (np.log10(lumo_core) - 0.07 * + np.log10(freq_ref * 10.0E-3) + + 0.29 * np.log10(freq_ref * 10.0E-3) * + np.log10(freq_ref * 10.0E-3)) + lgs = (a0 + 0.07 * np.log10(freq * 10.0E-3) - 0.29 * + np.log10(freq * 10.0E-3) * + np.log10(freq * 10.0E-3)) + flux_core = 10**lgs # [Jy] + # core area + npix = hp.nside2npix(self.nside) + core_area = 4 * np.pi / npix # [sr] + Tb_core = convert.Fnu_to_Tb_fast(flux_core, core_area, freq) # [K] + # lobe + lumo_lobe = lumo_151 * (1 - ratio_obs) / (1 + ratio_obs) # [Jy] + flux_lobe = (freq / freq_ref)**(-0.75) * lumo_lobe + Tb_lobe = convert.Fnu_to_Tb_fast(flux_lobe, area, freq) # [K] + + Tb = [Tb_core, Tb_lobe] + return Tb + + def calc_Tb(self, freq): + """ + Calculate the surface brightness temperature of the point sources. + + Parameters + ------------ + area: `~astropy.units.Quantity` + Area of the PS, e.g., `1.0*au.sr` + freq: `~astropy.units.Quantity` + Frequency, e.g., `1.0*au.MHz` + + Return + ------ + Tb_list: list + Point sources brightness temperature + """ + # Tb_list + num_ps = self.ps_catalog.shape[0] + Tb_list = np.zeros((num_ps, 2)) + # Iteratively calculate Tb + for i in range(num_ps): + ps_area = self.ps_catalog['Area (sr)'][i] # [sr] + Tb_list[i, :] = self.calc_single_Tb(ps_area, freq) + + return Tb_list diff --git a/fg21sim/extragalactic/pointsources/fr2.py b/fg21sim/extragalactic/pointsources/fr2.py new file mode 100644 index 0000000..b40f9ff --- /dev/null +++ b/fg21sim/extragalactic/pointsources/fr2.py @@ -0,0 +1,380 @@ +# Copyright (c) 2016 Zhixian MA <zxma_sjtu@qq.com> +# MIT license + +import numpy as np +import healpy as hp + +from .base import BasePointSource +from .psparams import PixelParams +from ...utils import grid +from ...utils import convert + + +class FRII(BasePointSource): + """ + Generate Faranoff-Riley II (FRII) AGN + + Parameters + ---------- + lobe_maj: float + The major half axis of the lobe + lobe_min: float + The minor half axis of the lobe + lobe_ang: float + The rotation angle of the lobe correspoind to line of sight + + Reference + ---------- + [1] Wang J et al., + "How to Identify and Separate Bright Galaxy Clusters from the + Low-frequency Radio Sky?", + 2010, ApJ, 723, 620-633. + http://adsabs.harvard.edu/abs/2010ApJ...723..620W + [2] Fast cirles drawing + https://github.com/liweitianux/fg21sim/fg21sim/utils/draw.py + https://github.com/liweitianux/fg21sim/fg21sim/utils/grid.py + """ + + def __init__(self, configs): + super().__init__(configs) + self.columns.extend( + ['lobe_maj (rad)', 'lobe_min (rad)', 'lobe_ang (deg)']) + self.nCols = len(self.columns) + self._set_configs() + # Paramters for core/lobe ratio + # Willman et al. 2008 Sec2.5.(iii)-(iv) + self.xmed = -2.8 + # Lorentz factor of the jet + self.gamma = 8 + # Number density matrix + self.rho_mat = self.calc_number_density() + # Cumulative distribution of z and lumo + self.cdf_z, self.cdf_lumo = self.calc_cdf() + + def _set_configs(self): + """Load the configs and set the corresponding class attributes""" + super()._set_configs() + pscomp = "extragalactic/pointsources/FRII/" + # point sources amount + self.num_ps = self.configs.getn(pscomp+"numps") + # prefix + self.prefix = self.configs.getn(pscomp+"prefix") + # redshift bin + z_type = self.configs.getn(pscomp+"z_type") + if z_type == 'custom': + start = self.configs.getn(pscomp+"z_start") + stop = self.configs.getn(pscomp+"z_stop") + step = self.configs.getn(pscomp+"z_step") + self.zbin = np.arange(start, stop + step, step) + else: + self.zbin = np.arange(0.1, 10, 0.05) + # luminosity bin + lumo_type = self.configs.getn(pscomp+"lumo_type") + if lumo_type == 'custom': + start = self.configs.getn(pscomp+"lumo_start") + stop = self.configs.getn(pscomp+"lumo_stop") + step = self.configs.getn(pscomp+"lumo_step") + self.lumobin = np.arange(start, stop + step, step) + else: + self.lumobin = np.arange(25.5, 30.5, 0.1) # [W/Hz/sr] + + def calc_number_density(self): + """ + Calculate number density rho(lumo,z) of FRI + + References + ---------- + [1] Wilman et al., + "A semi-empirical simulation of the extragalactic radio continuum + sky for next generation radio telescopes", + 2008, MNRAS, 388, 1335-1348. + http://adsabs.harvard.edu/abs/2008MNRAS.388.1335W + [2] Willott et al., + "The radio luminosity function from the low-frequency 3CRR, + 6CE and 7CRS complete samples", + 2001, MNRAS, 322, 536-552. + http://adsabs.harvard.edu/abs/2001MNRAS.322..536W + + Returns + ------- + rho_mat: np.ndarray + Number density matris (joint-distribution of luminosity and + reshift). + """ + # Init + rho_mat = np.zeros((len(self.lumobin), len(self.zbin))) + # Parameters + # Refer to [2] Table. 1 model C and Willman's section 2.4 + alpha = 2.27 # spectral index + lumo_star = 10.0**26.95 # critical luminosity + rho_l0 = 10.0**(-6.196) # normalization constant + z0 = 1.91 # center redshift + z2 = 1.378 # variance + # Calculation + for i, z in enumerate(self.zbin): + # space density revolusion + fh = np.exp(-0.5 * (z - z0)**2 / z2**2) + rho_mat[:, i] = ((rho_l0 * (10**self.lumobin / lumo_star) ** + (-alpha) * + np.exp(-lumo_star / 10.0**self.lumobin)) * + fh) + + return rho_mat + + def gen_lobe(self): + """ + Calculate lobe parameters + + References + ---------- + [1] Wilman et al., + "A semi-empirical simulation of the extragalactic radio continuum + sky for next generation radio telescopes", + 2008, MNRAS, 388, 1335-1348. + http://adsabs.harvard.edu/abs/2008MNRAS.388.1335W + + Return + ------ + lobe: list + lobe = [lobe_maj, lobe_min, lobe_ang], which represent the major + and minor axes and the rotation angle. + """ + D0 = 1 # [Mpc] + self.lobe_maj = 0.5 * np.random.uniform(0, D0 * (1 + self.z)**(-1.4)) + self.lobe_min = self.lobe_maj * np.random.uniform(0.2, 1) + self.lobe_ang = np.random.uniform(0, np.pi) / np.pi * 180 + + # Transform to pixel + self.lobe_maj = self.param.get_angle(self.lobe_maj) + self.lobe_min = self.param.get_angle(self.lobe_min) + lobe = [self.lobe_maj, self.lobe_min, self.lobe_ang] + + return lobe + + def gen_single_ps(self): + """ + Generate single point source, and return its data as a list. + + """ + # Redshift and luminosity + self.z, self.lumo = self.get_lumo_redshift() + self.lumo_sr = self.lumo + # angular diameter distance + self.param = PixelParams(self.z) + self.dA = self.param.dA + # W/Hz/Sr to Jy + dA = self.dA * 3.0856775814671917E+22 # Mpc to meter + self.lumo = self.lumo / dA**2 / (10.0**-24) # [Jy] + # Position + x = np.random.uniform(0, 1) + self.lat = (np.arccos(2 * x - 1) / np.pi * 180 - 90) # [deg] + self.lon = np.random.uniform(0, np.pi * 2) / np.pi * 180 # [deg] + # lobe + lobe = self.gen_lobe() + # Area + self.area = np.pi * self.lobe_maj * self.lobe_min + + ps_list = [self.z, self.dA, self.lumo, self.lat, self.lon, self.area] + ps_list.extend(lobe) + + return ps_list + + def draw_single_ps(self, freq): + """ + Designed to draw the elliptical lobes of FRI and FRII + + Prameters + --------- + nside: int and dyadic + self.ps_catalog: pandas.core.frame.DataFrame + Data of the point sources + ps_type: int + Class type of the point soruces + freq: float + frequency + """ + # Init + resolution = self.resolution / 60 # [degree] + npix = hp.nside2npix(self.nside) + hpmap = np.zeros((npix,)) + num_ps = self.ps_catalog.shape[0] + # Gen flux list + Tb_list = self.calc_Tb(freq) + ps_lobe = Tb_list[:, 1] + # Iteratively draw ps + for i in range(num_ps): + # Parameters + c_lat = self.ps_catalog['Lat (deg)'][i] # core lat [deg] + c_lon = self.ps_catalog['Lon (deg)'][i] # core lon [au.deg] + lobe_maj = self.ps_catalog['lobe_maj (rad)'][ + i] * 180 / np.pi # [deg] + lobe_min = self.ps_catalog['lobe_min (rad)'][ + i] * 180 / np.pi # [deg] + lobe_ang = self.ps_catalog['lobe_ang (deg)'][ + i] / 180 * np.pi # [rad] + # Offset to the core, refer to Willman Sec2.5.vii + offset = lobe_maj * 2 * np.random.uniform(0.2, 0.8) + # Lobe1 + lobe1_lat = (lobe_maj / 2 + offset) * np.cos(lobe_ang) + lobe1_lat = c_lat + lobe1_lat + lobe1_lon = (lobe_maj / 2 + offset) * np.sin(lobe_ang) + lobe1_lon = c_lon + lobe1_lon + # draw + # Fill with ellipse + lon, lat, gridmap = grid.make_grid_ellipse( + (lobe1_lon, lobe1_lat), (lobe_maj, lobe_min), + resolution, lobe_ang / np.pi * 180) + indices, values = grid.map_grid_to_healpix( + (lon, lat, gridmap), self.nside) + hpmap[indices] += ps_lobe[i] + + # lobe1_hotspot + lobe1_hot_lat = (lobe_maj + offset) * np.cos(lobe_ang) + lobe1_hot_lat = (c_lat + 90 + lobe1_lat) / 180 * np.pi + lobe1_hot_lon = (lobe_maj + offset) * np.sin(lobe_ang) + lobe1_hot_lon = (c_lon + lobe1_lon) / 180 * np.pi + if lobe1_hot_lat < 0: + lobe1_hot_lat += np.pi + elif lobe1_hot_lat > np.pi: + lobe1_hot_lat -= np.pi + lobe1_hot_index = hp.ang2pix( + self.nside, lobe1_hot_lat, lobe1_hot_lon) + hpmap[lobe1_hot_index] += Tb_list[i, 2] + + # Lobe2 + lobe2_lat = (lobe_maj / 2) * np.cos(lobe_ang + np.pi) + lobe2_lat = c_lat + lobe2_lat + lobe2_lon = (lobe_maj / 2) * np.sin(lobe_ang + np.pi) + lobe2_lon = c_lon + lobe2_lon + # draw + # Fill with ellipse + lon, lat, gridmap = grid.make_grid_ellipse( + (lobe2_lon, lobe2_lat), (lobe_maj, lobe_min), + resolution, lobe_ang / np.pi * 180) + indices, values = grid.map_grid_to_healpix( + (lon, lat, gridmap), self.nside) + hpmap[indices] += ps_lobe[i] + + # lobe2_hotspot + lobe2_hot_lat = (lobe_maj + offset) * np.cos(lobe_ang + np.pi) + lobe2_hot_lat = (c_lat + 90 + lobe1_lat) / 180 * np.pi + lobe2_hot_lon = (lobe_maj + offset) * np.sin(lobe_ang + np.pi) + lobe2_hot_lon = (c_lon + lobe1_lon) / 180 * np.pi + if lobe2_hot_lat < 0: + lobe2_hot_lat += np.pi + elif lobe2_hot_lat > np.pi: + lobe2_hot_lat -= np.pi + lobe2_hot_index = hp.ang2pix( + self.nside, lobe2_hot_lat, lobe2_hot_lon) + hpmap[lobe2_hot_index] += Tb_list[i, 2] + + # Core + pix_tmp = hp.ang2pix(self.nside, + (self.ps_catalog['Lat (deg)'] + 90) / + 180 * np.pi, self.ps_catalog['Lon (deg)'] / + 180 * np.pi) + ps_core = Tb_list[:, 0] + hpmap[pix_tmp] += ps_core + + return hpmap + + def draw_ps(self, freq): + """ + Read csv ps list file, and generate the healpix structure vector + with the respect frequency. + """ + # Init + num_freq = len(freq) + npix = hp.nside2npix(self.nside) + hpmaps = np.zeros((npix, num_freq)) + + # Gen ps_catalog + self.gen_catalog() + # get hpmaps + for i in range(num_freq): + hpmaps[:, i] = self.draw_single_ps(freq[i]) + + return hpmaps + + def calc_single_Tb(self, area, freq): + """ + Calculate brightness temperatur of a single ps + + Parameters + ------------ + area: `~astropy.units.Quantity` + Area of the PS, e.g., `1.0*au.sr` + freq: `~astropy.units.Quantity` + Frequency, e.g., `1.0*au.MHz` + + Return + ------ + Tb:`~astropy.units.Quantity` + Average brightness temperature, e.g., `1.0*au.K` + """ + # Init + freq_ref = 151 # [MHz] + freq = freq # [MHz] + # Luminosity at 151MHz + lumo_151 = self.lumo # [Jy] + # Calc flux + # core-to-extend ratio + ang = self.lobe_ang / 180 * np.pi + x = np.random.normal(self.xmed, 0.5) + beta = np.sqrt((self.gamma**2 - 1) / self.gamma) + B_theta = 0.5 * ((1 - beta * np.cos(ang))**-2 + + (1 + beta * np.cos(ang))**-2) + ratio_obs = 10**x * B_theta + # Core + lumo_core = ratio_obs / (1 + ratio_obs) * lumo_151 + a0 = (np.log10(lumo_core) - 0.07 * + np.log10(freq_ref * 10.0E-3) + + 0.29 * np.log10(freq_ref * 10.0E-3) * + np.log10(freq_ref * 10.0E-3)) + lgs = (a0 + 0.07 * np.log10(freq * 10.0E-3) - 0.29 * + np.log10(freq * 10.0E-3) * + np.log10(freq * 10.0E-3)) + flux_core = 10**lgs # [Jy] + # core area + npix = hp.nside2npix(self.nside) + core_area = 4 * np.pi / npix # [sr] + Tb_core = convert.Fnu_to_Tb_fast(flux_core, core_area, freq) # [K] + # lobe + lumo_lobe = lumo_151 * (1 - ratio_obs) / (1 + ratio_obs) # [Jy] + flux_lobe = (freq / freq_ref)**(-0.75) * lumo_lobe + Tb_lobe = convert.Fnu_to_Tb_fast(flux_lobe, area, freq) # [K] + + # hotspots + # Willman Eq. (3) + f_hs = (0.4 * (np.log10(self.lumo_sr) - 25.5) + + np.random.uniform(-0.5, 0.5)) + Tb_hotspot = Tb_lobe * (1 + f_hs) + Tb = [Tb_core, Tb_lobe, Tb_hotspot] + + return Tb + + def calc_Tb(self, freq): + """ + Calculate the surface brightness temperature of the point sources. + + Parameters + ------------ + area: `~astropy.units.Quantity` + Area of the PS, e.g., `1.0*au.sr` + freq: `~astropy.units.Quantity` + Frequency, e.g., `1.0*au.MHz` + + Return + ------ + Tb_list: list + Point sources brightness temperature + """ + # Tb_list + num_ps = self.ps_catalog.shape[0] + Tb_list = np.zeros((num_ps, 3)) + # Iteratively calculate Tb + for i in range(num_ps): + ps_area = self.ps_catalog['Area (sr)'][i] # [sr] + Tb_list[i, :] = self.calc_single_Tb(ps_area, freq) + + return Tb_list diff --git a/fg21sim/extragalactic/pointsources/pointsources.py b/fg21sim/extragalactic/pointsources/pointsources.py new file mode 100644 index 0000000..85f6ec4 --- /dev/null +++ b/fg21sim/extragalactic/pointsources/pointsources.py @@ -0,0 +1,126 @@ +# Copyright (c) 2016 Zhixian MA <zxma_sjtu@qq.com> +# MIT license + +""" +Extragalactic point sources (ps) simulation +""" + +import logging +import numpy as np +import healpy as hp +from collections import OrderedDict + +from .starforming import StarForming +from .starbursting import StarBursting +from .radioquiet import RadioQuiet +from .fr1 import FRI +from .fr2 import FRII + + +logger = logging.getLogger(__name__) + + +class PointSources: + """ + This class namely pointsource is designed to generate PS catalogs, + read csv format PS lists, calculate the flux and surface brightness + of the sources at different frequencies, and then ouput hpmaps + + Parameters + ---------- + configs: ConfigManager object + An 'ConfigManager' object contains default and user configurations. + For more details, see the example config specification. + + Functions + --------- + preprocessing + Generate the ps catalogs for each type. + simulate_frequency + Simualte point sources at provivded frequency + simulate + Simulate and project PSs to the healpix map. + postprocessing + Save catalogs + """ + PSCOMPONENTS_ALL = OrderedDict([ + ("starforming", StarForming), + ("starbursting", StarBursting), + ("radioquiet", RadioQuiet), + ("FRI", FRI), + ("FRII", FRII), + ]) + + def __init__(self, configs): + self.configs = configs + self._set_configs() + self.pscomps = OrderedDict() + for comp in self.pscomps_id: + logger.info("Initlalize PS component: {0}".format(comp)) + comp_type = self.PSCOMPONENTS_ALL[comp] + self.pscomps[comp] = comp_type(configs) + logger.info("Done initlalize %d PS components!" % + len(self.pscomps)) + + def _set_configs(self): + """Load configs and set the attributes""" + # Prefix of simulated point sources + self.pscomps_id = self.configs.getn("extragalactic/pscomponents") + if self.pscomps_id is None: + self.pscomps_id = ['starforming', 'starbursting', 'radioquiet', + 'FRI', 'FRII'] + print(self.pscomps_id) + # nside of the healpix cell + self.nside = self.configs.getn("common/nside") + # save flag + self.save = self.configs.getn("extragalactic/pointsources/save") + + def preprocess(self): + """Preprocess and generate the catalogs""" + logger.info("Generating PS catalogs...") + # Gen ps_catalog + for pscomp_obj in self.pscomps.values(): + pscomp_obj.gen_catalog() + logger.info("Generating PS catalogs done!") + + def simulate_frequency(self, freq): + """Simulate the point sources and output hpmaps""" + npix = hp.nside2npix(self.nside) + hpmap_f = np.zeros((npix,)) + # Projecting + logger.info("Generating PS hpmaps...") + for pscomp_obj in self.pscomps.values(): + hpmap_f += pscomp_obj.draw_single_ps(freq) + logger.info("Generating PS hpmaps done!") + + return hpmap_f + + def simulate(self, frequencies): + """Simulate the emission (HEALPix) maps of all Galactic SNRs for + every specified frequency. + + Parameters + ---------- + frequency : list[float] + List of frequencies (unit: `self.freq_unit`) where the + simulation performed. + + Returns + ------- + hpmaps : list[1D `~numpy.ndarray`] + List of HEALPix maps (in RING ordering) at each frequency. + """ + hpmaps = [] + for f in np.array(frequencies, ndmin=1): + hpmap_f = self.simulate_frequency(f) + hpmaps.append(hpmap_f) + return hpmaps + + def postprocess(self): + """Perform the post-simulation operations before the end.""" + # Save the catalog actually used in the simulation + if self.save: + logger.info("Saving simulated catalogs...") + for pscomp_obj in self.pscomps.values(): + pscomp_obj.save_as_csv() + logger.info("Saving simulated catalogs done!") diff --git a/fg21sim/extragalactic/pointsources/psparams.py b/fg21sim/extragalactic/pointsources/psparams.py new file mode 100644 index 0000000..7c3a93a --- /dev/null +++ b/fg21sim/extragalactic/pointsources/psparams.py @@ -0,0 +1,77 @@ +# Copyright (c) 2016 Zhixian MA <zxma_sjtu@qq.com> +# MIT license + +""" +Basic parameters to be used in point sources simulation. + +References +---------- +[1] Angular diameter distance, + https://en.wikipedia.org/wiki/Angular_diameter_distance +[2] FlatLambdaCDM, + {http://docs.astropy.org/en/stable/api/astropy.cosmology. + FlatLambdaCDM.html#astropy.cosmology.FlatLambdaCDM} +""" + +from astropy.cosmology import FlatLambdaCDM + + +class PixelParams(): + """ + A class to transform cosmology distance to angles or pixels. + Parameters + ------------ + H0: float + The hubble constant at z = 0, whose unit is km/s/Mpc + Om0: float + The total matter density. + ang_res: float + Angular resolution, i.e. degree per pixel. (May be useless) + ang_total: list + Total angles of the simulated sky region,whose unit is degree (\deg) + z : float + Redshift + scale: float + The real object scale. + Example + ------------ + >>> PixelParams = PixelParams(img_size=(1024,1024),ang_total=(5,5)) + """ + + # Hubble constant at z = 0 + H0 = 71.0 + # Omega0, total matter density + Om0 = 0.27 + # Redshift + z = 0.0 + # Cosmology calculator + cosmo = 0.0 + # angular diameter distance, [Mpc] + dA = 0.0 + # angular resolution + ang_res = 0.0 + + def __init__(self, z=0.0): + self.z = z + self.cosmo = FlatLambdaCDM(H0=self.H0, Om0=self.Om0) + + # angular diameter distance, [Mpc] + self.dA = self.cosmo.angular_diameter_distance(self.z).value + + def get_angle(self, scale=1.0): + """ + Input real object scale, and output the respect observed + angle, and pixels. + """ + ang = scale / self.dA # [rac] + + return ang + + def get_scale(self, ang=1.0): + """ + Input real observed scale, and output the respect + real object scale, and pixels. + """ + scale = ang * self.dA # [Mpc] + + return scale diff --git a/fg21sim/extragalactic/pointsources/radioquiet.py b/fg21sim/extragalactic/pointsources/radioquiet.py new file mode 100644 index 0000000..2d2eefe --- /dev/null +++ b/fg21sim/extragalactic/pointsources/radioquiet.py @@ -0,0 +1,189 @@ +# Copyright (c) 2016 Zhixian MA <zxma_sjtu@qq.com> +# MIT license + +import numpy as np +import healpy as hp + +from .base import BasePointSource +from ...utils import convert + + +class RadioQuiet(BasePointSource): + + def __init__(self, configs): + super().__init__(configs) + self._set_configs() + # Number density matrix + self.rho_mat = self.calc_number_density() + # Cumulative distribution of z and lumo + self.cdf_z, self.cdf_lumo = self.calc_cdf() + + def _set_configs(self): + """Load the configs and set the corresponding class attributes""" + super()._set_configs() + pscomp = "extragalactic/pointsources/radioquiet/" + # point sources amount + self.num_ps = self.configs.getn(pscomp+"numps") + # prefix + self.prefix = self.configs.getn(pscomp+"prefix") + # redshift bin + z_type = self.configs.getn(pscomp+"z_type") + if z_type == 'custom': + start = self.configs.getn(pscomp+"z_start") + stop = self.configs.getn(pscomp+"z_stop") + step = self.configs.getn(pscomp+"z_step") + self.zbin = np.arange(start, stop + step, step) + else: + self.zbin = np.arange(0.1, 10, 0.05) + # luminosity bin + lumo_type = self.configs.getn(pscomp+"lumo_type") + if lumo_type == 'custom': + start = self.configs.getn(pscomp+"lumo_start") + stop = self.configs.getn(pscomp+"lumo_stop") + step = self.configs.getn(pscomp+"lumo_step") + self.lumobin = np.arange(start, stop + step, step) + else: + self.lumobin = np.arange(18.7, 25.7, 0.1) # [W/Hz/sr] + + def calc_number_density(self): + """ + Calculate number density rho(lumo,z) of FRI + + References + ------------ + [1] Wilman et al., + "A semi-empirical simulation of the extragalactic radio continuum + sky for next generation radio telescopes", + 2008, MNRAS, 388, 1335-1348. + http://adsabs.harvard.edu/abs/2008MNRAS.388.1335W + + Returns + ------- + rho_mat: np.ndarray + Number density matris (joint-distribution of luminosity and + reshift). + """ + # Init + rho_mat = np.zeros((len(self.lumobin), len(self.zbin))) + # Parameters + # Refer to Willman's section 2.4 + alpha = 0.7 # spectral index + lumo_star = 10.0**21.3 # critical luminosity at 1400MHz + rho_l0 = 10.0**(-7) # normalization constant + z1 = 1.9 # cut-off redshift + k1 = -3.27 # index of space density revolution + # Calculation + for i, z in enumerate(self.zbin): + if z <= z1: + rho_mat[:, i] = ((rho_l0 * (10**self.lumobin / lumo_star) ** + (-alpha) * + np.exp(-10**self.lumobin / lumo_star)) * + (1 + z)**k1) + else: + rho_mat[:, i] = ((rho_l0 * (10**self.lumobin / lumo_star) ** + (-alpha) * + np.exp(-10**self.lumobin / lumo_star)) * + (1 + z1)**k1) + return rho_mat + + def draw_single_ps(self, freq): + """ + Designed to draw the radio quiet AGN + + Parameters + ---------- + ImgMat: np.ndarray + Two dimensional matrix, to describe the image + self.ps_catalog: pandas.core.frame.DataFrame + Data of the point sources + freq: float + frequency + """ + # Init + npix = hp.nside2npix(self.nside) + hpmap = np.zeros((npix,)) + # Gen Tb list + Tb_list = self.calc_Tb(freq) + # Iteratively draw the ps + num_ps = self.ps_catalog.shape[0] + for i in range(num_ps): + # Angle to pix + lat = (self.ps_catalog['Lat (deg)'] + 90) / 180 * np.pi + lon = self.ps_catalog['Lon (deg)'] / 180 * np.pi + pix = hp.ang2pix(self.nside, lat, lon) + # Gen hpmap + hpmap[pix] += Tb_list[i] + + return hpmap + + def draw_ps(self, freq): + """ + Read csv ps list file, and generate the healpix structure vector + with the respect frequency. + """ + # Init + num_freq = len(freq) + npix = hp.nside2npix(self.nside) + hpmaps = np.zeros((npix, num_freq)) + + # Gen ps_catalog + self.gen_catalog() + # get hpmaps + for i in range(num_freq): + hpmaps[:, i] = self.draw_single_ps(freq[i]) + + return hpmaps + + def calc_single_Tb(self, area, freq): + """ + Calculate brightness temperatur of a single ps + + Parameters + ------------ + area: `~astropy.units.Quantity` + Area of the PS, e.g., `1.0*au.sr2` + freq: `~astropy.units.Quantity` + Frequency, e.g., `1.0*au.MHz` + + Return + ------ + Tb:`~astropy.units.Quantity` + Average brightness temperature, e.g., `1.0*au.K` + """ + # Init + freq_ref = 1400 # [MHz] + freq = freq # [MHz] + # Luminosity at 1400MHz + lumo_1400 = self.lumo # [Jy] + # Calc flux + flux = (freq / freq_ref)**(-0.7) * lumo_1400 + # Calc brightness temperature + Tb = convert.Fnu_to_Tb_fast(flux, area, freq) + + return Tb + + def calc_Tb(self, freq): + """ + Calculate the surface brightness temperature of the point sources. + + Parameters + ------------ + area: `~astropy.units.Quantity` + Area of the PS, e.g., `1.0*au.sr` + freq: `~astropy.units.Quantity` + Frequency, e.g., `1.0*au.MHz` + + Return + ------ + Tb_list: list + Point sources brightness temperature + """ + # Tb_list + num_ps = self.ps_catalog.shape[0] + Tb_list = np.zeros((num_ps,)) + # Iteratively calculate Tb + for i in range(num_ps): + ps_area = self.ps_catalog['Area (sr)'][i] # [sr] + Tb_list[i] = self.calc_single_Tb(ps_area, freq) + + return Tb_list diff --git a/fg21sim/extragalactic/pointsources/starbursting.py b/fg21sim/extragalactic/pointsources/starbursting.py new file mode 100644 index 0000000..8b5c597 --- /dev/null +++ b/fg21sim/extragalactic/pointsources/starbursting.py @@ -0,0 +1,238 @@ +# Copyright (c) 2016 Zhixian MA <zxma_sjtu@qq.com> +# MIT license + +import numpy as np +import healpy as hp + +from .psparams import PixelParams +from .base import BasePointSource +from ...utils import grid +from ...utils import convert + + +class StarBursting(BasePointSource): + + """ + Generate star forming point sources, inheritate from PointSource class. + """ + # Init + + def __init__(self, configs): + super().__init__(configs) + self.columns.append('radius (rad)') + self.nCols = len(self.columns) + self._set_configs() + # Number density matrix + self.rho_mat = self.calc_number_density() + # Cumulative distribution of z and lumo + self.cdf_z, self.cdf_lumo = self.calc_cdf() + + def _set_configs(self): + """Load the configs and set the corresponding class attributes""" + super()._set_configs() + pscomp = "extragalactic/pointsources/starbursting/" + # point sources amount + self.num_ps = self.configs.getn(pscomp+"numps") + # prefix + self.prefix = self.configs.getn(pscomp+"prefix") + # redshift bin + z_type = self.configs.getn(pscomp+"z_type") + if z_type == 'custom': + start = self.configs.getn(pscomp+"z_start") + stop = self.configs.getn(pscomp+"z_stop") + step = self.configs.getn(pscomp+"z_step") + self.zbin = np.arange(start, stop + step, step) + else: + self.zbin = np.arange(0.1, 10, 0.05) + # luminosity bin + lumo_type = self.configs.getn(pscomp+"lumo_type") + if lumo_type == 'custom': + start = self.configs.getn(pscomp+"lumo_start") + stop = self.configs.getn(pscomp+"lumo_stop") + step = self.configs.getn(pscomp+"lumo_step") + self.lumobin = np.arange(start, stop + step, step) + else: + self.lumobin = np.arange(21, 27, 0.1) # [W/Hz/sr] + + def calc_number_density(self): + """ + Calculate number density rho(lumo,z) of FRI + + References + ---------- + [1] Wilman et al., + "A semi-empirical simulation of the extragalactic radio continuum + sky for next generation radio telescopes", + 2008, MNRAS, 388, 1335-1348. + http://adsabs.harvard.edu/abs/2008MNRAS.388.1335W + + Returns + ------- + rho_mat: np.ndarray + Number density matris (joint-distribution of luminosity and + reshift). + """ + # Init + rho_mat = np.zeros((len(self.lumobin), len(self.zbin))) + # Parameters + # Refer to Willman's section 2.4 + alpha = 0.7 # spectral index + lumo_star = 10.0**22 # critical luminosity at 1400MHz + rho_l0 = 10.0**(-7) # normalization constant + z1 = 1.5 # cut-off redshift + k1 = 3.1 # index of space density revolution + # Calculation + for i, z in enumerate(self.zbin): + if z <= z1: + rho_mat[:, i] = ((rho_l0 * (10**self.lumobin / lumo_star) ** + (-alpha) * + np.exp(-10**self.lumobin / lumo_star)) * + (1 + z)**k1) + else: + rho_mat[:, i] = ((rho_l0 * (10**self.lumobin / lumo_star) ** + (-alpha) * + np.exp(-10**self.lumobin / lumo_star)) * + (1 + z1)**k1) + return rho_mat + + def get_radius(self): + if self.z <= 1.5: + self.radius = (1 + self.z)**2.5 * 1e-3 / 2 # [Mpc] + else: + self.radius = 10 * 1e-3 / 2 # [Mpc] + + return self.radius + + def gen_single_ps(self): + """ + Generate single point source, and return its data as a list. + """ + # Redshift and luminosity + self.z, self.lumo = self.get_lumo_redshift() + # angular diameter distance + self.param = PixelParams(self.z) + self.dA = self.param.dA + # W/Hz/Sr to Jy + dA = self.dA * 3.0856775814671917E+22 # Mpc to meter + self.lumo = self.lumo / dA**2 / (10.0**-24) # [Jy] + # Position + x = np.random.uniform(0, 1) + self.lat = (np.arccos(2 * x - 1) / np.pi * 180 - 90) # [deg] + self.lon = np.random.uniform(0, np.pi * 2) / np.pi * 180 # [deg] + # Radius + self.radius = self.param.get_angle(self.get_radius()) # [rad] + # Area + self.area = np.pi * self.radius**2 # [sr] + + ps_list = [self.z, self.dA, self.lumo, self.lat, self.lon, + self.area, self.radius] + + return ps_list + + def draw_single_ps(self, freq): + """ + Designed to draw the circular star forming and star bursting ps. + + Prameters + --------- + nside: int and dyadic + number of sub pixel in a cell of the healpix structure + self.ps_catalog: pandas.core.frame.DataFrame + Data of the point sources + freq: float + frequency + """ + # Init + npix = hp.nside2npix(self.nside) + hpmap = np.zeros((npix,)) + # Gen Tb list + Tb_list = self.calc_Tb(freq) + # Iteratively draw the ps + num_ps = self.ps_catalog.shape[0] + resolution = self.resolution / 60 # [degree] + for i in range(num_ps): + # grid + ps_radius = self.ps_catalog['radius (rad)'][i] # [rad] + ps_radius = ps_radius * 180 / np.pi # radius[rad] + c_lat = self.ps_catalog['Lat (deg)'][i] # core_lat [deg] + c_lon = self.ps_catalog['Lon (deg)'][i] # core_lon [deg] + # Fill with circle + lon, lat, gridmap = grid.make_grid_ellipse( + (c_lon, c_lat), (2 * ps_radius, 2 * ps_radius), resolution) + indices, values = grid.map_grid_to_healpix( + (lon, lat, gridmap), self.nside) + hpmap[indices] += Tb_list[i] + + return hpmap + + def draw_ps(self, freq): + """ + Read csv ps list file, and generate the healpix structure vector + with the respect frequency. + """ + # Init + num_freq = len(freq) + npix = hp.nside2npix(self.nside) + hpmaps = np.zeros((npix, num_freq)) + + # Gen ps_catalog + self.gen_catalog() + # get hpmaps + for i in range(num_freq): + hpmaps[:, i] = self.draw_single_ps(freq[i]) + + return hpmaps + + def calc_single_Tb(self, area, freq): + """ + Calculate brightness temperatur of a single ps + + Parameters + ------------ + area: `~astropy.units.Quantity` + Area of the PS, e.g., `1.0*au.sr2` + freq: `~astropy.units.Quantity` + Frequency, e.g., `1.0*au.MHz` + + Return + ------ + Tb:`~astropy.units.Quantity` + Average brightness temperature, e.g., `1.0*au.K` + """ + # Init + freq_ref = 1400 # [MHz] + freq = freq # [MHz] + # Luminosity at 1400MHz + lumo_1400 = self.lumo # [Jy] + # Calc flux + flux = (freq / freq_ref)**(-0.7) * lumo_1400 + # Calc brightness temperature + Tb = convert.Fnu_to_Tb_fast(flux, area, freq) + + return Tb + + def calc_Tb(self, freq): + """ + Calculate the surface brightness temperature of the point sources. + + Parameters + ------------ + area: `~astropy.units.Quantity` + Area of the PS, e.g., `1.0*au.sr` + freq: `~astropy.units.Quantity` + Frequency, e.g., `1.0*au.MHz` + + Return + ------ + Tb_list: list + Point sources brightness temperature + """ + # Tb_list + num_ps = self.ps_catalog.shape[0] + Tb_list = np.zeros((num_ps,)) + # Iteratively calculate Tb + for i in range(num_ps): + ps_area = self.ps_catalog['Area (sr)'][i] # [sr] + Tb_list[i] = self.calc_single_Tb(ps_area, freq) + + return Tb_list diff --git a/fg21sim/extragalactic/pointsources/starforming.py b/fg21sim/extragalactic/pointsources/starforming.py new file mode 100644 index 0000000..5d518d8 --- /dev/null +++ b/fg21sim/extragalactic/pointsources/starforming.py @@ -0,0 +1,257 @@ +# Copyright (c) 2016 Zhixian MA <zxma_sjtu@qq.com> +# MIT license + +import numpy as np +import healpy as hp + +# from .psparams import PixelParams +from .base import BasePointSource +from ...utils import grid +from ...utils import convert +from .psparams import PixelParams + + +class StarForming(BasePointSource): + """ + Generate star forming point sources, inheritate from PointSource class. + + Reference + --------- + [1] Fast cirles drawing + https://github.com/liweitianux/fg21sim/fg21sim/utils/draw.py + https://github.com/liweitianux/fg21sim/fg21sim/utils/grid.py + """ + + def __init__(self, configs): + super().__init__(configs) + self.columns.append('radius (rad)') + self.nCols = len(self.columns) + self._set_configs() + # Number density matrix + self.rho_mat = self.calc_number_density() + # Cumulative distribution of z and lumo + self.cdf_z, self.cdf_lumo = self.calc_cdf() + + def _set_configs(self): + """ Load the configs and set the corresponding class attributes""" + super()._set_configs() + pscomp = "extragalactic/pointsources/starforming/" + # point sources amount + self.num_ps = self.configs.getn(pscomp+"numps") + # prefix + self.prefix = self.configs.getn(pscomp+"prefix") + # redshift bin + z_type = self.configs.getn(pscomp+"z_type") + if z_type == 'custom': + start = self.configs.getn(pscomp+"z_start") + stop = self.configs.getn(pscomp+"z_stop") + step = self.configs.getn(pscomp+"z_step") + self.zbin = np.arange(start, stop + step, step) + else: + self.zbin = np.arange(0.1, 10, 0.05) + # luminosity bin + lumo_type = self.configs.getn(pscomp+"lumo_type") + if lumo_type == 'custom': + start = self.configs.getn(pscomp+"lumo_start") + stop = self.configs.getn(pscomp+"lumo_stop") + step = self.configs.getn(pscomp+"lumo_step") + self.lumobin = np.arange(start, stop + step, step) + else: + self.lumobin = np.arange(17, 25.5, 0.1) # [W/Hz/sr] + + def calc_number_density(self): + """ + Calculate number density rho(lumo,z) of FRI + + References + ---------- + [1] Wilman et al., + "A semi-empirical simulation of the extragalactic radio continuum + sky for next generation radio telescopes", + 2008, MNRAS, 388, 1335-1348. + http://adsabs.harvard.edu/abs/2008MNRAS.388.1335W + + Returns + ------- + rho_mat: np.ndarray + Number density matris (joint-distribution of luminosity and + reshift). + """ + # Init + rho_mat = np.zeros((len(self.lumobin), len(self.zbin))) + # Parameters + # Refer to Willman's section 2.4 + alpha = 0.7 # spectral index + lumo_star = 10.0**22 # critical luminosity at 1400MHz + rho_l0 = 10.0**(-7) # normalization constant + z1 = 1.5 # cut-off redshift + k1 = 3.1 # index of space density revolution + # Calculation + for i, z in enumerate(self.zbin): + if z <= z1: + rho_mat[:, i] = (rho_l0 * (10**self.lumobin / lumo_star) ** + (-alpha) * np.exp(-10**self.lumobin / + lumo_star) * (1 + z)**k1) + else: + rho_mat[:, i] = (rho_l0 * (10**self.lumobin / lumo_star) ** + (-alpha) * np.exp(-10**self.lumobin / + lumo_star) * (1 + z1)**k1) + + return rho_mat + + def get_radius(self): + """ + Generate the disc diameter of normal starforming galaxies. + + Reference + --------- + [1] Wilman et al., Eq(7-9), + "A semi-empirical simulation of the extragalactic radio continuum + sky for next generation radio telescopes", + 2008, MNRAS, 388, 1335-1348. + http://adsabs.harvard.edu/abs/2008MNRAS.388.1335W + """ + # Willman Eq. (8) + delta = np.random.normal(0, 0.3) + log_M_HI = 0.44 * np.log10(self.lumo) + 0.48 + delta + # Willman Eq. (7) + log_D_HI = ((log_M_HI - (6.52 + np.random.uniform(-0.06, 0.06))) / + 1.96 + np.random.uniform(-0.04, 0.04)) + # Willman Eq. (9) + log_D = log_D_HI - 0.23 - np.log10(1 + self.z) + self.radius = 10**log_D / 2 * 1e-3 # [Mpc] + return self.radius + + def gen_single_ps(self): + """ + Generate single point source, and return its data as a list. + """ + # Redshift and luminosity + self.z, self.lumo = self.get_lumo_redshift() + # angular diameter distance + self.param = PixelParams(self.z) + self.dA = self.param.dA + # Radius + self.radius = self.param.get_angle(self.get_radius()) # [rad] + # W/Hz/Sr to Jy + dA = self.dA * 3.0856775814671917E+22 # Mpc to meter + self.lumo = self.lumo / dA**2 / (10.0**-24) # [Jy] + # Position + x = np.random.uniform(0, 1) + self.lat = (np.arccos(2 * x - 1) / np.pi * 180 - 90) # [deg] + self.lon = np.random.uniform(0, np.pi * 2) / np.pi * 180 # [deg] + # Area + self.area = np.pi * self.radius**2 # [sr] ? + + ps_list = [self.z, self.dA, self.lumo, self.lat, self.lon, + self.area, self.radius] + + return ps_list + + def draw_single_ps(self, freq): + """ + Designed to draw the circular star forming and star bursting ps. + + Prameters + --------- + nside: int and dyadic + number of sub pixel in a cell of the healpix structure + self.ps_catalog: pandas.core.frame.DataFrame + Data of the point sources + freq: float + frequency + """ + # Init + npix = hp.nside2npix(self.nside) + hpmap = np.zeros((npix,)) + # Gen Tb list + Tb_list = self.calc_Tb(freq) + # Iteratively draw the ps + num_ps = self.ps_catalog.shape[0] + resolution = self.resolution / 60 # [degree] + for i in range(num_ps): + # grid + ps_radius = self.ps_catalog['radius (rad)'][i] # [rad] + ps_radius = ps_radius * 180 / np.pi # radius [deg] + c_lat = self.ps_catalog['Lat (deg)'][i] # core_lat [deg] + c_lon = self.ps_catalog['Lon (deg)'][i] # core_lon [deg] + # Fill with circle + lon, lat, gridmap = grid.make_grid_ellipse( + (c_lon, c_lat), (2 * ps_radius, 2 * ps_radius), resolution) + indices, values = grid.map_grid_to_healpix( + (lon, lat, gridmap), self.nside) + hpmap[indices] += Tb_list[i] + + return hpmap + + def draw_ps(self, freq): + """ + Read csv ps list file, and generate the healpix structure vector + with the respect frequency. + """ + # Init + num_freq = len(freq) + npix = hp.nside2npix(self.nside) + hpmaps = np.zeros((npix, num_freq)) + + # Gen ps_catalog + self.gen_catalog() + # get hpmaps + for i in range(num_freq): + hpmaps[:, i] = self.draw_single_ps(freq[i]) + + return hpmaps + + def calc_single_Tb(self, area, freq): + """ + Calculate brightness temperatur of a single ps + + Parameters + ------------ + area: `~astropy.units.Quantity` + Area of the PS, e.g., `1.0*au.sr2` + freq: `~astropy.units.Quantity` + Frequency, e.g., `1.0*au.MHz` + + Return + ------ + Tb:`~astropy.units.Quantity` + Average brightness temperature, e.g., `1.0*au.K` + """ + # Init + freq_ref = 1400 # [MHz] + freq = freq # [MHz] + # Luminosity at 1400MHz + lumo_1400 = self.lumo # [Jy] + # Calc flux + flux = (freq / freq_ref)**(-0.7) * lumo_1400 + # Calc brightness temperature + Tb = convert.Fnu_to_Tb_fast(flux, area, freq) + + return Tb + + def calc_Tb(self, freq): + """ + Calculate the surface brightness temperature of the point sources. + + Parameters + ------------ + area: `~astropy.units.Quantity` + Area of the PS, e.g., `1.0*au.sr` + freq: `~astropy.units.Quantity` + Frequency, e.g., `1.0*au.MHz` + + Return + ------ + Tb_list: list + Point sources brightness temperature + """ + # Tb_list + num_ps = self.ps_catalog.shape[0] + Tb_list = np.zeros((num_ps,)) + # Iteratively calculate Tb + for i in range(num_ps): + ps_area = self.ps_catalog['Area (sr)'][i] # [sr] + Tb_list[i] = self.calc_single_Tb(ps_area, freq) + + return Tb_list |