hawc_hal.response.response   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 177
dl 0
loc 376
rs 9.76
c 0
b 0
f 0
wmc 33

9 Methods

Rating   Name   Duplication   Size   Complexity  
A HAWCResponse.n_energy_planes() 0 4 1
A HAWCResponse.response_bins() 0 4 1
A HAWCResponse.__init__() 0 5 1
A HAWCResponse.dec_bins() 0 4 1
A HAWCResponse.display() 0 14 3
A HAWCResponse.get_response_dec_bin() 0 50 5
A HAWCResponse.write() 0 45 4
B HAWCResponse.from_hdf5() 0 81 5
C HAWCResponse.from_root_file() 0 101 8

1 Function

Rating   Name   Duplication   Size   Complexity  
A hawc_response_factory() 0 39 4
1
import numpy as np
0 ignored issues
show
Coding Style introduced by
This module should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
Unused Code introduced by
Unused numpy imported as np
Loading history...
2
import pandas as pd
3
import os
0 ignored issues
show
introduced by
standard import "import os" should be placed before "import numpy as np"
Loading history...
4
import collections
0 ignored issues
show
introduced by
standard import "import collections" should be placed before "import numpy as np"
Loading history...
5
6
from ..serialize import Serialization
7
8
from threeML.io.file_utils import file_existing_and_readable, sanitize_filename
0 ignored issues
show
introduced by
third party import "from threeML.io.file_utils import file_existing_and_readable, sanitize_filename" should be placed before "from ..serialize import Serialization"
Loading history...
9
from threeML.exceptions.custom_exceptions import custom_warnings
0 ignored issues
show
introduced by
third party import "from threeML.exceptions.custom_exceptions import custom_warnings" should be placed before "from ..serialize import Serialization"
Loading history...
10
11
from ..psf_fast import PSFWrapper
12
from response_bin import ResponseBin
0 ignored issues
show
Coding Style introduced by
Relative import 'response_bin', should be 'hawc_hal.response.response_bin'
Loading history...
introduced by
first party import "from response_bin import ResponseBin" should be placed before "from ..serialize import Serialization"
Loading history...
13
14
_instances = {}
0 ignored issues
show
Coding Style Naming introduced by
Constant name "_instances" doesn't conform to UPPER_CASE naming style ('(([A-Z_][A-Z0-9_]*)|(__.*__))$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
15
16
17
def hawc_response_factory(response_file_name):
18
    """
19
    A factory function for the response which keeps a cache, so that the same response is not read over and
20
    over again.
21
22
    :param response_file_name:
23
    :return: an instance of HAWCResponse
24
    """
25
26
    response_file_name = sanitize_filename(response_file_name, abspath=True)
27
28
    # See if this response is in the cache, if not build it
29
30
    if not response_file_name in _instances:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable _instances does not seem to be defined.
Loading history...
31
32
        print("Creating singleton for %s" % response_file_name)
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after print.
Loading history...
33
34
        # Use the extension of the file to figure out which kind of response it is (ROOT or HDF)
35
36
        extension = os.path.splitext(response_file_name)[-1]
37
38
        if extension == ".root":
39
40
            new_instance = HAWCResponse.from_root_file(response_file_name)
41
42
        elif extension in ['.hd5', '.hdf5', '.hdf']:
43
44
            new_instance = HAWCResponse.from_hdf5(response_file_name)
45
46
        else:  # pragma: no cover
47
48
            raise NotImplementedError("Extension %s for response file %s not recognized." % (extension,
49
                                                                                             response_file_name))
50
51
        _instances[response_file_name] = new_instance
52
53
    # return the response, whether it was already in the cache or we just built it
54
55
    return _instances[response_file_name]  # type: HAWCResponse
56
57
58
class HAWCResponse(object):
0 ignored issues
show
Coding Style introduced by
This class should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
59
60
    def __init__(self, response_file_name, dec_bins, response_bins):
61
62
        self._response_file_name = response_file_name
63
        self._dec_bins = dec_bins
64
        self._response_bins = response_bins
65
66
    @classmethod
67
    def from_hdf5(cls, response_file_name):
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (31/15).
Loading history...
68
        """
69
        Build response from a HDF5 file. Do not use directly, use the hawc_response_factory function instead.
70
71
        :param response_file_name:
72
        :return: a HAWCResponse instance
73
        """
