def test_cash(test_data, reference_values): statsvec = stats.cash(n_on=test_data["n_on"], mu_on=test_data["mu_sig"]) assert_allclose(statsvec, reference_values["cash"])
def test_cash_sum_cython(test_data): counts = np.array(test_data["n_on"], dtype=float) npred = np.array(test_data["mu_sig"], dtype=float) stat = stats.cash_sum_cython(counts=counts, npred=npred) ref = stats.cash(counts, npred).sum() assert_allclose(stat, ref)
def run(self, dataset, steps="all"): """ Run TS map estimation. Requires a MapDataset with counts, exposure and background_model properly set to run. Parameters ---------- dataset : `~gammapy.datasets.MapDataset` Input MapDataset. steps : list of str or 'all' Which maps to compute. Available options are: * "ts": estimate delta TS and significance (sqrt_ts) * "flux-err": estimate symmetric error on flux. * "flux-ul": estimate upper limits on flux. By default all steps are executed. Returns ------- maps : dict Dictionary containing result maps. Keys are: * ts : delta TS map * sqrt_ts : sqrt(delta TS), or significance map * flux : flux map * flux_err : symmetric error map * flux_ul : upper limit map """ p = self.parameters # First create 2D map arrays counts = dataset.counts.sum_over_axes(keepdims=False) background = dataset.npred().sum_over_axes(keepdims=False) exposure = dataset.exposure.sum_over_axes(keepdims=False) kernel = self.get_kernel(dataset) if dataset.mask is not None: mask = counts.copy( data=(dataset.mask.sum(axis=0) > 0).astype("int")) else: mask = counts.copy(data=np.ones_like(counts).astype("int")) if self.downsampling_factor: shape = counts.data.shape pad_width = symmetric_crop_pad_width(shape, shape_2N(shape))[0] counts = counts.pad(pad_width).downsample(self.downsampling_factor, preserve_counts=True) background = background.pad(pad_width).downsample( self.downsampling_factor, preserve_counts=True) exposure = exposure.pad(pad_width).downsample( self.downsampling_factor, preserve_counts=False) mask = mask.pad(pad_width).downsample(self.downsampling_factor, preserve_counts=False) mask.data = mask.data.astype("int") mask.data &= self.mask_default(exposure, background, kernel.data).data if steps == "all": steps = ["ts", "sqrt_ts", "flux", "flux_err", "flux_ul", "niter"] result = {} for name in steps: data = np.nan * np.ones_like(counts.data) result[name] = counts.copy(data=data) flux_map = self.flux_default(dataset, kernel.data) if p["threshold"] or p["method"] == "root newton": flux = flux_map.data else: flux = None # prepare dtype for cython methods counts_array = counts.data.astype(float) background_array = background.data.astype(float) exposure_array = exposure.data.astype(float) # Compute null statistics per pixel for the whole image c_0 = cash(counts_array, background_array) error_method = p["error_method"] if "flux_err" in steps else "none" ul_method = p["ul_method"] if "flux_ul" in steps else "none" wrap = functools.partial( _ts_value, counts=counts_array, exposure=exposure_array, background=background_array, c_0=c_0, kernel=kernel.data, flux=flux, method=p["method"], error_method=error_method, threshold=p["threshold"], error_sigma=p["error_sigma"], ul_method=ul_method, ul_sigma=p["ul_sigma"], rtol=p["rtol"], ) x, y = np.where(np.squeeze(mask.data)) positions = list(zip(x, y)) results = list(map(wrap, positions)) # Set TS values at given positions j, i = zip(*positions) for name in ["ts", "flux", "niter"]: result[name].data[j, i] = [_[name] for _ in results] if "flux_err" in steps: result["flux_err"].data[j, i] = [_["flux_err"] for _ in results] if "flux_ul" in steps: result["flux_ul"].data[j, i] = [_["flux_ul"] for _ in results] # Compute sqrt(TS) values if "sqrt_ts" in steps: result["sqrt_ts"] = self.sqrt_ts(result["ts"]) if self.downsampling_factor: for name in steps: order = 0 if name == "niter" else 1 result[name] = result[name].upsample( factor=self.downsampling_factor, preserve_counts=False, order=order) result[name] = result[name].crop(crop_width=pad_width) # Set correct units if "flux" in steps: result["flux"].unit = flux_map.unit if "flux_err" in steps: result["flux_err"].unit = flux_map.unit if "flux_ul" in steps: result["flux_ul"].unit = flux_map.unit return result
def stat_array(self): """Likelihood per bin given the current model parameters""" return cash(n_on=self.counts.data, mu_on=self.npred().data)
def test_cash_bad_truncation(): with pytest.raises(ValueError): stats.cash(10, 10, 0.0)
def run(self, dataset, kernel, which="all", downsampling_factor=None): """ Run TS map estimation. Requires a MapDataset with counts, exposure and background_model properly set to run. Parameters ---------- kernel : `astropy.convolution.Kernel2D` or 2D `~numpy.ndarray` Source model kernel. which : list of str or 'all' Which maps to compute. downsampling_factor : int Sample down the input maps to speed up the computation. Only integer values that are a multiple of 2 are allowed. Note that the kernel is not sampled down, but must be provided with the downsampled bin size. Returns ------- maps : dict Result maps. """ p = self.parameters if (np.array(kernel.shape) > np.array(dataset.counts.data.shape[1:])).any(): raise ValueError( "Kernel shape larger than map shape, please adjust" " size of the kernel" ) # First create 2D map arrays counts = dataset.counts.sum_over_axes(keepdims=False) background = dataset.npred().sum_over_axes(keepdims=False) exposure = dataset.exposure.sum_over_axes(keepdims=False) if dataset.mask is not None: mask = counts.copy(data=(dataset.mask.sum(axis=0) > 0).astype("int")) else: mask = counts.copy(data=np.ones_like(counts).astype("int")) if downsampling_factor: shape = counts.data.shape pad_width = symmetric_crop_pad_width(shape, shape_2N(shape))[0] counts = counts.pad(pad_width).downsample( downsampling_factor, preserve_counts=True ) background = background.pad(pad_width).downsample( downsampling_factor, preserve_counts=True ) exposure = exposure.pad(pad_width).downsample( downsampling_factor, preserve_counts=False ) mask = mask.pad(pad_width).downsample( downsampling_factor, preserve_counts=False ) mask.data = mask.data.astype("int") mask.data &= self.mask_default(exposure, background, kernel).data if not isinstance(kernel, Kernel2D): kernel = CustomKernel(kernel) if which == "all": which = ["ts", "sqrt_ts", "flux", "flux_err", "flux_ul", "niter"] result = {} for name in which: data = np.nan * np.ones_like(counts.data) result[name] = counts.copy(data=data) flux_map = self.flux_default(dataset, kernel) if p["threshold"] or p["method"] == "root newton": flux = flux_map.data else: flux = None # prepare dtype for cython methods counts_array = counts.data.astype(float) background_array = background.data.astype(float) exposure_array = exposure.data.astype(float) # Compute null statistics per pixel for the whole image c_0 = cash(counts_array, background_array) error_method = p["error_method"] if "flux_err" in which else "none" ul_method = p["ul_method"] if "flux_ul" in which else "none" wrap = functools.partial( _ts_value, counts=counts_array, exposure=exposure_array, background=background_array, c_0=c_0, kernel=kernel, flux=flux, method=p["method"], error_method=error_method, threshold=p["threshold"], error_sigma=p["error_sigma"], ul_method=ul_method, ul_sigma=p["ul_sigma"], rtol=p["rtol"], ) x, y = np.where(np.squeeze(mask.data)) positions = list(zip(x, y)) results = list(map(wrap, positions)) # Set TS values at given positions j, i = zip(*positions) for name in ["ts", "flux", "niter"]: result[name].data[j, i] = [_[name] for _ in results] if "flux_err" in which: result["flux_err"].data[j, i] = [_["flux_err"] for _ in results] if "flux_ul" in which: result["flux_ul"].data[j, i] = [_["flux_ul"] for _ in results] # Compute sqrt(TS) values if "sqrt_ts" in which: result["sqrt_ts"] = self.sqrt_ts(result["ts"]) if downsampling_factor: for name in which: order = 0 if name == "niter" else 1 result[name] = result[name].upsample( factor=downsampling_factor, preserve_counts=False, order=order ) result[name] = result[name].crop(crop_width=pad_width) # Set correct units if "flux" in which: result["flux"].unit = flux_map.unit if "flux_err" in which: result["flux_err"].unit = flux_map.unit if "flux_ul" in which: result["flux_ul"].unit = flux_map.unit return result
def _stat_fcn(self, mu, delta=0, index=None): return cash(self.n_on[index], self.mu_bkg[index] + mu) - delta
def TS_max(self): """Stat value for best fit hypothesis, i.e. expected signal mu = n_on - mu_bkg""" return cash(self.n_on, self.n_on)
def TS_null(self): """Stat value for null hypothesis, i.e. 0 expected signal counts""" return cash(self.n_on, self.mu_bkg + 0)
def run(self, maps, kernel, which="all", downsampling_factor=None): """ Run TS map estimation. Requires "counts", "exposure" and "background" map to run. Parameters ---------- maps : dict Input sky maps. kernel : `astropy.convolution.Kernel2D` or 2D `~numpy.ndarray` Source model kernel. which : list of str or 'all' Which maps to compute. downsampling_factor : int Sample down the input maps to speed up the computation. Only integer values that are a multiple of 2 are allowed. Note that the kernel is not sampled down, but must be provided with the downsampled bin size. Returns ------- maps : dict Result maps. """ p = self.parameters if (np.array(kernel.shape) > np.array(maps["counts"].data.shape)).any(): raise ValueError( "Kernel shape larger than map shape, please adjust" " size of the kernel" ) if downsampling_factor: maps_downsampled = {} shape = maps["counts"].data.shape pad_width = symmetric_crop_pad_width(shape, shape_2N(shape))[0] for name, map_ in maps.items(): preserve_counts = name in ["counts", "background", "exclusion"] maps_downsampled[name] = map_.pad(pad_width).downsample( downsampling_factor, preserve_counts=preserve_counts ) maps = maps_downsampled if not isinstance(kernel, Kernel2D): kernel = CustomKernel(kernel) if which == "all": which = ["ts", "sqrt_ts", "flux", "flux_err", "flux_ul", "niter"] result = {} for name in which: data = np.nan * np.ones_like(maps["counts"].data) result[name] = maps["counts"].copy(data=data) mask = self.mask_default(maps, kernel) if "mask" in maps: mask.data &= maps["mask"].data if p["threshold"] or p["method"] == "root newton": flux = self.flux_default(maps, kernel).data else: flux = None # prepare dtype for cython methods counts = maps["counts"].data.astype(float) background = maps["background"].data.astype(float) exposure = maps["exposure"].data.astype(float) # Compute null statistics per pixel for the whole image c_0 = cash(counts, background) error_method = p["error_method"] if "flux_err" in which else "none" ul_method = p["ul_method"] if "flux_ul" in which else "none" wrap = functools.partial( _ts_value, counts=counts, exposure=exposure, background=background, c_0=c_0, kernel=kernel, flux=flux, method=p["method"], error_method=error_method, threshold=p["threshold"], error_sigma=p["error_sigma"], ul_method=ul_method, ul_sigma=p["ul_sigma"], rtol=p["rtol"], ) x, y = np.where(mask.data) positions = list(zip(x, y)) with contextlib.closing(multiprocessing.Pool(processes=p["n_jobs"])) as pool: log.info("Using {} jobs to compute TS map.".format(p["n_jobs"])) results = pool.map(wrap, positions) pool.join() # Set TS values at given positions j, i = zip(*positions) for name in ["ts", "flux", "niter"]: result[name].data[j, i] = [_[name] for _ in results] if "flux_err" in which: result["flux_err"].data[j, i] = [_["flux_err"] for _ in results] if "flux_ul" in which: result["flux_ul"].data[j, i] = [_["flux_ul"] for _ in results] # Compute sqrt(TS) values if "sqrt_ts" in which: result["sqrt_ts"] = self.sqrt_ts(result["ts"]) if downsampling_factor: for name in which: order = 0 if name == "niter" else 1 result[name] = result[name].upsample( factor=downsampling_factor, preserve_counts=False, order=order ) result[name] = result[name].crop(crop_width=pad_width) return result
def _excess_matching_significance_fcn(self, excess, significance, index): TS0 = cash(excess + self.background[index], self.mu_bkg[index]) TS1 = cash(excess + self.background[index], self.mu_bkg[index] + excess) return np.sign(excess) * np.sqrt(np.clip(TS0 - TS1, 0, None)) - significance