def calc_counts(skydir, ltc, event_class, event_types, egy_bins, cth_bins, fn, npts=1): """Calculate the expected counts vs. true energy and incidence angle for a source with spectral parameterization ``fn``. Parameters ---------- skydir : `~astropy.coordinate.SkyCoord` ltc : `~fermipy.irfs.LTCube` egy_bins : `~numpy.ndarray` Bin edges in observed energy in MeV. cth_bins : `~numpy.ndarray` Bin edges in cosine of the true incidence angle. npts : int Number of points by which to oversample each energy bin. """ #npts = int(np.ceil(32. / bins_per_dec(egy_bins))) egy_bins = np.exp(utils.split_bin_edges(np.log(egy_bins), npts)) exp = calc_exp(skydir, ltc, event_class, event_types, egy_bins, cth_bins) dnde = fn.dnde(egy_bins) cnts = loglog_quad(egy_bins, exp * dnde[:, None], 0) cnts = sum_bins(cnts, 0, npts) return cnts
def calc_exp(skydir, ltc, event_class, event_types, egy, cth_bins, npts=None): """Calculate the exposure on a 2D grid of energy and incidence angle. Parameters ---------- npts : int Number of points by which to sample the response in each incidence angle bin. If None then npts will be automatically set such that incidence angle is sampled on intervals of < 0.05 in Cos(Theta). Returns ------- exp : `~numpy.ndarray` 2D Array of exposures vs. energy and incidence angle. """ if npts is None: npts = int(np.ceil(np.max(cth_bins[1:] - cth_bins[:-1]) / 0.025)) exp = np.zeros((len(egy), len(cth_bins) - 1)) cth_bins = utils.split_bin_edges(cth_bins, npts) cth = edge_to_center(cth_bins) ltw = ltc.get_skydir_lthist(skydir, cth_bins).reshape(-1, npts) for et in event_types: aeff = create_aeff(event_class, et, egy, cth) aeff = aeff.reshape(exp.shape + (npts,)) exp += np.sum(aeff * ltw[np.newaxis, :, :], axis=-1) return exp
def create_avg_rsp(rsp_fn, skydir, ltc, event_class, event_types, x, egy, cth_bins, npts=None): """Calculate the weighted response function. """ if npts is None: npts = int(np.ceil(np.max(cth_bins[1:] - cth_bins[:-1]) / 0.05)) wrsp = np.zeros((len(x), len(egy), len(cth_bins) - 1)) exps = np.zeros((len(egy), len(cth_bins) - 1)) cth_bins = utils.split_bin_edges(cth_bins, npts) cth = edge_to_center(cth_bins) ltw = ltc.get_skydir_lthist(skydir, cth_bins) ltw = ltw.reshape(-1, npts) for et in event_types: rsp = rsp_fn(event_class, et, x, egy, cth) aeff = create_aeff(event_class, et, egy, cth) rsp = rsp.reshape(wrsp.shape + (npts,)) aeff = aeff.reshape(exps.shape + (npts,)) wrsp += np.sum(rsp * aeff[np.newaxis, :, :, :] * ltw[np.newaxis, np.newaxis, :, :], axis=-1) exps += np.sum(aeff * ltw[np.newaxis, :, :], axis=-1) exps_inv = np.zeros_like(exps) exps_inv[exps > 0] = 1./exps[exps>0] wrsp *= exps_inv[np.newaxis, :, :] return wrsp
def get_skydir_lthist(self, skydir, cth_bins): """Get the livetime distribution (observing profile) for a given sky direction with binning in incidence angle defined by ``cth_bins``. Parameters ---------- skydir : `~astropy.coordinates.SkyCoord` Sky coordinate for which the observing profile will be computed. cth_bins : `~numpy.ndarray` Bin edges in cosine of the incidence angle. """ ra = skydir.ra.deg dec = skydir.dec.deg npts = 1 bins = utils.split_bin_edges(cth_bins, npts) center = edge_to_center(bins) width = edge_to_width(bins) ipix = hp.ang2pix(self.hpx.nside, np.pi / 2. - np.radians(dec), np.radians(ra), nest=self.hpx.nest) lt = np.histogram(self._cth_center, weights=self.data[:, ipix], bins=bins)[0] lt = np.sum(lt.reshape(-1, npts), axis=1) return lt
def interp_bin(self, egy_bins, dtheta, scale_fn=None): """Evaluate the bin-averaged PSF model over the energy bins ``egy_bins``. Parameters ---------- egy_bins : array_like Energy bin edges in MeV. dtheta : array_like Array of angular separations in degrees. scale_fn : callable Function that evaluates the PSF scaling function. Argument is energy in MeV. """ npts = 4 egy_bins = np.exp(utils.split_bin_edges(np.log(egy_bins), npts)) egy = np.exp(utils.edge_to_center(np.log(egy_bins))) log_energies = np.log10(egy) vals = self.interp(egy[None, :], dtheta[:, None], scale_fn=scale_fn) wts = np.exp(self._wts_fn((log_energies,))) wts = wts.reshape((1,) + wts.shape) vals = np.sum( (vals * wts).reshape((vals.shape[0], int(vals.shape[1] / npts), npts)), axis=2) vals /= np.sum(wts.reshape(wts.shape[0], int(wts.shape[1] / npts), npts), axis=2) return vals
def calc_exp(skydir, ltc, event_class, event_types, egy, cth_bins, npts=None): """Calculate the exposure on a 2D grid of energy and incidence angle. Parameters ---------- npts : int Number of points by which to sample the response in each incidence angle bin. If None then npts will be automatically set such that incidence angle is sampled on intervals of < 0.05 in Cos(Theta). Returns ------- exp : `~numpy.ndarray` 2D Array of exposures vs. energy and incidence angle. """ if npts is None: npts = int(np.ceil(np.max(cth_bins[1:] - cth_bins[:-1]) / 0.025)) exp = np.zeros((len(egy), len(cth_bins) - 1)) cth_bins = utils.split_bin_edges(cth_bins, npts) cth = edge_to_center(cth_bins) ltw = ltc.get_skydir_lthist(skydir, cth_bins).reshape(-1, npts) for et in event_types: aeff = create_aeff(event_class, et, egy, cth) aeff = aeff.reshape(exp.shape + (npts, )) exp += np.sum(aeff * ltw[np.newaxis, :, :], axis=-1) return exp
def calc_counts(skydir, ltc, event_class, event_types, egy_bins, cth_bins, fn, npts=1): """Calculate the expected counts vs. true energy and incidence angle for a source with spectral parameterization ``fn``. Parameters ---------- skydir : `~astropy.coordinate.SkyCoord` ltc : `~fermipy.irfs.LTCube` egy_bins : `~numpy.ndarray` Bin edges in observed energy in MeV. cth_bins : `~numpy.ndarray` Bin edges in cosine of the true incidence angle. npts : int Number of points by which to oversample each energy bin. """ #npts = int(np.ceil(32. / bins_per_dec(egy_bins))) egy_bins = np.exp(utils.split_bin_edges(np.log(egy_bins), npts)) exp = calc_exp(skydir, ltc, event_class, event_types, egy_bins, cth_bins) dnde = fn.dnde(egy_bins) cnts = loglog_quad(egy_bins, exp * dnde[:, None], 0) cnts = sum_bins(cnts, 0, npts) return cnts
def calc_counts_edisp(skydir, ltc, event_class, event_types, egy_bins, cth_bins, fn, nbin=64, npts=1): """Calculate the expected counts vs. observed energy and true incidence angle for a source with spectral parameterization ``fn``. Parameters ---------- skydir : `~astropy.coordinate.SkyCoord` ltc : `~fermipy.irfs.LTCube` egy_bins : `~numpy.ndarray` Bin edges in observed energy in MeV. cth_bins : `~numpy.ndarray` Bin edges in cosine of the true incidence angle. npts : int Number of points by which to oversample each energy bin. """ #npts = int(np.ceil(32. / bins_per_dec(egy_bins))) # Split energy bins egy_bins = np.exp(utils.split_bin_edges(np.log(egy_bins), npts)) etrue_bins = 10**np.linspace(1.0, 6.5, nbin * 5.5 + 1) drm = calc_drm(skydir, ltc, event_class, event_types, egy_bins, cth_bins, nbin=nbin) cnts_etrue = calc_counts(skydir, ltc, event_class, event_types, etrue_bins, cth_bins, fn) cnts = np.sum(cnts_etrue[None, :, :] * drm[:, :, :], axis=1) cnts = sum_bins(cnts, 0, npts) return cnts
def interp_bin(self, egy_bins, dtheta, scale_fn=None): """Evaluate the bin-averaged PSF model over the energy bins ``egy_bins``. Parameters ---------- egy_bins : array_like Energy bin edges in MeV. dtheta : array_like Array of angular separations in degrees. scale_fn : callable Function that evaluates the PSF scaling function. Argument is energy in MeV. """ npts = 4 egy_bins = np.exp(utils.split_bin_edges(np.log(egy_bins), npts)) egy = np.exp(utils.edge_to_center(np.log(egy_bins))) log_energies = np.log10(egy) vals = self.interp(egy[None, :], dtheta[:, None], scale_fn=scale_fn) wts = np.exp(self._wts_fn((log_energies, ))) wts = wts.reshape((1, ) + wts.shape) vals = np.sum((vals * wts).reshape( (vals.shape[0], int(vals.shape[1] / npts), npts)), axis=2) vals /= np.sum(wts.reshape(wts.shape[0], int(wts.shape[1] / npts), npts), axis=2) return vals
def create_avg_rsp(rsp_fn, skydir, ltc, event_class, event_types, x, egy, cth_bins, npts=None): if npts is None: npts = int(np.ceil(np.max(cth_bins[1:] - cth_bins[:-1]) / 0.05)) wrsp = np.zeros((len(x), len(egy), len(cth_bins) - 1)) exps = np.zeros((len(egy), len(cth_bins) - 1)) cth_bins = utils.split_bin_edges(cth_bins, npts) cth = edge_to_center(cth_bins) ltw = ltc.get_skydir_lthist(skydir, cth_bins) ltw = ltw.reshape(-1, npts) for et in event_types: rsp = rsp_fn(event_class, et, x, egy, cth) aeff = create_aeff(event_class, et, egy, cth) rsp = rsp.reshape(wrsp.shape + (npts, )) aeff = aeff.reshape(exps.shape + (npts, )) wrsp += np.sum(rsp * aeff[np.newaxis, :, :, :] * ltw[np.newaxis, np.newaxis, :, :], axis=-1) exps += np.sum(aeff * ltw[np.newaxis, :, :], axis=-1) wrsp /= exps[np.newaxis, :, :] return wrsp
def calc_counts_edisp(skydir, ltc, event_class, event_types, egy_bins, cth_bins, fn, nbin=64, npts=1): """Calculate the expected counts vs. observed energy and true incidence angle for a source with spectral parameterization ``fn``. Parameters ---------- skydir : `~astropy.coordinate.SkyCoord` ltc : `~fermipy.irfs.LTCube` egy_bins : `~numpy.ndarray` Bin edges in observed energy in MeV. cth_bins : `~numpy.ndarray` Bin edges in cosine of the true incidence angle. npts : int Number of points by which to oversample each energy bin. """ #npts = int(np.ceil(32. / bins_per_dec(egy_bins))) # Split energy bins egy_bins = np.exp(utils.split_bin_edges(np.log(egy_bins), npts)) etrue_bins = 10**np.linspace(1.0, 6.5, nbin * 5.5 + 1) drm = calc_drm(skydir, ltc, event_class, event_types, egy_bins, cth_bins, nbin=nbin) cnts_etrue = calc_counts(skydir, ltc, event_class, event_types, etrue_bins, cth_bins, fn) cnts = np.sum(cnts_etrue[None,:,:] * drm[:, :, :], axis=1) cnts = sum_bins(cnts,0,npts) return cnts
def get_skydir_lthist(self, skydir, cth_bins): """Get the livetime distribution (observing profile) for a given sky direction with binning in incidence angle defined by ``cth_bins``. Parameters ---------- skydir : `~astropy.coordinates.SkyCoord` Sky coordinate for which the observing profile will be computed. cth_bins : `~numpy.ndarray` Bin edges in cosine of the incidence angle. """ ra = skydir.ra.deg dec = skydir.dec.deg npts = 1 bins = utils.split_bin_edges(cth_bins, npts) center = edge_to_center(bins) width = edge_to_width(bins) ipix = hp.ang2pix(self.hpx.nside, np.pi / 2. - np.radians(dec), np.radians(ra), nest=self.hpx.nest) lt = np.histogram(self._cth_center, weights=self.data[:, ipix], bins=bins)[0] lt = np.sum(lt.reshape(-1, npts), axis=1) return lt
def create_wtd_psf(skydir, ltc, event_class, event_types, dtheta, egy_bins, cth_bins, fn, nbin=64, npts=1): """Create an exposure- and dispersion-weighted PSF model for a source with spectral parameterization ``fn``. The calculation performed by this method accounts for the influence of energy dispersion on the PSF. Parameters ---------- dtheta : `~numpy.ndarray` egy_bins : `~numpy.ndarray` Bin edges in observed energy. cth_bins : `~numpy.ndarray` Bin edges in cosine of the true incidence angle. nbin : int Number of bins per decade in true energy. npts : int Number of points by which to oversample each energy bin. """ #npts = int(np.ceil(32. / bins_per_dec(egy_bins))) egy_bins = np.exp(utils.split_bin_edges(np.log(egy_bins), npts)) etrue_bins = 10**np.linspace(1.0, 6.5, nbin * 5.5 + 1) etrue = 10**utils.edge_to_center(np.log10(etrue_bins)) psf = create_avg_psf(skydir, ltc, event_class, event_types, dtheta, etrue, cth_bins) drm = calc_drm(skydir, ltc, event_class, event_types, egy_bins, cth_bins, nbin=nbin) cnts = calc_counts(skydir, ltc, event_class, event_types, etrue_bins, cth_bins, fn) wts = drm * cnts[None, :, :] wts_norm = np.sum(wts, axis=1) wts_norm[wts_norm == 0] = 1.0 wts = wts / wts_norm[:, None, :] wpsf = np.sum(wts[None, :, :, :] * psf[:, None, :, :], axis=2) wts = np.sum(wts[None, :, :, :], axis=2) if npts > 1: shape = (wpsf.shape[0], int(wpsf.shape[1] / npts), npts, wpsf.shape[2]) wpsf = np.sum((wpsf * wts).reshape(shape), axis=2) shape = (wts.shape[0], int(wts.shape[1] / npts), npts, wts.shape[2]) wpsf = wpsf / np.sum(wts.reshape(shape), axis=2) return wpsf
def create_wtd_psf(skydir, ltc, event_class, event_types, dtheta, egy_bins, cth_bins, fn, nbin=64, npts=1): """Create an exposure- and dispersion-weighted PSF model for a source with spectral parameterization ``fn``. The calculation performed by this method accounts for the influence of energy dispersion on the PSF. Parameters ---------- dtheta : `~numpy.ndarray` egy_bins : `~numpy.ndarray` Bin edges in observed energy. cth_bins : `~numpy.ndarray` Bin edges in cosine of the true incidence angle. nbin : int Number of bins per decade in true energy. npts : int Number of points by which to oversample each energy bin. """ #npts = int(np.ceil(32. / bins_per_dec(egy_bins))) egy_bins = np.exp(utils.split_bin_edges(np.log(egy_bins), npts)) etrue_bins = 10**np.linspace(1.0, 6.5, nbin * 5.5 + 1) etrue = 10**utils.edge_to_center(np.log10(etrue_bins)) psf = create_avg_psf(skydir, ltc, event_class, event_types, dtheta, etrue, cth_bins) drm = calc_drm(skydir, ltc, event_class, event_types, egy_bins, cth_bins, nbin=nbin) cnts = calc_counts(skydir, ltc, event_class, event_types, etrue_bins, cth_bins, fn) wts = drm * cnts[None, :, :] wts_norm = np.sum(wts,axis=1) wts_norm[wts_norm == 0] = 1.0 wts = wts / wts_norm[:, None, :] wpsf = np.sum(wts[None, :, :, :] * psf[:, None, :, :], axis=2) wts = np.sum(wts[None, :, :, :],axis=2) if npts > 1: shape = (wpsf.shape[0], int(wpsf.shape[1] / npts), npts, wpsf.shape[2]) wpsf = np.sum((wpsf * wts).reshape(shape), axis=2) shape = (wts.shape[0], int(wts.shape[1] / npts), npts, wts.shape[2]) wpsf = wpsf / np.sum(wts.reshape(shape), axis=2) return wpsf
def calc_drm(skydir, ltc, event_class, event_types, egy_bins, cth_bins, nbin=64): """Calculate the detector response matrix.""" npts = int(np.ceil(128. / bins_per_dec(egy_bins))) egy_bins = np.exp(utils.split_bin_edges(np.log(egy_bins), npts)) etrue_bins = 10**np.linspace(1.0, 6.5, nbin * 5.5 + 1) egy = 10**utils.edge_to_center(np.log10(egy_bins)) egy_width = utils.edge_to_width(egy_bins) etrue = 10**utils.edge_to_center(np.log10(etrue_bins)) edisp = create_avg_edisp(skydir, ltc, event_class, event_types, egy, etrue, cth_bins) edisp = edisp * egy_width[:, None, None] edisp = sum_bins(edisp, 0, npts) return edisp
def calc_drm(skydir, ltc, event_class, event_types, egy_bins, cth_bins, nbin=64): """Calculate the detector response matrix.""" npts = int(np.ceil(128. / bins_per_dec(egy_bins))) egy_bins = np.exp(utils.split_bin_edges(np.log(egy_bins), npts)) etrue_bins = 10**np.linspace(1.0, 6.5, nbin * 5.5 + 1) egy = 10**utils.edge_to_center(np.log10(egy_bins)) egy_width = utils.edge_to_width(egy_bins) etrue = 10**utils.edge_to_center(np.log10(etrue_bins)) edisp = create_avg_edisp(skydir, ltc, event_class, event_types, egy, etrue, cth_bins) edisp = edisp * egy_width[:, None, None] edisp = sum_bins(edisp,0,npts) return edisp