74
75
        response_bins = collections.OrderedDict()
76
77
        with Serialization(response_file_name, mode='r') as serializer:
78
79
            meta_dfs, _ = serializer.retrieve_pandas_object('/dec_bins_definition')
80
            effarea_dfs, _ = serializer.retrieve_pandas_object('/effective_area')
81
            psf_dfs, _ = serializer.retrieve_pandas_object('/psf')
82
83
        declination_centers = effarea_dfs.index.levels[0]
84
        energy_bins = effarea_dfs.index.levels[1]
85
86
        min_decs = []
87
        max_decs = []
88
89
        for dec_center in declination_centers:
90
91
            these_response_bins = collections.OrderedDict()
92
93
            for i, energy_bin in enumerate(energy_bins):
94
95
                these_meta = meta_dfs.loc[dec_center, energy_bin]
96
97
                min_dec = these_meta['min_dec']
98
                max_dec = these_meta['max_dec']
99
                dec_center_ = these_meta['declination_center']
100
101
                assert dec_center_ == dec_center, "Response is corrupted"
102
103
                # If this is the first energy bin, let's store the minimum and maximum dec of this bin
104
                if i == 0:
105
106
                    min_decs.append(min_dec)
107
                    max_decs.append(max_dec)
108
109
                else:
110
111
                    # Check that the minimum and maximum declination for this bin are the same as for
112
                    # the first energy bin
113
                    assert min_dec == min_decs[-1], "Response is corrupted"
114
                    assert max_dec == max_decs[-1], "Response is corrupted"
115
116
                sim_n_sig_events = these_meta['n_sim_signal_events']
117
                sim_n_bg_events = these_meta['n_sim_bkg_events']
118
119
                this_effarea_df = effarea_dfs.loc[dec_center, energy_bin]
120
121
                sim_energy_bin_low = this_effarea_df.loc[:, 'sim_energy_bin_low'].values
122
                sim_energy_bin_centers = this_effarea_df.loc[:, 'sim_energy_bin_centers'].values
123
                sim_energy_bin_hi = this_effarea_df.loc[:, 'sim_energy_bin_hi'].values
124
                sim_differential_photon_fluxes = this_effarea_df.loc[:, 'sim_differential_photon_fluxes'].values
125
                sim_signal_events_per_bin = this_effarea_df.loc[:, 'sim_signal_events_per_bin'].values
126
127
                this_psf = PSFWrapper.from_pandas(psf_dfs.loc[dec_center, energy_bin, :])
128
129
                this_response_bin = ResponseBin(energy_bin, min_dec, max_dec, dec_center,
130
                                                sim_n_sig_events, sim_n_bg_events,
131
                                                sim_energy_bin_low,
132
                                                sim_energy_bin_centers,
133
                                                sim_energy_bin_hi,
134
                                                sim_differential_photon_fluxes,
135
                                                sim_signal_events_per_bin,
136
                                                this_psf)
137
138
                these_response_bins[energy_bin] = this_response_bin
139
140
            # Store the response bins for this declination bin
141
142
            response_bins[dec_center] = these_response_bins
143
144
        dec_bins = zip(min_decs, declination_centers, max_decs)
145
146
        return cls(response_file_name, dec_bins, response_bins)
147
148
    @classmethod
149
    def from_root_file(cls, response_file_name):
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (25/15).
Loading history...
150
        """
151
        Build response from a ROOT file. Do not use directly, use the hawc_response_factory function instead.
152
153
        :param response_file_name:
154
        :return: a HAWCResponse instance
155
        """
156
157
        from ..root_handler import open_ROOT_file, get_list_of_keys, tree_to_ndarray
158
159
        # Make sure file is readable
160
161
        response_file_name = sanitize_filename(response_file_name)
162
163
        # Check that they exists and can be read
164
165
        if not file_existing_and_readable(response_file_name):  # pragma: no cover
166
167
            raise IOError("Response %s does not exist or is not readable" % response_file_name)
168
169
        # Read response
170
171
        with open_ROOT_file(response_file_name) as root_file:
172
173
            # Get the name of the trees
174
            object_names = get_list_of_keys(root_file)
175
176
            # Make sure we have all the things we need
177
178
            assert 'LogLogSpectrum' in object_names
