Producing a Sky Scan with IceCube Public 14-year data

This tutorial shows how to produce a Sky scan using the IceCube 14-year public data with SkyLLH.

[ ]:
import matplotlib.pyplot as plt
import numpy as np

First we have to create a configuration instance.

[3]:
from skyllh.core.config import Config

cfg = Config()

Importing dataset definition of the public 14-year point-source data set. We can get a list of data sets via the access operator [dataset1, dataset2, ...] of the dsc instance. For more details on the create_dataset_collection function check its docstring.

[ ]:
from skyllh.datasets.i3.PublicData_14y_ps import create_dataset_collection

dsc = create_dataset_collection(cfg=cfg)

print(dsc.dataset_names)
datasets = dsc['IC40', 'IC59', 'IC79', 'IC86_I-XI']

The analysis used for the published PRL results is referred in SkyLLH as “traditional point-source analysis” and is pre-defined:

[6]:
from skyllh.analyses.i3.publicdata_ps.time_integrated_ps import create_analysis
from skyllh.core.source_model import PointLikeSource

Defining the Source: Here we use NGC 1068 as the source

Once the source is defined the create_analysis function can create the analysis instance

[7]:
src_ra = 40.67  # degrees
src_dec = -0.01  # degrees

source = PointLikeSource(ra=np.radians(src_ra), dec=np.radians(src_dec))

ana = create_analysis(cfg=cfg, datasets=datasets, source=source)
100%|██████████| 33/33 [00:30<00:00,  1.09it/s]
100%|██████████| 33/33 [00:31<00:00,  1.04it/s]
100%|██████████| 33/33 [00:48<00:00,  1.48s/it]
100%|██████████| 33/33 [00:23<00:00,  1.38it/s]
100%|██████████| 4/4 [12:38<00:00, 189.53s/it]
100%|██████████| 136/136 [00:00<00:00, 845.70it/s]

Unblinding the data

After creating the analysis instance we can unblind the data for the choosen source. Hence, we initialize the analysis with a trial of the experimental data, maximize the log-likelihood ratio function for all given experimental data events, and calculate the test-statistic value. The analysis instance has the method unblind that can be used for that. This method requires a RandomStateService instance in case the minimizer does not succeed and a new set of initial values for the fit parameters need to get generated.

[8]:
from skyllh.core.random import RandomStateService

rss = RandomStateService(3090)
TS, bestfit_params, minimizer_status = ana.unblind(rss)
ns_bf = bestfit_params['ns']
gamma_bf = bestfit_params['gamma']
print(f'Unblinded results: TS={TS:.2f}, ns={ns_bf:.2f}, gamma={gamma_bf:.2f}')
print(f'Minimizer status: {minimizer_status}')
Unblinded results: TS=29.15, ns=80.09, gamma=3.21
Minimizer status: {'grad': array([-3.62369895e-06, -2.31360821e-04]), 'task': 'CONVERGENCE: REL_REDUCTION_OF_F_<=_FACTR*EPSMCH', 'funcalls': 13, 'nit': 10, 'warnflag': 0, 'skyllh_minimizer_n_reps': 0, 'n_llhratio_func_calls': 13}

Producing a Sky Scan near the source

A Sky Scan is done over a patch near the source coordinates.

The change_source property of the Analysis instance allows us to do the scan by reinitializing for every new sky coordinates in the scan

change_source method allows to update all is necessary without having to re-initialize the entire analysis instance. One could obtain the same result by re-instantiating a new analysis at each point in the sky, which would be much slower. change_source is an optimized way to carry this out.

[9]:
# Produce a little skyscan around the source location

src_ra = 40.67  # degrees
src_dec = -0.01  # degrees

ra_min = src_ra - 1.0
ra_max = src_ra + 1.0
dec_min = src_dec - 1.0
dec_max = src_dec + 1.0

nbins = 50
edges_x = np.linspace(ra_min, ra_max, nbins + 1)
edges_y = np.linspace(dec_min, dec_max, nbins + 1)
centers_x = 0.5 * (edges_x[1:] + edges_x[:-1])
centers_y = 0.5 * (edges_y[1:] + edges_y[:-1])

# Initialize the results arrays
results_ts = np.zeros((nbins, nbins), dtype=float)
results_p = np.zeros((nbins, nbins), dtype=float)
results_ns = np.zeros((nbins, nbins), dtype=float)
results_g = np.zeros((nbins, nbins), dtype=float)

for i, ra in enumerate(centers_x):
    for j, dec in enumerate(centers_y):
        # Change source position to evaluate the likelihood
        src_dec = np.radians(dec)
        src_ra = np.radians(ra)
        source = PointLikeSource(src_ra, src_dec)
        ana.change_source(source, update_detsigyield_service=False)

        # Unblind the new position
        rss = RandomStateService(3090)
        (TS, fitparam_dict, status) = ana.unblind(rss)
        results_ts[i, j] = TS
        # results_p[i, j] = ts_to_pvalue.get_pvalue(TS, src_dec, gamma=gamma)
        results_ns[i, j] = fitparam_dict['ns']
        results_g[i, j] = fitparam_dict['gamma']

The contours are computed assuming that the Wilks theorem holds true. That is the TS distribution follows the \(\chi^2\) distribution with 2 degrees of freedom

[32]:
from scipy.stats import chi2

ts_max = results_ts.max()
chi2_68_quantile = ts_max - chi2.ppf(0.68, df=2)
chi2_90_quantile = ts_max - chi2.ppf(0.90, df=2)
chi2_95_quantile = ts_max - chi2.ppf(0.95, df=2)

idx = np.unravel_index(results_ts.argmax(), results_ts.shape)
best_dec = centers_y[idx[0]]
best_ra = centers_x[idx[1]]

fig, ax = plt.subplots(figsize=(4, 4))
X, Y = np.meshgrid(centers_x, centers_y)

src_ra = 40.67  # degrees
src_dec = -0.01

im = ax.pcolormesh(centers_x, centers_y, results_ts, cmap='GnBu')
ax.invert_xaxis()
contour_68 = ax.contour(
    centers_x, centers_y, results_ts, levels=[chi2_68_quantile], colors='w', linestyles='-', linewidths=2
)
contour_90 = ax.contour(
    centers_x, centers_y, results_ts, levels=[chi2_90_quantile], colors='w', linestyles='--', linewidths=2
)
contour_95 = ax.contour(
    centers_x, centers_y, results_ts, levels=[chi2_95_quantile], colors='w', linestyles=':', linewidths=2
)
ax.scatter([src_ra], [src_dec], marker='*', color='gold', s=120, label='Source position')
ax.scatter([best_ra], [best_dec], marker='x', color='w', s=120, label='best fit')


ax.set_ylim(-0.5, 0.75)
ax.set_xlim(40, 41.2)
cbar = plt.colorbar(im)
cbar.set_label('Test Statistic')
plt.xlabel('R.A. (Deg)')
plt.ylabel('Dec. (Deg)')

ax.clabel(
    contour_68, fmt={chi2_68_quantile: '68% CL'}, fontsize=10, inline=False, inline_spacing=5, manual=[(40.5, 0.2)]
)

ax.clabel(
    contour_90, fmt={chi2_90_quantile: '90% CL'}, fontsize=10, inline=False, inline_spacing=5, manual=[(40.1, -0.3)]
)

ax.clabel(
    contour_95, fmt={chi2_95_quantile: '95% CL'}, fontsize=10, inline=False, inline_spacing=5, manual=[(40.6, 0.4)]
)

ax.invert_xaxis()
plt.legend()
plt.show()
../_images/tutorials_sky_scan_19_0.png