""" Module for galaxies related to FRBs
"""
from __future__ import print_function, absolute_import, division, unicode_literals
import numpy as np
import os
import warnings
import importlib_resources
from astropy.coordinates import SkyCoord
from astropy import units
from astropy.table import Table
from frb import defs as frb_defs
from frb.galaxies import defs
from frb.galaxies import nebular
from frb.galaxies import utils as gutils
from frb.galaxies import offsets
from frb.surveys.catalog_utils import convert_mags_to_flux
from frb import utils
from frb.dm import host as dm_host
from scipy.integrate import simpson
[docs]
class FRBGalaxy(object):
"""
Parent class for galaxies in FRB fields
Simple object to hold key observable and derived quantities
Warning: Generating hundreds of these objects will likely be slow.
Especially SkyCoord generation. A new class will be warranted for that
Args:
ra (float): RA in deg
dec (float): DEC in deg
frb (frb.FRB): FRB object
cosmo (astropy.cosmology): Cosmology, e.g. Planck18
Attributes:
redshift (dict):
photom (dict):
morphology (dict):
neb_lines (dict):
kinematics (dict):
derived (dict):
"""
[docs]
@classmethod
def from_dict(cls, frb, idict, override:bool=False, **kwargs):
"""
Instantiate from a dict
Args:
frb (frb.FRB):
idict (dict):
override (bool, optional):
Over-ride the cosmology error
Not recommended unless you know what you are doing
**kwargs: Passed to the __init__ call
Returns:
"""
# Init
slf = cls(idict['ra'], idict['dec'], frb, **kwargs)
# FRB coord
if 'ra_FRB' in idict.keys():
slf.frb_coord = SkyCoord(ra=idict['ra_FRB'], dec=idict['dec_FRB'], unit='deg')
# Check cosmology
if slf.cosmo.name != idict['cosmo'] and not override:
raise AssertionError("Your cosmology does not match the expected. Gotta deal..")
# Fill me up
for attr in slf.main_attr:
if attr in idict.keys():
setattr(slf,attr,idict[attr])
# Redshift
if 'z' in idict.keys():
slf.set_z(idict['z'], idict['z_origin'])
if 'z_err' in idict.keys():
slf.set_z(idict['z'], idict['z_origin'], err=idict['z_err'])
# Physical Offset -- remove this when these get into JSON files
if 'z_spec' in slf.redshift.keys() and 'physical' not in slf.offsets.keys():
slf.offsets['physical'] = (slf.offsets['ang_best']*units.arcsec / slf.cosmo.arcsec_per_kpc_proper(slf.z)).to('kpc').value
slf.offsets['physical_err'] = (slf.offsets['ang_best_err']*units.arcsec / slf.cosmo.arcsec_per_kpc_proper(slf.z)).to('kpc').value
# Return
return slf
[docs]
@classmethod
def from_json(cls, frb, json_file, verbose:bool=True, **kwargs):
"""
Args:
frb (frb.FRB):
json_file:
**kwargs:
Returns:
FRBGalaxy or None
"""
try:
idict = utils.loadjson(json_file)
except FileNotFoundError:
if verbose:
warnings.warn("File {} not found. This galaxy probably does not exist yet.".format(json_file))
return None
slf = cls.from_dict(frb, idict, **kwargs)
return slf
[docs]
def __init__(self, ra, dec, frb, cosmo=None):
"""
Args:
ra (float):
dec (float):
frb (frb.FRB) :
cosmo (astropy.cosmology, optional):
"""
# Init
self.coord = SkyCoord(ra=ra, dec=dec, unit='deg')
self.frb = frb # FRB object
#
self.name = ''
# Cosmology
if cosmo is None:
self.cosmo = frb_defs.frb_cosmo
else:
self.cosmo = cosmo
# Main attributes
self.redshift = {}
self.photom = {}
self.morphology = {}
self.neb_lines = {}
self.kinematics = {}
self.derived = {}
self.offsets = {}
self.positional_error = {}
self.main_attr = ('photom', 'redshift', 'morphology', 'neb_lines',
'kinematics', 'derived', 'offsets', 'positional_error')
# Angular offset
self.offsets['ang_avg'], self.offsets['ang_avg_err'], \
self.offsets['ang_best'], self.offsets['ang_best_err'] \
= offsets.angular_offset(frb, self)
@property
def z(self):
"""
Return the redshift of the galaxy
Returns:
float or None: redshift or nadda
"""
if len(self.redshift) == 0:
return None
else:
return self.redshift['z']
@property
def z_err(self):
"""
Return the redshift error of the galaxy
Returns:
float or None: redshift or nadda
"""
if len(self.redshift) == 0:
return None
else:
return self.redshift['z_err']
[docs]
def calc_nebular_lum(self, line):
"""
Calculate the line luminosity
Applies dust extinction if self.derived['AV_nebular'] is filled
Mainly a wrapper to nebular.calc_lum()
Args:
line (str): Name of the line
"""
# Checks
assert len(self.neb_lines) > 0
assert len(self.redshift) > 0
# Dust?
if 'AV_nebular' in self.derived.keys():
AV = self.derived['AV_nebular']
print("Using AV={} for a dust correction of the SFR".format(AV))
else:
print("Not making a dust correction of the SFR. Set AV_nebular to do so or input AV to this method")
AV = None
Lum, Lum_err = nebular.calc_lum(self.neb_lines, line, self.z, self.cosmo, AV=AV)
return Lum, Lum_err
[docs]
def calc_nebular_AV(self, method='Ha/Hb', min_AV=None, **kwargs):
"""
Calculate an A_V extinction from a pair of Nebular lines
Mainly a wrapper to nebular.calc_dust_extinct
self.derived['AV_nebular'] is filled
Args:
method (str): Method to use
min_AV (float): Minimum A_V value allowed; might set 0. someday
**kwargs: Passed to nebular.calc_dust_extinct
Returns:
"""
# Checks
assert len(self.neb_lines) > 0
# Do it
AV = nebular.calc_dust_extinct(self.neb_lines, method, **kwargs)
if min_AV is not None:
AV = max(AV, min_AV)
# Set
self.derived['AV_nebular'] = AV
[docs]
def calc_nebular_SFR(self, method='Ha', **kwargs):
"""
Calculate a SFR from a nebular line
Mainly a wrapper to nebular.calc_SFR
self.derived['AV_nebular'] is filled with units SFR/yr
Args:
method (str): Method to use, e.g. 'Ha' for Halpha
**kwargs: passed to nebular.calc_SFR
Returns:
"""
# Checks
assert len(self.neb_lines) > 0
assert len(self.redshift) > 0
# Dust?
if 'AV_nebular' in self.derived.keys():
AV = self.derived['AV_nebular']
print("Using AV={} for a dust correction of the SFR".format(AV))
else:
print("Not making a dust correction of the SFR. Set AV_nebular to do so or input AV to this method")
AV = None
# Calculate
SFR = nebular.calc_SFR(self.neb_lines, method, self.redshift['z'], self.cosmo, AV=AV)
self.derived['SFR_nebular'] = SFR.to('Msun/yr').value
[docs]
def calc_tot_uncert(self):
"""Calculate total uncertainty in arcsec of
FRB localization + Host localization in the
reference frame of the FRB
Returns:
tuple: uncerta, uncertb [arcsec]
"""
# set to zero, but change if we have astrometric and source errors
if hasattr(self, 'positional_error') and len(self.positional_error) > 0:
host_ra_sig = np.sqrt(self.positional_error['ra_astrometric']**2 +
self.positional_error['ra_source']**2)
host_dec_sig = np.sqrt(self.positional_error['dec_astrometric']**2 +
self.positional_error['dec_source']**2)
else:
host_ra_sig, host_dec_sig = 0., 0.
# Rotate to the FRB frame
# sigma**2
# will be zero if no positional errors saved in host json file
theta = self.frb.eellipse['theta']
sig2_gal_a = host_dec_sig ** 2 * np.cos(theta) ** 2 + host_ra_sig ** 2 * np.sin(theta) ** 2
sig2_gal_b = host_ra_sig ** 2 * np.cos(theta) ** 2 + host_dec_sig ** 2 * np.sin(theta) ** 2
# will only be FRB error if positional errors saved in host json file
# Units are pixels
uncerta = np.sqrt(self.frb.sig_a**2 + sig2_gal_a)
uncertb = np.sqrt(self.frb.sig_b**2 + sig2_gal_b)
# Return
return uncerta, uncertb
[docs]
def parse_photom(self, phot_tbl, max_off=1*units.arcsec,
overwrite=True, EBV=None):
"""
Parse photometry from an input table
Fills the self.photom dict
Also fills fluxes in mJy
Args:
phot_tbl (astropy.table.Table):
ra, dec entires are required
max_off (Angle, optional):
overwrite (bool, optional):
EBV (float, optional): Galactic reddening. If included, the photometry
has been corrected for this. If not, who knows?! :)
Returns:
"""
phot_coord = SkyCoord(ra=phot_tbl['ra'], dec=phot_tbl['dec'], unit='deg')
sep = self.coord.separation(phot_coord)
row = np.argmin(sep)
# Satisfy minimum offset?
if sep[row] > max_off:
print("No photometric sources within {} of the galaxy".format(max_off))
return
# Get a flux table
flux_tbl = convert_mags_to_flux(phot_tbl, fluxunits='mJy')
# Fill fluxes (mJy)
for filter in defs.valid_filters:
# Value
if filter in phot_tbl.keys():
if (filter in self.photom.keys()) and (not overwrite):
pass
else:
# -999. is used as empty fill value
if phot_tbl[filter][row] < -990:
pass
else:
self.photom[filter] = phot_tbl[filter][row]
self.photom[filter+'_flux'] = flux_tbl[filter][row]
# Try error
if filter+'_err' in phot_tbl.keys():
self.photom[filter+'_err'] = phot_tbl[filter+'_err'][row]
self.photom[filter+'_flux_err'] = flux_tbl[filter+'_err'][row]
# Try reference
if filter+'_ref' in phot_tbl.keys():
self.photom[filter+'_ref'] = phot_tbl[filter+'_ref'][row]
# Add entries for corresponding flux values.
# EBV
if EBV is not None:
self.photom['EBV'] = EBV
[docs]
def run_cigale(self, data_file="cigale_in.fits", config_file="pcigale.ini",
wait_for_input=False, save_sed=True, plot=True, outdir='out',**kwargs):
"""
Generates the input data file for CIGALE
given the photometric points and redshift
of a galaxy
Args:
ID: str, optional
An ID for the galaxy. If none, "GalaxyA" is assigned.
data_file (str, optional):
Root name for the photometry data file generated used as input to CIGALE
config_file (str, optional):
Root name for the file where CIGALE's configuration is generated
wait_for_input (bool, optional):
If true, waits for the user to finish editing the auto-generated config file
before running.
save_sed (bool, optional):
Saves the best fit SED if true
plot (bool, optional):
Plots the best fit SED if true
cores (int, optional):
Number of CPU cores to be used. Defaults
to all cores on the system.
outdir (str, optional):
Path to the many outputs of CIGALE
If not supplied, the outputs will appear in a folder named out/
kwargs: These are passed into gen_cigale_in() and _initialise()
sed_modules (list of 'str', optional):
A list of SED modules to be used in the
PDF analysis. If this is being input, there
should be a corresponding correct dict
for sed_modules_params.
sed_module_params (dict, optional):
A dict containing parameter values for
the input SED modules. Better not use this
unless you know exactly what you're doing.
"""
# Adding import statement here in case CIGALE is
# not installed.
from .cigale import run
assert (len(self.photom) > 0 ),"No photometry found. CIGALE cannot be run."
assert (len(self.redshift) > 0),"No redshift found. CIGALE cannot be run"
new_photom = Table([self.photom])
new_photom['redshift'] = self.z
if self.name != '':
new_photom['ID'] = self.name
else:
new_photom['ID'] = 'FRBGalaxy'
run(photometry_table = new_photom,
zcol = 'redshift',
data_file = data_file,
config_file = config_file,
wait_for_input = wait_for_input,
save_sed = save_sed,
plot = plot,
outdir = outdir,
**kwargs)
return
[docs]
def parse_cigale(self, cigale_file, sfh_file=None, overwrite=True):
"""
Parse the output file from CIGALE
Read into self.derived
Args:
cigale_file (str): Name of the CIGALE results file
sfh_file (str, optional): Name of the best SFH model file.
overwrite (bool, optional): Over-write any previous values
Returns:
"""
# Read
cigale_tbl = Table.read(cigale_file)
# Derived quantities
cigale_translate = [ # Internal key, CIGALE key
('Mstar', 'bayes.stellar.m_star'),
('f_AGN', 'bayes.agn.fracAGN_dale2014'),
('u-r', 'bayes.param.restframe_u_prime-r_prime'),
('Lnu_r', 'bayes.param.restframe_Lnu(r_prime)'),
('SFR_photom', 'bayes.sfh.sfr'),
('EBV_photom', 'bayes.attenuation.E_BVs.stellar.old'),
('Z_photom', 'bayes.stellar.metallicity')
]
# Do it
cigale = {}
for item in cigale_translate:
#try:
if not(item[0] in defs.valid_derived_photom):
raise AssertionError("{} not valid!!".format(item[0]))
if item[1] in cigale_tbl.keys():
cigale[item[0]] = cigale_tbl[item[1]][0] # Solar masses, linear
cigale[item[0]+'_err'] = cigale_tbl[item[1]+'_err'][0] # Solar masses, linear
#except AssertionError:
# print('Error with', item[0])
# Absolute magnitude (astronomers...)
if 'Lnu_r' in cigale.keys():
cigale['M_r'] = -2.5*np.log10(cigale['Lnu_r']) + 34.1
cigale['M_r_err'] = 2.5*(cigale['Lnu_r_err']/cigale['Lnu_r']) / np.log(10)
# Compute mass weighted age?
if sfh_file is not None:
try:
sfh_tab = Table.read(sfh_file)
except:
warnings.warn("Invalid SFH file. Skipping mass-weighted age.")
return
mass = simpson(sfh_tab['SFR'], x=sfh_tab['time']) # M_sun/yr *Myr
# Computed mass weighted age
t_mass = simpson(sfh_tab['SFR']*sfh_tab['time'], x=sfh_tab['time'])/mass # Myr
# Store
if ('age_mass' not in self.derived.keys()) or (overwrite):
cigale['age_mass'] = t_mass
# Fill Derived
for key, item in cigale.items():
if (key not in self.derived.keys()) or (overwrite):
self.derived[key] = item
[docs]
def parse_galfit(self, galfit_file, overwrite=True,
twocomponent=False):
"""
Parse an output GALFIT file
or a gallight JSON file
Loaded into self.morphology
Args:
galfit_file (str): processed 'out.fits' file
produced by frb.galaxies.galfit.run. Contains
a binary table with fit parameters.
Or a JSON file from gallight
overwrite (bool, optional): Need to overwrite
the object's attributes?
twocomponent (bool, optional): Should the morphology
attribute generated contain fit parameters of
two components?
"""
assert os.path.isfile(galfit_file), "Incorrect file path {:s}".format(galfit_file)
is_json = False
if galfit_file.endswith('.json'):
is_json = True
fit_tab = utils.loadjson(galfit_file)
elif galfit_file.endswith('.fits'):
try:
fit_tab = Table.read(galfit_file, hdu=4)
except:
raise IndexError("The binary table with fit parameters was not found as the 4th hdu in {:s}. Was GALFIT run using the wrapper?".format(galfit_file))
for key in fit_tab.keys():
if 'mag' in key:
continue
if 'center_' in key:
continue
if 'flux_' in key:
continue
if (key not in self.morphology.keys()) or (overwrite):
if is_json:
self.morphology[key] = fit_tab[key]
else:
if twocomponent:
self.morphology[key] = fit_tab[key].data
else:
self.morphology[key] = fit_tab[key][0]
# Hack for galight
if 'reff_err_ang' in self.morphology.keys():
self.morphology['reff_ang_err'] = self.morphology.pop('reff_err_ang')
# reff kpc?
if 'reff_kpc' in self.morphology.keys():
pass
elif (self.z is not None) and ('reff_ang' in self.morphology.keys()):
self.morphology['reff_kpc'] = \
(self.morphology['reff_ang']*units.arcsec * self.cosmo.kpc_proper_per_arcmin(self.z)).to('kpc').value
self.morphology['reff_kpc_err'] = \
(self.morphology['reff_ang_err']*units.arcsec * self.cosmo.kpc_proper_per_arcmin(self.z)).to('kpc').value
[docs]
def parse_ppxf(self, ppxf_file, overwrite=True, format='ascii.ecsv'):
"""
Parse an output pPXF file generated by our custom run
Loaded into self.lines
Args:
ppxf_file (str): pPXF results file
overwrite (bool, optional):
format (str, optional): Format of the table
Returns:
"""
ppxf_tbl = Table.read(ppxf_file, format=format)
names = ppxf_tbl['name'].data
ppxf_translate = [ # Internal key, CIGALE key
('Halpha', 'Halpha'),
('Hbeta', 'Hbeta'),
('Hgamma', 'Hgamma'),
('[NII] 6584', '[NII]6583_d'), # [NII] 6583 flux erg/s/cm^2; pPXF
('[OII] 3726', '[OII]3726'), # [OII] flux erg/s/cm^2; pPXF
('[OII] 3729', '[OII]3729'), # [OII] flux erg/s/cm^2; pPXF
('[OIII] 5007', '[OIII]5007_d') # [OII] 5007 flux erg/s/cm^2; pPXF
]
# Fluxes first
ppxf = {}
for item in ppxf_translate:
if not(item[0] in defs.valid_neb_lines):
raise AssertionError("{} not valid!!".format(item[0]))
tidx = np.where(names == item[1])[0]
if len(tidx) > 0:
tidx = tidx[0]
ppxf[item[0]] = ppxf_tbl['flux'][tidx]
ppxf[item[0]+'_err'] = ppxf_tbl['err'][tidx]
# Fill Nebular
for line in defs.valid_neb_lines:
# Value
if line in ppxf.keys():
if (line in self.neb_lines.keys()) and (not overwrite):
pass
else:
self.neb_lines[line] = ppxf[line]
# Try error
if line+'_err' in ppxf.keys():
self.neb_lines[line+'_err'] = ppxf[line+'_err']
# Fitted quantities
self.derived['EBV_spec'] = ppxf_tbl.meta['EBV']
self.derived['Z_spec'] = ppxf_tbl.meta['METALS']
self.derived['Mstar_spec'] = 10.**ppxf_tbl.meta['LOGMSTAR']
[docs]
def set_z(self, z, origin, err=None):
"""
Set the redshift value(s) in self.redshift
Args:
z (float): Redshift value
origin (str): Origin
'spec' for spectroscopic
'phot' for photometric
err (float, optional): Error in the redshift
Returns:
"""
# Set internal
if origin == 'spec':
key = 'z_spec'
elif origin == 'phot':
key = 'z_phot'
else:
raise IOError("Bad option for origin")
#
self.redshift[key] = z
if err is not None:
self.redshift[key+'_err'] = err
# Preferred?
if (origin == 'spec') or ((origin == 'phot') and ('z' not in self.redshift.keys())):
self.redshift['z'] = z
if err is not None:
self.redshift['z_err'] = err
# Physical offset
if origin == 'spec':
self.offsets['physical'] = (self.offsets['ang_best'] * units.arcsec *
self.cosmo.kpc_proper_per_arcmin(self.z)).to('kpc').value
self.offsets['physical_err'] = (self.offsets['ang_best_err'] * units.arcsec *
self.cosmo.kpc_proper_per_arcmin(self.z)).to('kpc').value
[docs]
def vet_one(self, attr):
"""
Vette one of the main_attr
Parameters:
attr (str):
Returns:
bool: True = passed
"""
vet = True
# Check
assert attr in self.main_attr
# Setup
if attr == 'neb_lines':
defs_list = defs.valid_neb_lines + defs.valid_neb_ref
elif attr == 'morphology':
defs_list = defs.valid_morphology
elif attr == 'photom':
defs_list = defs.valid_photom+defs.valid_flux+defs.valid_ref
elif attr == 'derived':
defs_list = defs.valid_derived + defs.valid_derived_ref
elif attr == 'redshift':
defs_list = defs.valid_z
elif attr == 'offsets':
defs_list = defs.valid_offsets
elif attr == 'positional_error':
defs_list = defs.valid_positional_error
else:
return True
# Vet
for key in getattr(self, attr).keys():
# Skip error
if '_err' in key:
continue
if '_loerr' in key:
continue
if '_uperr' in key:
continue
if key not in defs_list:
vet = False
warnings.warn("{} in {} is not valid!".format(key,attr))
# Return
return vet
[docs]
def vet_all(self):
"""
Vette all of the main dicts
Args:
dict:
valid_defs:
Returns:
bool: True = passed
"""
vet = True
# Loop me
for attr in self.main_attr:
vet &= self.vet_one(attr)
# Return
return vet
[docs]
def make_outfile(self):
"""
Auto-generate an output name for the class
Returns:
str: Output filename
"""
jname = utils.name_from_coord(self.coord)
outfile = jname+'_FRB{}.json'.format(self.frb)
return outfile
[docs]
def write_to_json(self, outfile=None, path='./', overwrite=True):
"""
Write key aspects of the class to disk in a JSON file
Args:
outfile (str, optional): Output filename
If not provided, one will be generated with make_outfile()
path (str, optional): Path for the output file
overwrite (bool, optional): Overwrite?
Returns:
"""
# Generate path as needed
if not os.path.isdir(path):
os.mkdir(path)
if outfile is None:
outfile = self.make_outfile()
# Build the dict
frbgal_dict = {}
# Basics
frbgal_dict['ra'] = self.coord.ra.value
frbgal_dict['dec'] = self.coord.dec.value
frbgal_dict['FRB'] = self.frb.frb_name
if self.frb.coord is not None:
frbgal_dict['ra_FRB'] = self.frb.coord.ra.value
frbgal_dict['dec_FRB'] = self.frb.coord.dec.value
frbgal_dict['cosmo'] = self.cosmo.name
# Main attributes
for attr in self.main_attr:
if len(getattr(self,attr)) > 0:
frbgal_dict[attr] = getattr(self,attr)
# JSONify
jdict = utils.jsonify(frbgal_dict)
# Write
utils.savejson(os.path.join(path,outfile), jdict, easy_to_read=True, overwrite=overwrite)
print("Wrote data to {}".format(os.path.join(path,outfile)))
def __repr__(self):
txt = '<{:s}: {:s} {:s}, FRB={:s}'.format(
self.__class__.__name__, self.coord.icrs.ra.to_string(unit=units.hour,sep=':', pad=True),
self.coord.icrs.dec.to_string(sep=':',pad=True,alwayssign=True), self.frb.frb_name)
if self.z is not None:
txt += ' z={}'.format(self.z)
# Finish
txt = txt + '>'
return (txt)
[docs]
class FRBHost(FRBGalaxy):
"""
Child of FRBGalaxy specific for an FRB host
Args:
ra (float): RA in deg
dec (float): DEC in deg
FRB (frb.FRB):
"""
[docs]
@classmethod
def by_frb(cls, frb, **kwargs):
"""
Args:
frb (frb.FRB): FRB object
**kwargs:
Returns:
FRBHost:
"""
# Strip off FRB
if frb.frb_name[0:3] == 'FRB':
name = frb.frb_name[3:]
else:
name = frb.frb_name
#
path = importlib_resources.files('frb.data.Galaxies')/ name
json_file = os.path.join(path, FRBHost._make_outfile(name))
slf = cls.from_json(frb, json_file, **kwargs)
return slf
[docs]
def __init__(self, ra, dec, frb, z_frb=None, **kwargs):
# Instantiate
super(FRBHost, self).__init__(ra, dec, frb, **kwargs)
# Name
idname = utils.parse_frb_name(frb.frb_name, prefix='')
self.name = 'HG{}'.format(idname)
# Optional
if z_frb is not None:
self.redshift['z_FRB'] = z_frb
@staticmethod
def _make_outfile(frbname):
"""
Static method to generate outfile based on frbname
Args:
frbname (str): FRB name, e.g. 121102 or FRB121102
Returns:
str: outfile
"""
if frbname[0:3] != 'FRB':
prefix = 'FRB'
else:
prefix = ''
#
outfile = '{}{}_host.json'.format(prefix, frbname)
return outfile
[docs]
def calc_dm_halo(self, **kwargs):
""" Calculate the Halo contribution to the host DM
given the host stellar mass in its derived properties
dict and the FRB coordinates.
Args:
**kwargs: Passed to dm_host.dm_host_halo
Returns:
DM_halo (float): Halo contribution to the DM in pc/cm^3
"""
# Setup
R = self.offsets['physical'] * units.kpc
Mstar = self.derived['Mstar']
log10_Mstar = np.log10(Mstar)
# Calculate
self.DM_halo = dm_host.dm_host_halo(
R, log10_Mstar, self.z, **kwargs)
return self.DM_halo
[docs]
def make_outfile(self):
"""
Overloads the parent method for Host specific naming
Naming is FRBXXXXXX_host.json with XXXXXXX supplied by self.frb
Returns:
str: Name of the default outfile
"""
outfile = self._make_outfile(self.frb.frb_name)
return outfile
[docs]
def set_z(self, z, origin, err=None):
"""
Partially overload the main method
The main change is that the input z also sets z_FRB
self.redshift is modified in place
Args:
z (float): Redshift value
origin (str): Origin
'spec' for spectroscopic
'phot' for photometric
err (float, optional): Error in the redshift
Returns:
"""
super(FRBHost,self).set_z(z, origin, err=err)
self.redshift['z_FRB'] = z
if err is not None:
self.redshift['z_FRB_err'] = err
[docs]
class FGGalaxy(FRBGalaxy):
"""
Foreground galaxy class (child of FRBGalaxy)
"""
[docs]
def __init__(self, ra, dec, frb, **kwargs):
# Instantiate
super(FGGalaxy, self).__init__(ra, dec, frb, **kwargs)
# Load up FRB info from name
self.name = 'FG{}_{}'.format(self.frb, utils.name_from_coord(self.coord))