179
            assert 'DecBins' in object_names
180
            assert 'AnalysisBins' in object_names
181
182
            # Read spectrum used during the simulation
183
            log_log_spectrum = root_file.Get("LogLogSpectrum")
184
185
            # Get the analysis bins definition
186
            dec_bins_ = tree_to_ndarray(root_file.Get("DecBins"))
187
188
            dec_bins_lower_edge = dec_bins_['lowerEdge']  # type: np.ndarray
189
            dec_bins_upper_edge = dec_bins_['upperEdge']  # type: np.ndarray
190
            dec_bins_center = dec_bins_['simdec']  # type: np.ndarray
191
192
            dec_bins = zip(dec_bins_lower_edge, dec_bins_center, dec_bins_upper_edge)
193
194
            # Read in the ids of the response bins ("analysis bins" in LiFF jargon)
195
            try:
196
197
                response_bins_ids = tree_to_ndarray(root_file.Get("AnalysisBins"), "name")  # type: np.ndarray
198
199
            except ValueError:
200
201
                try:
202
203
                    response_bins_ids = tree_to_ndarray(root_file.Get("AnalysisBins"), "id")  # type: np.ndarray
204
205
                except ValueError:
206
207
                    # Some old response files (or energy responses) have no "name" branch
208
                    custom_warnings.warn("Response %s has no AnalysisBins 'id' or 'name' branch. "
209
                                         "Will try with default names" % response_file_name)
210
211
                    response_bins_ids = None
212
213
            response_bins_ids = response_bins_ids.astype(str)
214
215
            # Now we create a dictionary of ResponseBin instances for each dec bin_name
216
            response_bins = collections.OrderedDict()
217
218
            for dec_id in range(len(dec_bins)):
0 ignored issues
show
unused-code introduced by
Consider using enumerate instead of iterating with range and len
Loading history...
219
220
                this_response_bins = collections.OrderedDict()
221
222
                min_dec, dec_center, max_dec = dec_bins[dec_id]
223
224
                # If we couldn't get the reponse_bins_ids above, let's use the default names
225
                if response_bins_ids is None:
226
227
                    # Default are just integers. let's read how many nHit bins are from the first dec bin
228
                    dec_id_label = "dec_%02i" % dec_id
229
230
                    n_energy_bins = root_file.Get(dec_id_label).GetNkeys()
231
232
                    response_bins_ids = range(n_energy_bins)
233
234
                for response_bin_id in response_bins_ids:
235
                    this_response_bin = ResponseBin.from_ttree(root_file, dec_id, response_bin_id, log_log_spectrum,
236
                                                               min_dec, dec_center, max_dec)
237
238
                    this_response_bins[response_bin_id] = this_response_bin
239
240
                response_bins[dec_bins[dec_id][1]] = this_response_bins
241
242
        # Now the file is closed. Let's explicitly remove f so we are sure it is freed
243
        del root_file
244
245
        # Instance the class and return it
246
        instance = cls(response_file_name, dec_bins, response_bins)
247
248
        return instance
249
250
    def get_response_dec_bin(self, dec, interpolate=False):
251
        """
252
        Get the responses for the provided declination bin, optionally interpolating the PSF
253
254
        :param dec: the declination where the response is desired at
255
        :param interpolate: whether to interpolate or not the PSF between the two closes response bins
256
        :return:
257
        """
258
259
        # Sort declination bins by distance to the provided declination
260
        dec_bins_keys = self._response_bins.keys()
261
        dec_bins_by_distance = sorted(dec_bins_keys, key=lambda x: abs(x - dec))
262
263
        if not interpolate:
264
265
            # Find the closest dec bin_name. We iterate over all the dec bins because we don't want to assume
266
            # that the bins are ordered by Dec in the file (and the operation is very cheap anyway,
267
            # since the dec bins are few)
268
269
            closest_dec_key = dec_bins_by_distance[0]
270
271
            return self._response_bins[closest_dec_key]
272
273
        else:
274
275
            # Find the two closest responses
276
            dec_bin_one, dec_bin_two = dec_bins_by_distance[:2]
277
278
            # Let's handle the special case where the requested dec is exactly on a response bin
279
            if abs(dec_bin_one - dec) < 0.01:
280
                # Do not interpolate
281
                return self._response_bins[dec_bin_one]
