1
|
|
|
from ..psf_fast import PSFWrapper, InvalidPSF |
|
|
|
|
2
|
|
|
import numpy as np |
|
|
|
|
3
|
|
|
import pandas as pd |
|
|
|
|
4
|
|
|
|
5
|
|
|
from threeML.exceptions.custom_exceptions import custom_warnings |
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
class ResponseBin(object): |
|
|
|
|
9
|
|
|
|
10
|
|
|
def __init__(self, name, min_dec, max_dec, dec_center, |
|
|
|
|
11
|
|
|
sim_n_sig_events, sim_n_bg_events, |
12
|
|
|
sim_energy_bin_low, sim_energy_bin_centers, sim_energy_bin_hi, |
13
|
|
|
sim_differential_photon_fluxes, sim_signal_events_per_bin, |
14
|
|
|
psf): |
15
|
|
|
|
16
|
|
|
self._name = name |
17
|
|
|
self._min_dec = min_dec |
18
|
|
|
self._max_dec = max_dec |
19
|
|
|
self._dec_center = dec_center |
20
|
|
|
self._sim_n_sig_events = sim_n_sig_events |
21
|
|
|
self._sim_n_bg_events = sim_n_bg_events |
22
|
|
|
self._sim_energy_bin_low = sim_energy_bin_low |
23
|
|
|
self._sim_energy_bin_centers = sim_energy_bin_centers |
24
|
|
|
self._sim_energy_bin_hi = sim_energy_bin_hi |
25
|
|
|
self._sim_differential_photon_fluxes = sim_differential_photon_fluxes |
26
|
|
|
self._sim_signal_events_per_bin = sim_signal_events_per_bin |
27
|
|
|
self._psf = psf # type: PSFWrapper |
28
|
|
|
|
29
|
|
|
@staticmethod |
30
|
|
|
def _get_en_th1d(open_ttree, dec_id, dec_id_label, analysis_bin_id, analysis_bin_id_label, prefix): |
|
|
|
|
31
|
|
|
|
32
|
|
|
nh_name = "nh%i" % analysis_bin_id |
33
|
|
|
|
34
|
|
|
en_sig_label = "En%s_dec%i_%s" % (prefix, dec_id, nh_name) |
35
|
|
|
|
36
|
|
|
this_en_th1d = open_ttree.Get("%s/%s/%s" % (dec_id_label, analysis_bin_id_label, en_sig_label)) |
|
|
|
|
37
|
|
|
|
38
|
|
|
if not this_en_th1d: |
39
|
|
|
|
40
|
|
|
# Some old responses have a slightly different label. Try with that |
41
|
|
|
nh_name = "nh%02i" % analysis_bin_id |
42
|
|
|
|
43
|
|
|
en_sig_label = "En%s_dec%i_%s" % (prefix, dec_id, nh_name) |
44
|
|
|
|
45
|
|
|
this_en_th1d = open_ttree.Get("%s/%s/%s" % (dec_id_label, analysis_bin_id_label, en_sig_label)) |
|
|
|
|
46
|
|
|
|
47
|
|
|
if not this_en_th1d: |
48
|
|
|
|
49
|
|
|
raise IOError("Could not find TH1D named %s" % this_en_th1d) |
50
|
|
|
|
51
|
|
|
return this_en_th1d, nh_name |
52
|
|
|
|
53
|
|
|
@classmethod |
54
|
|
|
def from_ttree(cls, open_ttree, dec_id, analysis_bin_id, log_log_spectrum, min_dec, dec_center, max_dec): |
|
|
|
|
55
|
|
|
|
56
|
|
|
from ..root_handler import ROOT |
57
|
|
|
|
58
|
|
|
# Compute the labels as used in the response file |
59
|
|
|
dec_id_label = "dec_%02i" % dec_id |
60
|
|
|
analysis_bin_id_label = "nh_%02i" % analysis_bin_id |
61
|
|
|
|
62
|
|
|
# Read the histogram of the simulated events detected in this bin_name |
63
|
|
|
# NOTE: we do not copy this TH1D instance because we won't use it after the |
64
|
|
|
# file is closed |
65
|
|
|
|
66
|
|
|
this_en_sig_th1d, nh_name = cls._get_en_th1d(open_ttree, dec_id, dec_id_label, analysis_bin_id, analysis_bin_id_label, |
|
|
|
|
67
|
|
|
'Sig') |
68
|
|
|
|
69
|
|
|
# The sum of the histogram is the total number of simulated events detected |
70
|
|
|
# in this analysis bin_name |
71
|
|
|
sim_n_sig_events = this_en_sig_th1d.Integral() |
72
|
|
|
|
73
|
|
|
# Now let's see what has been simulated, i.e., the differential flux |
74
|
|
|
# at the center of each bin_name of the en_sig histogram |
75
|
|
|
sim_energy_bin_low = np.zeros(this_en_sig_th1d.GetNbinsX()) |
76
|
|
|
sim_energy_bin_centers = np.zeros(this_en_sig_th1d.GetNbinsX()) |
77
|
|
|
sim_energy_bin_hi = np.zeros(this_en_sig_th1d.GetNbinsX()) |
78
|
|
|
sim_signal_events_per_bin = np.zeros_like(sim_energy_bin_centers) |
79
|
|
|
sim_differential_photon_fluxes = np.zeros_like(sim_energy_bin_centers) |
80
|
|
|
|
81
|
|
|
for i in range(sim_energy_bin_centers.shape[0]): |
82
|
|
|
# Remember: bin_name 0 is the underflow bin_name, that is why there |
83
|
|
|
# is a "i+1" and not just "i" |
84
|
|
|
bin_lo = this_en_sig_th1d.GetBinLowEdge(i+1) |
85
|
|
|
bin_center = this_en_sig_th1d.GetBinCenter(i + 1) |
86
|
|
|
bin_hi = this_en_sig_th1d.GetBinWidth(i+1) + bin_lo |
87
|
|
|
|
88
|
|
|
# Store the center of the logarithmic bin_name |
89
|
|
|
sim_energy_bin_low[i] = 10 ** bin_lo # TeV |
90
|
|
|
sim_energy_bin_centers[i] = 10 ** bin_center # TeV |
91
|
|
|
sim_energy_bin_hi[i] = 10 ** bin_hi # TeV |
92
|
|
|
|
93
|
|
|
# Get from the simulated spectrum the value of the differential flux |
94
|
|
|
# at the center energy |
95
|
|
|
sim_differential_photon_fluxes[i] = 10 ** log_log_spectrum.Eval(bin_center) # TeV^-1 cm^-1 s^-1 |
|
|
|
|
96
|
|
|
|
97
|
|
|
# Get from the histogram the detected events in each log-energy bin_name |
98
|
|
|
sim_signal_events_per_bin[i] = this_en_sig_th1d.GetBinContent(i + 1) |
99
|
|
|
|
100
|
|
|
# Read the histogram of the bkg events detected in this bin_name |
101
|
|
|
# NOTE: we do not copy this TH1D instance because we won't use it after the |
102
|
|
|
# file is closed |
103
|
|
|
|
104
|
|
|
this_en_bg_th1d, _ = cls._get_en_th1d(open_ttree, dec_id, dec_id_label, analysis_bin_id, analysis_bin_id_label, |
|
|
|
|
105
|
|
|
'Bg') |
106
|
|
|
|
107
|
|
|
# The sum of the histogram is the total number of simulated events detected |
108
|
|
|
# in this analysis bin_name |
109
|
|
|
sim_n_bg_events = this_en_bg_th1d.Integral() |
110
|
|
|
|
111
|
|
|
# Now read the various TF1(s) for PSF, signal and background |
112
|
|
|
|
113
|
|
|
# Read the PSF and make a copy (so it will stay when we close the file) |
114
|
|
|
|
115
|
|
|
psf_label_tf1 = "PSF_dec%i_%s_fit" % (dec_id, nh_name) |
116
|
|
|
|
117
|
|
|
psf_path = "%s/%s/%s" % (dec_id_label, analysis_bin_id_label, psf_label_tf1) |
118
|
|
|
|
119
|
|
|
tf1 = ROOT.TF1() |
120
|
|
|
|
121
|
|
|
open_ttree.GetObject(psf_path, tf1) |
122
|
|
|
|
123
|
|
|
psf_fun = PSFWrapper.from_TF1(tf1) |
124
|
|
|
|
125
|
|
|
return cls(analysis_bin_id, min_dec, max_dec, dec_center, sim_n_sig_events, sim_n_bg_events, |
126
|
|
|
sim_energy_bin_low, |
127
|
|
|
sim_energy_bin_centers, |
128
|
|
|
sim_energy_bin_hi, |
129
|
|
|
sim_differential_photon_fluxes, sim_signal_events_per_bin, |
130
|
|
|
psf_fun) |
131
|
|
|
|
132
|
|
|
def to_pandas(self): |
|
|
|
|
133
|
|
|
|
134
|
|
|
# In the metadata let's save all single values (floats) |
135
|
|
|
meta = {'min_dec': self._min_dec, |
136
|
|
|
'max_dec': self._max_dec, |
137
|
|
|
'declination_center': self._dec_center, |
138
|
|
|
'n_sim_signal_events': self._sim_n_sig_events, |
139
|
|
|
'n_sim_bkg_events': self._sim_n_bg_events |
140
|
|
|
} |
|
|
|
|
141
|
|
|
|
142
|
|
|
# Now make a dataframe containing the elements of the simulation |
143
|
|
|
items = ( |
144
|
|
|
('sim_energy_bin_low', pd.Series(self.sim_energy_bin_low)), |
145
|
|
|
('sim_energy_bin_centers', pd.Series(self.sim_energy_bin_centers)), |
146
|
|
|
('sim_energy_bin_hi', pd.Series(self.sim_energy_bin_hi)), |
147
|
|
|
('sim_differential_photon_fluxes', pd.Series(self.sim_differential_photon_fluxes)), |
148
|
|
|
('sim_signal_events_per_bin', pd.Series(self.sim_signal_events_per_bin)) |
149
|
|
|
) |
150
|
|
|
|
151
|
|
|
df = pd.DataFrame.from_dict(dict(items)) |
|
|
|
|
152
|
|
|
|
153
|
|
|
return df, meta, self.psf.to_pandas() |
154
|
|
|
|
155
|
|
|
def combine_with_weights(self, other_response_bin, dec_center, w1, w2): |
|
|
|
|
156
|
|
|
""" |
157
|
|
|
Produce another response bin which is the weighted sum of this one and the other one passed. |
158
|
|
|
|
159
|
|
|
:param other_response_bin: |
160
|
|
|
:param w1: |
161
|
|
|
:param w2: |
162
|
|
|
:return: |
163
|
|
|
""" |
164
|
|
|
|
165
|
|
|
assert np.isclose(w1+w2, 1.0), "Weights are not properly normalized" |
166
|
|
|
|
167
|
|
|
new_name = "interpolated_%s" % self._name |
168
|
|
|
|
169
|
|
|
# Use np.nan as declination boundaries to indicate that this is actually interpolated |
170
|
|
|
min_dec, max_dec = np.nan, np.nan |
171
|
|
|
|
172
|
|
|
n_sim_signal_events = w1 * self._sim_n_sig_events + w2 * other_response_bin._sim_n_sig_events |
|
|
|
|
173
|
|
|
n_sim_bkg_events = w1 * self._sim_n_bg_events + w2 * other_response_bin._sim_n_bg_events |
174
|
|
|
|
175
|
|
|
# We assume that the bin centers are the same |
176
|
|
|
assert np.allclose(self._sim_energy_bin_centers, other_response_bin._sim_energy_bin_centers) |
|
|
|
|
177
|
|
|
|
178
|
|
|
sim_differential_photon_fluxes = w1 * self._sim_differential_photon_fluxes + \ |
179
|
|
|
w2 * other_response_bin._sim_differential_photon_fluxes |
180
|
|
|
|
181
|
|
|
sim_signal_events_per_bin = w1 * self._sim_signal_events_per_bin + \ |
182
|
|
|
w2 * other_response_bin._sim_signal_events_per_bin |
183
|
|
|
|
184
|
|
|
# Now interpolate the psf |
185
|
|
|
new_psf = self._psf.combine_with_other_psf(other_response_bin._psf, w1, w2) |
|
|
|
|
186
|
|
|
|
187
|
|
|
new_response_bin = ResponseBin(new_name, min_dec, max_dec, dec_center, |
188
|
|
|
n_sim_signal_events, n_sim_bkg_events, |
189
|
|
|
self._sim_energy_bin_low, |
190
|
|
|
self._sim_energy_bin_centers, |
191
|
|
|
self._sim_energy_bin_hi, |
192
|
|
|
sim_differential_photon_fluxes, |
193
|
|
|
sim_signal_events_per_bin, |
194
|
|
|
new_psf) |
195
|
|
|
|
196
|
|
|
return new_response_bin |
197
|
|
|
|
198
|
|
|
@property |
199
|
|
|
def name(self): |
|
|
|
|
200
|
|
|
return self._name |
201
|
|
|
|
202
|
|
|
@property |
203
|
|
|
def declination_boundaries(self): |
|
|
|
|
204
|
|
|
return (self._min_dec, self._max_dec) |
205
|
|
|
|
206
|
|
|
@property |
207
|
|
|
def declination_center(self): |
|
|
|
|
208
|
|
|
return self._dec_center |
209
|
|
|
|
210
|
|
|
@property |
211
|
|
|
def psf(self): |
|
|
|
|
212
|
|
|
return self._psf |
213
|
|
|
|
214
|
|
|
@property |
215
|
|
|
def n_sim_signal_events(self): |
|
|
|
|
216
|
|
|
return self._sim_n_sig_events |
217
|
|
|
|
218
|
|
|
@property |
219
|
|
|
def n_sim_bkg_events(self): |
|
|
|
|
220
|
|
|
return self._sim_n_bg_events |
221
|
|
|
|
222
|
|
|
@property |
223
|
|
|
def sim_energy_bin_low(self): |
|
|
|
|
224
|
|
|
return self._sim_energy_bin_low |
225
|
|
|
|
226
|
|
|
@property |
227
|
|
|
def sim_energy_bin_centers(self): |
|
|
|
|
228
|
|
|
return self._sim_energy_bin_centers |
229
|
|
|
|
230
|
|
|
@property |
231
|
|
|
def sim_energy_bin_hi(self): |
|
|
|
|
232
|
|
|
return self._sim_energy_bin_hi |
233
|
|
|
|
234
|
|
|
@property |
235
|
|
|
def sim_differential_photon_fluxes(self): |
|
|
|
|
236
|
|
|
return self._sim_differential_photon_fluxes |
237
|
|
|
|
238
|
|
|
@property |
239
|
|
|
def sim_signal_events_per_bin(self): |
|
|
|
|
240
|
|
|
return self._sim_signal_events_per_bin |
|
|
|
|
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.