3
3
from fact .analysis import li_ma_significance
4
4
from scipy .optimize import newton
5
5
import warnings
6
+ import pandas as pd
7
+ from scipy .stats import norm
6
8
7
9
8
10
@u .quantity_input (t_obs = u .hour , t_ref = u .hour )
@@ -11,7 +13,7 @@ def relative_sensitivity(
11
13
n_off ,
12
14
alpha ,
13
15
t_obs ,
14
- t_ref = 50 * u .hour ,
16
+ t_ref = u . Quantity ( 50 , u .hour ) ,
15
17
target_significance = 5 ,
16
18
significance_function = li_ma_significance ,
17
19
initial_guess = 0.5 ,
@@ -99,3 +101,92 @@ def equation(relative_flux):
99
101
'significance_function' ,
100
102
]
101
103
)
104
+
105
+
106
+ @u .quantity_input ()
107
+ def calculate_sensitivity (
108
+ df ,
109
+ theta2_cut ,
110
+ e_min : u .TeV ,
111
+ e_max : u .TeV ,
112
+ n_bins ,
113
+ t_obs : u .hour ,
114
+ t_ref : u .hour ,
115
+ n_bootstrap = 100 ,
116
+ min_events = 5 ,
117
+ ):
118
+ theta_cut = np .sqrt (theta2_cut )
119
+
120
+ def select_on (df ):
121
+ return df .theta_deg <= theta_cut
122
+
123
+ def select_off (df ):
124
+ m = df ['theta_deg_off_1' ] <= theta_cut
125
+ for i in range (2 , 6 ):
126
+ m |= df [f'theta_deg_off_{ i } ' ] <= theta_cut
127
+ return m
128
+
129
+ df = df .copy ()
130
+ if 'weight' not in df .columns :
131
+ df ['weight' ] = 1
132
+
133
+ bin_edges = np .logspace (np .log10 (e_min / u .GeV ), np .log10 (e_max / u .GeV ), n_bins + 1 )
134
+ bin_edges = np .append (- np .inf , np .append (bin_edges , np .inf ))
135
+ bin_id = np .arange (n_bins + 2 ) + 1
136
+
137
+ df ['bin' ] = np .digitize (df ['gamma_energy_prediction' ].values , bin_edges )
138
+
139
+ sensitivity = pd .DataFrame (index = bin_id )
140
+ sensitivity ['e_low' ] = bin_edges [:- 1 ]
141
+ sensitivity ['e_high' ] = bin_edges [1 :]
142
+ sensitivity ['e_center' ] = 0.5 * (sensitivity ['e_low' ] + sensitivity ['e_high' ])
143
+ sensitivity ['e_width' ] = np .diff (bin_edges )
144
+ sensitivity ['n_on_weighted' ] = 0
145
+ sensitivity ['n_off_weighted' ] = 0
146
+ sensitivity ['n_on' ] = 0
147
+ sensitivity ['n_off' ] = 0
148
+ sensitivity .index .name = 'bin'
149
+
150
+ sensitivities = []
151
+ for i in range (n_bootstrap ):
152
+ cur_sensitivity = sensitivity .copy ()
153
+
154
+ sampled = df .sample (len (df ), replace = True )
155
+ for bin_id , g in sampled .groupby ('bin' ):
156
+ on = select_on (g )
157
+ off = select_off (g )
158
+
159
+ cur_sensitivity .loc [bin_id , 'n_on_weighted' ] = g .loc [on , 'weight' ].sum ()
160
+ cur_sensitivity .loc [bin_id , 'n_off_weighted' ] += g .loc [off , 'weight' ].sum ()
161
+ cur_sensitivity .loc [bin_id , 'n_on' ] = on .sum ()
162
+ cur_sensitivity .loc [bin_id , 'n_off' ] = off .sum ()
163
+
164
+ cur_sensitivity ['relative_sensitivity' ] = relative_sensitivity (
165
+ cur_sensitivity ['n_on_weighted' ],
166
+ cur_sensitivity ['n_off_weighted' ],
167
+ alpha = 0.2 ,
168
+ t_obs = t_obs ,
169
+ )
170
+ cur_sensitivity ['iteration' ] = i
171
+
172
+ sensitivities .append (cur_sensitivity .reset_index ())
173
+
174
+ sensitivities = pd .concat (sensitivities )
175
+
176
+ # aggregate bootstrap samples
177
+ grouped = sensitivities .groupby ('bin' )
178
+ keys = ['n_on' , 'n_off' , 'n_on_weighted' , 'n_off_weighted' , 'relative_sensitivity' ]
179
+ for key in keys :
180
+ sensitivity [key ] = grouped [key ].median ()
181
+ sensitivity [key + '_uncertainty_low' ] = grouped [key ].quantile (norm .cdf (- 1 ))
182
+ sensitivity [key + '_uncertainty_high' ] = grouped [key ].quantile (norm .cdf (1 ))
183
+ sensitivity ['count' ] = grouped ['relative_sensitivity' ].count ()
184
+
185
+ invalid = (
186
+ (sensitivity ['n_on' ] < min_events )
187
+ | (sensitivity ['n_off' ] < min_events )
188
+ | (sensitivity ['count' ] / n_bootstrap <= 0.95 )
189
+ )
190
+ sensitivity .loc [invalid , 'relative_sensitivity' ] = np .nan
191
+
192
+ return sensitivity
0 commit comments