282
283
            energy_bins_one = self._response_bins[dec_bin_one]
284
            energy_bins_two = self._response_bins[dec_bin_two]
285
286
            # Now linearly interpolate between them
287
288
            # Compute the weights according to the distance to the source
289
            w1 = (dec - dec_bin_two) / (dec_bin_one - dec_bin_two)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "w1" doesn't conform to snake_case naming style ('(([a-z_][a-z0-9_]2,30)|(_[a-z0-9_]*)|(__[a-z][a-z0-9_]+__))$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
290
            w2 = (dec - dec_bin_one) / (dec_bin_two - dec_bin_one)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "w2" doesn't conform to snake_case naming style ('(([a-z_][a-z0-9_]2,30)|(_[a-z0-9_]*)|(__[a-z][a-z0-9_]+__))$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
291
292
            new_responses = collections.OrderedDict()
293
294
            for bin_id in energy_bins_one:
295
                this_new_response = energy_bins_one[bin_id].combine_with_weights(energy_bins_two[bin_id], dec, w1, w2)
296
297
                new_responses[bin_id] = this_new_response
298
299
            return new_responses
300
301
    @property
302
    def dec_bins(self):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
303
304
        return self._dec_bins
305
306
    @property
307
    def response_bins(self):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
308
309
        return self._response_bins
310
311
    @property
312
    def n_energy_planes(self):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
313
314
        return len(self._response_bins.values()[0])
315
316
    def display(self, verbose=False):
317
        """
318
        Prints summary of the current object content.
319
320
        :param verbose bool: Prints the full list of declinations and analysis bins.
321
        """
322
323
        print("Response file: %s" % self._response_file_name)
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after print.
Loading history...
324
        print("Number of dec bins: %s" % len(self._dec_bins))
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after print.
Loading history...
325
        if verbose:
326
            print self._dec_bins
327
        print("Number of energy/nHit planes per dec bin_name: %s" % (self.n_energy_planes))
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after print.
Loading history...
328
        if verbose:
329
            print self._response_bins.values()[0].keys()
330
331
    def write(self, filename):
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (19/15).
Loading history...
332
        """
333
        Write the response to HDF5.
334
335
        :param filename: output file. WARNING: it will be overwritten if existing.
336
        :return:
337
        """
338
339
        filename = sanitize_filename(filename)
340
341
        # Unravel the dec bins
342
        min_decs, center_decs, max_decs = zip(*self._dec_bins)
0 ignored issues
show
Unused Code introduced by
The variable max_decs seems to be unused.
Loading history...
Unused Code introduced by
The variable min_decs seems to be unused.
Loading history...
343
344
        # We get the definition of the response bins, as well as their coordinates (the dec center) and store them
345
        # in lists. Later on we will use these to make 3 dataframes containing all the needed data
346
        multi_index_keys = []
347
        effarea_dfs = []
348
        psf_dfs = []
349
        all_metas = []
350
351
        # Loop over all the dec bins (making sure that they are in order)
352
        for dec_center in sorted(center_decs):
353
354
            for bin_id in self._response_bins[dec_center]:
355
                response_bin = self._response_bins[dec_center][bin_id]
356
                this_effarea_df, this_meta, this_psf_df = response_bin.to_pandas()
357
358
                effarea_dfs.append(this_effarea_df)
359
                psf_dfs.append(this_psf_df)
360
                assert bin_id == response_bin.name, \
361
                    'Bin name inconsistency: {} != {}'.format(bin_id, response_bin.name)
362
                multi_index_keys.append((dec_center, response_bin.name))
363
                all_metas.append(pd.Series(this_meta))
364
365
        # Create the dataframe with all the effective areas (with a multi-index)
366
        effarea_df = pd.concat(effarea_dfs, axis=0, keys=multi_index_keys)
367
        psf_df = pd.concat(psf_dfs, axis=0, keys=multi_index_keys)
368
        meta_df = pd.concat(all_metas, axis=1, keys=multi_index_keys).T
369
370
        # Now write the 4 dataframes to file
371
        with Serialization(filename, mode='w') as serializer:
372
373
            serializer.store_pandas_object('/dec_bins_definition', meta_df)
374
            serializer.store_pandas_object('/effective_area', effarea_df)
375
            serializer.store_pandas_object('/psf', psf_df)
376