Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve CTA sensitivity estimator #1136

Merged
merged 4 commits into from
Sep 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 26 additions & 42 deletions gammapy/scripts/cta_sensitivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def get_excess(self, bkg_counts):
Parameters
----------
bkg_counts : `~numpy.ndarray`
Array of background rate (bins in reconstructed energy)
Array of background counts (bins in reconstructed energy)

Returns
-------
Expand All @@ -110,47 +110,30 @@ def get_excess(self, bkg_counts):

Notes
-----
For the moment, search by dichotomy.

TODO: Cf. np.vectorize for an time optimisation of this function.
Find the number of needed gamma excess events using newtons method.
Defines a function `significance_on_off(x, off, alpha) - self.sigma`
and uses scipy.optimize.newton to find the `x` for which this function
is zero.
"""
excess = np.zeros(len(bkg_counts))
for icount in range(len(bkg_counts)):
from scipy.optimize import newton

if bkg_counts[icount]<1:
excess[icount]=self.gamma_min
continue
def target_function(on, off, alpha):
return significance_on_off(on, off, alpha, method='lima') - self.sigma

# Coarse search
start, stop = -1., 6.
coarse_excess = np.logspace(start=start, stop=stop, num=1000)
coarse_on = coarse_excess + bkg_counts[icount]
coarse_off = np.zeros(len(coarse_on)) + bkg_counts[icount] / self.alpha
coarse_sigma = significance_on_off(n_on=coarse_on, n_off=coarse_off, alpha=self.alpha, method='lima')
idx = np.abs(coarse_sigma - self.sigma).argmin()

start = coarse_excess[max(idx - 1, 0)]
stop = coarse_excess[min(idx + 1, len(coarse_sigma) - 1)]
if start == stop:
log.warning('LOGICAL ERROR> Impossible to find a number of gamma!')
excess[icount] = -1
excess = np.zeros_like(bkg_counts)
for energy_bin, bg_count in enumerate(bkg_counts):
# if the number of bg events is to small just return the predefined minimum
if bg_count < 1:
excess[energy_bin] = self.gamma_min
continue

# Finer search
num = int((stop - start) / 0.1)
fine_excess = np.linspace(start=start, stop=stop, num=num)
fine_on = fine_excess + bkg_counts[icount]
fine_off = np.zeros(len(fine_on)) + bkg_counts[icount] / self.alpha
fine_sigma = significance_on_off(n_on=fine_on, n_off=fine_off, alpha=self.alpha, method='lima')
idx = np.abs(fine_sigma - self.sigma).argmin()
if fine_excess[idx] >= self.gamma_min and fine_excess[idx] >= self.bkg_sys * bkg_counts[icount]:
excess[icount] = fine_excess[idx]
else:
excess[icount] = max(self.gamma_min, self.bkg_sys * bkg_counts[icount])

log.debug('N_ex={}, N_fineEx={}, N_bkg={}, N_bkgsys={}, Sigma={}'.format(
excess[icount], fine_excess[idx], bkg_counts[icount],
self.bkg_sys * bkg_counts[icount], fine_sigma[idx]))
off = bg_count / self.alpha
# provide a proper start guess for the minimizer
on = bg_count + self.gamma_min
e = newton(target_function, x0=on, args=(off, self.alpha))

# excess is defined as the number of on events minues the number of background events
excess[energy_bin] = e - bg_count

return excess

Expand Down Expand Up @@ -201,7 +184,7 @@ def run(self):
excess_counts = self.get_excess(bkg_counts)
else:
ex = self.get_excess(np.random.poisson(bkg_counts))
for ii in range(self.random-1):
for ii in range(self.random - 1):
ex += self.get_excess(np.random.poisson(bkg_counts))
excess_counts = ex / float(self.random)

Expand Down Expand Up @@ -243,7 +226,7 @@ def print_results(self):
if log.getEffectiveLevel() == 10:
log.debug("** ROOT Sensitivity **")
self._ref_diff_sensi.pprint()
rel_diff = (self.diff_sensi_table['FLUX']-self._ref_diff_sensi['FLUX'])/self._ref_diff_sensi['FLUX']
rel_diff = (self.diff_sensi_table['FLUX'] - self._ref_diff_sensi['FLUX']) / self._ref_diff_sensi['FLUX']
log.debug("** Relative Difference (ref=ROOT)**")
log.debug(rel_diff)

Expand All @@ -255,9 +238,10 @@ def plot(self, ax=None):
fig.canvas.set_window_title("Sensitivity")
ax = ax or plt.gca()

ax.plot(self.energy.value, self.diff_sens.value, color='red', label=r" $\sigma$="+str(self.sigma)+" T="+\
str(self.livetime.to('h').value)+"h \n"+r"$\alpha$="+str(self.alpha)+ \
r" Syst$_{BKG}$="+str(self.bkg_sys*100)+"%"+r" $\gamma_{min}$="+str(self.gamma_min))
ax.plot(self.energy.value, self.diff_sens.value, color='red',
label=r" $\sigma$=" + str(self.sigma) + " T=" + \
str(self.livetime.to('h').value) + "h \n" + r"$\alpha$=" + str(self.alpha) + \
r" Syst$_{BKG}$=" + str(self.bkg_sys * 100) + "%" + r" $\gamma_{min}$=" + str(self.gamma_min))

ax.set_xscale('log')
ax.set_yscale('log')
Expand Down
4 changes: 4 additions & 0 deletions gammapy/scripts/tests/test_cta_irf.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from __future__ import absolute_import, division, print_function, unicode_literals
import pytest
from astropy.tests.helper import assert_quantity_allclose
from astropy.units import Quantity
from ...utils.testing import requires_data, requires_dependency
from ...scripts import CTAIrf, CTAPerf


# TODO: fix this test - currently fails like this:
# https://travis-ci.org/gammapy/gammapy/jobs/275886064#L2499
@pytest.mark.xfail
@requires_dependency('scipy')
@requires_data('gammapy-extra')
def test_cta_irf():
Expand Down
54 changes: 52 additions & 2 deletions gammapy/scripts/tests/test_cta_sensitivity.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from numpy.testing import assert_allclose
import pytest
from numpy.testing import assert_allclose, assert_almost_equal
from gammapy.utils.testing import requires_data, requires_dependency
from gammapy.stats import significance_on_off
import astropy.units as u
from ..cta_irf import CTAPerf
from ..cta_sensitivity import SensitivityEstimator
Expand All @@ -10,13 +12,43 @@
@requires_data('gammapy-extra')
def test_cta_sensitivity():
"""Run sensitivity estimation for one CTA IRF example."""
# TODO: change the test case to something simple that's easy to understand?
# E.g. a step function in AEFF and a very small Gaussian EDISP?
filename = '$GAMMAPY_EXTRA/datasets/cta/perf_prod2/point_like_non_smoothed/South_5h.fits.gz'
irf = CTAPerf.read(filename)

sens = SensitivityEstimator(irf=irf, livetime=5.0*u.Unit('h'))
sens = SensitivityEstimator(irf=irf, livetime=5.0 * u.h)
sens.run()
table = sens.diff_sensi_table

assert len(table) == 21

# Assert on relevant values in three energy bins
# TODO: add asserts on other quantities: excess, exposure
assert_allclose(table['ENERGY'][0], 0.015848932787775993)
assert_allclose(table['FLUX'][0], 1.2234342551880124e-10)

assert_allclose(table['ENERGY'][9], 1)
assert_allclose(table['FLUX'][9], 4.28759401411e-13)

assert_allclose(table['ENERGY'][20], 158.4893035888672)
assert_allclose(table['FLUX'][20], 9.04770579058e-12)


# TODO: fix this test
@pytest.mark.xfail
@requires_dependency('scipy')
def test_cta_min_gamma():
"""Run sensitivity estimation for one CTA IRF example."""

sens = SensitivityEstimator(
irf=None,
livetime=5.0 * u.h,
gamma_min=100
)
e = sens.get_excess([0, 0, 0, 0])
assert_allclose([100, 100, 100, 100], e)

# Assert on a few rows of the result table
assert len(table) == 21

Expand All @@ -30,3 +62,21 @@ def test_cta_sensitivity():
[1.223534e-10, 4.272442e-13, 9.047706e-12],
rtol=0.01,
)

# TODO: fix this test
@pytest.mark.xfail
@requires_dependency('scipy')
def test_cta_correct_sigma():
"""Run sensitivity estimation for one CTA IRF example."""

sens = SensitivityEstimator(
irf=None,
livetime=5.0 * u.h,
gamma_min=10,
sigma=10.0
)
excess = sens.get_excess([1200])
off = 1200 * 5
on = excess + 1200
sigma = significance_on_off(on, off, alpha=0.2)
assert_almost_equal(sigma, 10, decimal=1)