Source code for frb.surveys.cluster_search

"""
A module to query for galaxy groups/clusters around a given FRB.
Currently has only the Tully cluster catalog but can be possibly extended for
other sources.
"""

from . import surveycoord
from frb.defs import frb_cosmo
from astropy.coordinates import SkyCoord
from astropy import units as u
from astropy.table import Table

try:
    from astroquery.vizier import Vizier
except ImportError:
    print("Warning: You need to install astroquery to use the cluster searches...")

import numpy as np

[docs] class VizierCatalogSearch(surveycoord.SurveyCoord): """ A class to query sources within a Vizier catalog. """
[docs] def __init__(self, coord, radius = 90*u.deg, survey=None, viziercatalog = None, cosmo=None, **kwargs): # Initialize a SurveyCoord object super(VizierCatalogSearch, self).__init__(coord, radius, **kwargs) self.survey = survey # Name self.viziercatalog = viziercatalog # Name of the Vizier table to draw from. self.coord = coord # Location around which to perform the search self.radius = radius.to('deg').value # Radius of cone search if cosmo is None: # Use the same cosmology as elsewhere in this repository unless specified. self.cosmo = frb_cosmo else: self.cosmo = cosmo
[docs] def clean_catalog(self, catalog): """ This will be survey specific. """ pass
def _transverse_distance_cut(self, catalog, transverse_distance_cut, distance_column='Dist'): # Apply a transverse distance cut angular_dist = self.coord.separation(SkyCoord(catalog['ra'], catalog['dec'], unit='deg')).to('rad').value transverse_dist = catalog[distance_column]*np.sin(angular_dist) catalog = catalog[transverse_dist<transverse_distance_cut] return catalog def _get_catalog(self, query_fields=None, **kwargs): """ Get the catalog of objects Args: z_lim (float): The maximum redshift of the objects to include in the catalog. transverse_distance_cut (Quantity): The maximum impact parameter of the objects to include in the catalog. richness_cut (int): The minimum number of members in any group/cluster returned. query_fields (list): The fields to include in the catalog. If None, all fields are used. Returns: A table of objects within the given limits. If no objects found, returns an empty table with fields ['ra','dec', and 'z']. """ if query_fields is None: query_fields = ['**'] # Get all. # Query Vizier v = Vizier(catalog = self.viziercatalog, columns=query_fields, row_limit= -1, **kwargs) # No row limit result = v.query_region(self.coord, radius=self.radius*u.deg) if len(result) == 0: print("No objects found within the given radius.") return Table(names = ('ra','dec','z')) else: result = result[0] return result
# Tully 2015
[docs] class TullyGroupCat(VizierCatalogSearch): """ A class to query sources within the Tully 2015 group/cluster catalog. """
[docs] def __init__(self, coord, radius = 90*u.deg, cosmo=None, **kwargs): # Initialize a SurveyCoord object super(TullyGroupCat, self).__init__(coord, radius, survey="Tully+2015", viziercatalog="J/AJ/149/171/table5", cosmo=cosmo, **kwargs)
[docs] def clean_catalog(self, catalog): catalog.rename_columns(['_RA.icrs', '_DE.icrs', 'Nmb'], ['ra', 'dec', 'Ngal']) # Rename the columns to match the SurveyCoord class # Convert distances from h^-1 Mpc to Mpc based on the cosmology being used. catalog['Dist'] /=self.cosmo.h rec_velocity = catalog['Dist'].value*self.cosmo.H0.value c_kms = 299792.458 redshift = ((1+rec_velocity/c_kms)/(1-rec_velocity/c_kms))**0.5-1 catalog['Dist'] = self.cosmo.angular_diameter_distance(redshift).value return catalog
[docs] def get_catalog(self, query_fields=None, transverse_distance_cut = np.inf*u.Mpc, richness_cut = 5): """ Get the catalog of objects Args: z_lim (float): The maximum redshift of the objects to include in the catalog. transverse_distance_cut (Quantity): The maximum impact parameter of the objects to include in the catalog. richness_cut (int): The minimum number of members in any group/cluster returned. query_fields (list): The fields to include in the catalog. If None, all fields are used. Returns: A table of objects within the given limits. """ result = super(TullyGroupCat, self)._get_catalog(query_fields=query_fields) if len(result) > 0: result = self.clean_catalog(result) # Apply a transverse distance cut if transverse_distance_cut<np.inf*u.Mpc: result = super(TullyGroupCat, self)._transverse_distance_cut(result, transverse_distance_cut) result = result[result['Ngal']>=richness_cut] self.catalog = result return self.catalog
# Wen+2024
[docs] class WenGroupCat(VizierCatalogSearch): """ A class to query sources within the Wen+2024 group/cluster catalog. """
[docs] def __init__(self, coord, radius = 0.2*u.deg, cosmo=None, **kwargs): # Initialize a SurveyCoord object super(WenGroupCat, self).__init__(coord, radius, survey="Wen+2024", viziercatalog="J/ApJS/272/39/table2", cosmo=cosmo, **kwargs)
[docs] def clean_catalog(self, catalog): try: catalog.rename_columns(['RAJ2000'],['ra']) except KeyError: assert 'ra' in catalog.keys() try: catalog.rename_columns(['DEJ2000'],['dec']) except KeyError: assert 'dec' in catalog.keys() try: catalog.rename_columns(['zCl'],['z']) except KeyError: assert 'z' in catalog.keys() # Add a distance estimate in Mpc using the given cosmology catalog['Dist'] = self.cosmo.angular_diameter_distance(catalog['z']).value return catalog
[docs] def get_catalog(self, query_fields=None, transverse_distance_cut = np.inf*u.Mpc, richness_cut = 5): """ Get the catalog of objects Args: z_lim (float): The maximum redshift of the objects to include in the catalog. transverse_distance_cut (Quantity): The maximum impact parameter of the objects to include in the catalog. richness_cut (int): The minimum number of members in any group/cluster returned. query_fields (list): The fields to include in the catalog. If None, all fields are used. Returns: A table of objects within the given limits. """ result = super(WenGroupCat, self)._get_catalog(query_fields=query_fields) if len(result) > 0: result = self.clean_catalog(result) # Apply a transverse distance cut if transverse_distance_cut<np.inf*u.Mpc: result = super(TullyGroupCat, self)._transverse_distance_cut(result, transverse_distance_cut) result = result[result['Ngal']>=richness_cut] self.catalog = result return self.catalog
# Bahk and Hwang 2024 (Updated Planck+2015)
[docs] class UPClusterSZCat(VizierCatalogSearch): """ A class to query sources within the Bahk and Hwang 2024 group/cluster catalog. """
[docs] def __init__(self, coord, radius = 90*u.deg, cosmo=None, **kwargs): # Initialize a SurveyCoord object super(UPClusterSZCat, self).__init__(coord, radius, survey="UPClusterSZ", viziercatalog="J/ApJS/272/7/table2", cosmo=cosmo, **kwargs)
[docs] def clean_catalog(self, catalog): if len(catalog) > 0: try: catalog.rename_columns(['RAJ2000', 'DEJ2000'], ['ra', 'dec']) # Rename the columns to match the SurveyCoord class except KeyError: print(catalog.keys()) assert 'ra' in catalog.keys() and 'dec' in catalog.keys() # Add a distance estimate in Mpc using the given cosmology catalog['Dist'] = self.cosmo.angular_diameter_distance(catalog['z']).value return catalog
[docs] def get_catalog(self, query_fields=None, transverse_distance_cut = np.inf*u.Mpc): """ Get the catalog of objects Args: z_lim (float): The maximum redshift of the objects to include in the catalog. transverse_distance_cut (Quantity): The maximum impact parameter of the objects to include in the catalog. richness_cut (int): The minimum number of members in any group/cluster returned. query_fields (list): The fields to include in the catalog. If None, all fields are used. Returns: A table of objects within the given limits. """ result = super(UPClusterSZCat, self)._get_catalog(query_fields=query_fields) if len(result) > 0: result = self.clean_catalog(result) # Apply a transverse distance cut if transverse_distance_cut<np.inf*u.Mpc: result = super(UPClusterSZCat, self)._transverse_distance_cut(result, transverse_distance_cut) self.catalog = result return self.catalog
# Xu+2022 (ROSAT X ray cluster)
[docs] class ROSATXClusterCat(VizierCatalogSearch): """ A class to query sources within the Xu+2022 group/cluster catalog. """
[docs] def __init__(self, coord, radius = 90*u.deg, cosmo=None, **kwargs): # Initialize a SurveyCoord object super(ROSATXClusterCat, self).__init__(coord, radius, survey="ROSATXCluster", viziercatalog="J/A+A/658/A59/table3", cosmo=cosmo, **kwargs)
[docs] def clean_catalog(self, catalog): if len(catalog) > 0: catalog.rename_columns(['RAJ2000', 'DEJ2000'], ['ra', 'dec']) # Rename the columns to match the SurveyCoord class # Add a distance estimate in Mpc using the given cosmology catalog['Dist'] = self.cosmo.angular_diameter_distance(catalog['z']).value return catalog
[docs] def get_catalog(self, query_fields=None, transverse_distance_cut = np.inf*u.Mpc): """ Get the catalog of objects Args: z_lim (float): The maximum redshift of the objects to include in the catalog. transverse_distance_cut (Quantity): The maximum impact parameter of the objects to include in the catalog. richness_cut (int): The minimum number of members in any group/cluster returned. query_fields (list): The fields to include in the catalog. If None, all fields are used. Returns: A table of objects within the given limits. """ result = super(ROSATXClusterCat, self)._get_catalog(query_fields=query_fields) if len(result) > 0: result = self.clean_catalog(result) # Apply a transverse distance cut if transverse_distance_cut<np.inf*u.Mpc: result = super(ROSATXClusterCat, self)._transverse_distance_cut(result, transverse_distance_cut) self.catalog = result return self.catalog
# Tempel+2018
[docs] class TempelClusterCat(VizierCatalogSearch): """ A class to query sources within the Tempel+2018 group/cluster catalog. """
[docs] def __init__(self, coord, radius = 90*u.deg, cosmo=None, **kwargs): # Initialize a SurveyCoord object super(TempelClusterCat, self).__init__(coord, radius, survey="TempelCluster", viziercatalog="J/A+A/618/A81/2mrs_gr", cosmo=cosmo, **kwargs)
[docs] def clean_catalog(self, catalog): if len(catalog) > 0: catalog.rename_columns(['RAJ2000', 'DEJ2000', 'zcmb'], ['ra', 'dec', 'z']) # Rename the columns to match the SurveyCoord class # Add a distance estimate in Mpc using the given cosmology catalog['Dist'] = self.cosmo.angular_diameter_distance(catalog['z']).to('Mpc').value return catalog
[docs] def get_catalog(self, query_fields=None, transverse_distance_cut = np.inf*u.Mpc): """ Get the catalog of objects Args: z_lim (float): The maximum redshift of the objects to include in the catalog. transverse_distance_cut (Quantity): The maximum impact parameter of the objects to include in the catalog. richness_cut (int): The minimum number of members in any group/cluster returned. query_fields (list): The fields to include in the catalog. If None, all fields are used. Returns: A table of objects within the given limits. """ result = super(TempelClusterCat, self)._get_catalog(query_fields=query_fields) if len(result) > 0: result = self.clean_catalog(result) # Apply a transverse distance cut if transverse_distance_cut<np.inf*u.Mpc: result = super(TempelClusterCat, self)._transverse_distance_cut(result, transverse_distance_cut) self.catalog = result return self.catalog
# Klein+ 2023 RASS-MCMF (Rosat All Sky Survey Multi Component Matched Filter)
[docs] class RASSClusterCat(VizierCatalogSearch): """ A class to query sources within the Klein+ 2023 group/cluster catalog. """
[docs] def __init__(self, coord, radius = 90*u.deg, cosmo=None, **kwargs): # Initialize a SurveyCoord object super(RASSClusterCat, self).__init__(coord, radius, survey="RASSCluster", viziercatalog="J/MNRAS/526/3757/catalog", cosmo=cosmo, **kwargs)
[docs] def clean_catalog(self, catalog): if len(catalog) > 0: catalog.rename_columns(['RAJ2000', 'DEJ2000','zsp1'], ['ra', 'dec','z']) # Rename the columns to match the SurveyCoord class # Add a distance estimate in Mpc using the given cosmology catalog['Dist'] = self.cosmo.angular_diameter_distance(catalog['z']).value return catalog
[docs] def get_catalog(self, query_fields=None, transverse_distance_cut = np.inf*u.Mpc): """ Get the catalog of objects Args: z_lim (float): The maximum redshift of the objects to include in the catalog. transverse_distance_cut (Quantity): The maximum impact parameter of the objects to include in the catalog. richness_cut (int): The minimum number of members in any group/cluster returned. query_fields (list): The fields to include in the catalog. If None, all fields are used. Returns: A table of objects within the given limits. """ result = super(RASSClusterCat, self)._get_catalog(query_fields=query_fields) if len(result) > 0: result = self.clean_catalog(result) # Apply a transverse distance cut if transverse_distance_cut<np.inf*u.Mpc: result = super(RASSClusterCat, self)._transverse_distance_cut(result, transverse_distance_cut) self.catalog = result return self.catalog
# Rykoff+ 2014 Clusters identified in SDSS8 using red mapper algorithm
[docs] class RedMapperClusterCat(VizierCatalogSearch): """ A class to query sources within the Rykoff+ 2014 group/cluster catalog. """
[docs] def __init__(self, coord, radius = 90*u.deg, cosmo=None, **kwargs): # Initialize a SurveyCoord object super(RedMapperClusterCat, self).__init__(coord, radius, survey="RedMapperCluster", viziercatalog="J/ApJ/785/104/table1", cosmo=cosmo, **kwargs)
[docs] def clean_catalog(self, catalog): if len(catalog) > 0: catalog.rename_columns(['RAJ2000', 'DEJ2000','zspec'], ['ra', 'dec','z']) # Rename the columns to match the SurveyCoord class # Add a distance estimate in Mpc using the given cosmology redshift = catalog['z'] redshift[redshift<0] = catalog['zlambda'][redshift<0] catalog['Dist'] = self.cosmo.angular_diameter_distance(redshift).value return catalog
[docs] def get_catalog(self, query_fields=None, transverse_distance_cut = np.inf*u.Mpc): """ Get the catalog of objects Args: z_lim (float): The maximum redshift of the objects to include in the catalog. transverse_distance_cut (Quantity): The maximum impact parameter of the objects to include in the catalog. richness_cut (int): The minimum number of members in any group/cluster returned. query_fields (list): The fields to include in the catalog. If None, all fields are used. Returns: A table of objects within the given limits. """ result = super(RedMapperClusterCat, self)._get_catalog(query_fields=query_fields) if len(result) > 0: result = self.clean_catalog(result) # Apply a transverse distance cut if transverse_distance_cut<np.inf*u.Mpc: result = super(RedMapperClusterCat, self)._transverse_distance_cut(result, transverse_distance_cut) self.catalog = result return self.catalog
# Klein+ 2024 Clusters identified in ACT data release 5
[docs] class ACTDR5ClusterCat(VizierCatalogSearch): """ A class to query sources within the Klein+ 2024 group/cluster catalog. """
[docs] def __init__(self, coord, radius = 90*u.deg, cosmo=None, **kwargs): # Initialize a SurveyCoord object super(ACTDR5ClusterCat, self).__init__(coord, radius, survey="ACTDR5Cluster", viziercatalog="J/A+A/690/A322/catalog", cosmo=cosmo, **kwargs)
[docs] def clean_catalog(self, catalog): if len(catalog) > 0: catalog.rename_columns(['RAJ2000', 'DEJ2000', 'z1C'], ['ra', 'dec','z']) # Rename the columns to match the SurveyCoord class # Add a distance estimate in Mpc using the given cosmology redshift = catalog['z'] catalog['Dist'] = self.cosmo.angular_diameter_distance(redshift).value return catalog
[docs] def get_catalog(self, query_fields=None, transverse_distance_cut = np.inf*u.Mpc): """ Get the catalog of objects Args: z_lim (float): The maximum redshift of the objects to include in the catalog. transverse_distance_cut (Quantity): The maximum impact parameter of the objects to include in the catalog. richness_cut (int): The minimum number of members in any group/cluster returned. query_fields (list): The fields to include in the catalog. If None, all fields are used. Returns: A table of objects within the given limits. """ result = super(ACTDR5ClusterCat, self)._get_catalog(query_fields=query_fields) if len(result) > 0: result = self.clean_catalog(result) # Apply a transverse distance cut if transverse_distance_cut<np.inf*u.Mpc: result = super(ACTDR5ClusterCat, self)._transverse_distance_cut(result, transverse_distance_cut) self.catalog = result return self.catalog
# Kluge+ 2024 Clusters identified in eRosita x-ray all sky survey
[docs] class ERASSClusterCat(VizierCatalogSearch): """ A class to query sources within the Kluge+ 2024 group/cluster catalog. """
[docs] def __init__(self, coord, radius = 90*u.deg, cosmo=None, **kwargs): # Initialize a SurveyCoord object super(ERASSClusterCat, self).__init__(coord, radius, survey="ERASSCluster", viziercatalog="J/A+A/688/A210/tablee1", cosmo=cosmo, **kwargs)
[docs] def clean_catalog(self, catalog): if len(catalog) > 0: catalog.rename_columns(['RAJ2000', 'DEJ2000', 'Bestz'], ['ra', 'dec','z']) # Rename the columns to match the SurveyCoord class # Add a distance estimate in Mpc using the given cosmology redshift = catalog['z'] catalog['Dist'] = self.cosmo.angular_diameter_distance(redshift).value return catalog
[docs] def get_catalog(self, query_fields=None, transverse_distance_cut = np.inf*u.Mpc): """ Get the catalog of objects Args: z_lim (float): The maximum redshift of the objects to include in the catalog. transverse_distance_cut (Quantity): The maximum impact parameter of the objects to include in the catalog. richness_cut (int): The minimum number of members in any group/cluster returned. query_fields (list): The fields to include in the catalog. If None, all fields are used. Returns: A table of objects within the given limits. """ result = super(ERASSClusterCat, self)._get_catalog(query_fields=query_fields) if len(result) > 0: result = self.clean_catalog(result) # Apply a transverse distance cut if transverse_distance_cut<np.inf*u.Mpc: result = super(ERASSClusterCat, self)._transverse_distance_cut(result, transverse_distance_cut) self.catalog = result return self.catalog