Skip to content

Commit 435a6a4

Browse files
authored
Use King function for Northern Tracks PSF (#444)
* Use astropy Tables for data everywhere * Load npz file if present * Improve cache coherence in NTSeason.simulate_background Cuts time in Injector.create_dataset() from ~1500 ms to ~600 ms * CircularGaussian: do not attempt to modify data in-place * Blacken/isorten following #415 * Use Table for EffectiveAreaInjector * Annotate factory functions * Avoid modifying columns in place * Use instance variables in NorthernTracksKDE * Normalize KDE PDF to region of interest * Speed up TableInjector by a factor ~2 when drawing ~175 events from 11.5e6 MC events across 1000 sources * Speed up StaticFloor numpy broadcasting is much faster than a list comprehension * Cache effective injection time for steady sources There's no point in calculating this tens of thousands of times, since it's independent of the source for a steady time pdf * Remove upper bound on n_s * Squeeze the last bits of fluff out of StdMatrixKDEEnabledLLH.get_spatially_coincident_indices Unexpected bottlenecks: - Repeatedly slicing into astropy Tables (overhead from copying units) - Temporary arrays in ra_dist and angular_distance * Evaluate SoB for different gammas lazily Keys appear as before, but values are only evaluated when accessed. Speeds up initialization when gamma is not being fitted. * Use less accurate (but much faster) angular distance calculation in NorthernTracksKDE * Annotate PDF factory functions * HACK: allow StdMatrixKDEEnabledLLH to use CircularGaussian pdf * Revert "Annotate PDF factory functions" This reverts commit 9afa53e13556e783a73909fa2751ed98bad35f27. * Normalize gaussian PDF * WIP: King function PSF * chore: annotate BaseInjector * Allow Season to simulate background only in the source box * Evaluate energy SoB lazily * Add log of King PDF * Add an option to disable energy S/B interpolation The 2nd-order spline interpolation does not necessarily reflect the normalization of the underyling signal or background energy PDFs * Use splines for King PDF parameters * Speed up make_season_weight 10x 180 ms -> 11 ms for 22k sources * Microoptimization: sparse matrix projection 25% faster * Microoptimization: slice result of background pdf calculation instead of inputs * mypy cleanup * Guard annotation-only imports * Cosmetic: raw string for latex * Return excluded events from create_dataset in unblinded injectors * Consider excluded events in flare likelihoods (but require ==0) * Handle unbounded n_s in 1D llh scan
1 parent 4b25a5f commit 435a6a4

File tree

19 files changed

+928
-406
lines changed

19 files changed

+928
-406
lines changed

flarestack/analyses/angular_error_floor/test_dynamic_pull_correction.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import matplotlib.pyplot as plt
77
import numpy as np
8-
from numpy.lib.recfunctions import append_fields
98

109
from flarestack.core.astro import angular_distance
1110
from flarestack.data.icecube.ps_tracks.ps_v002_p01 import IC86_1_dict
@@ -93,9 +92,7 @@ def weighted_quantile(values, quantiles, weight):
9392
cut_mc = mc[mask]
9493

9594
percentile = np.ones_like(cut_mc["ra"]) * np.nan
96-
cut_mc = append_fields(
97-
cut_mc, "percentile", percentile, usemask=False, dtypes=[np.float]
98-
)
95+
cut_mc.add_column(percentile, name="percentile")
9996

10097
weights = cut_mc["ow"] * cut_mc["trueE"] ** -gamma
10198
# weights = np.ones_like(cut_mc["ow"])

flarestack/analyses/ccsn/necker_2019/check_injections-compare_flarestack_injections.ipynb

Lines changed: 18 additions & 20 deletions
Large diffs are not rendered by default.

flarestack/core/angular_error_modifier.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import logging
33
import os
44
import pickle as Pickle
5+
from collections.abc import Callable, Mapping
6+
from typing import Any, Collection, Iterator, KeysView, TypeVar
57

68
import numexpr
79
import numpy as np
@@ -36,7 +38,7 @@
3638

3739

3840
class BaseFloorClass(object):
39-
subclasses: dict[str, object] = {}
41+
subclasses: dict[str, type["BaseFloorClass"]] = {}
4042

4143
def __init__(self, floor_dict):
4244
self.floor_dict = floor_dict
@@ -56,7 +58,7 @@ def decorator(subclass):
5658
return decorator
5759

5860
@classmethod
59-
def create(cls, floor_dict):
61+
def create(cls, floor_dict) -> "BaseFloorClass":
6062
floor_name = floor_dict["floor_name"]
6163

6264
if floor_name not in cls.subclasses:
@@ -129,7 +131,7 @@ def __init__(self, floor_dict):
129131
)
130132

131133
def floor(self, data):
132-
return np.array([self.min_error for _ in data])
134+
return self.min_error
133135

134136

135137
class BaseQuantileFloor(BaseFloorClass):
@@ -197,6 +199,30 @@ def create_function(self, pickled_array):
197199
return lambda data, params: func(data["logE"])
198200
"""
199201

202+
_K = TypeVar("_K")
203+
_V = TypeVar("_V")
204+
205+
206+
class LazyDict(Mapping[_K, _V]):
207+
def __init__(self, keys: Collection[_K], func: Callable[[_K], _V]):
208+
self._keys = frozenset(keys)
209+
self._func = func
210+
self._store: dict[_K, _V] = dict()
211+
212+
def keys(self) -> KeysView[_K]:
213+
return self._keys # type: ignore[return-value]
214+
215+
def __iter__(self) -> Iterator[_K]:
216+
return iter(self._keys)
217+
218+
def __len__(self):
219+
return len(self._keys)
220+
221+
def __getitem__(self, key: _K) -> _V:
222+
if key not in self._store:
223+
self._store[key] = self._func(key)
224+
return self._store[key]
225+
200226

201227
@BaseFloorClass.register_subclass("quantile_floor_1d_e")
202228
class QuantileFloor1D(BaseQuantileFloor, BaseDynamicFloorClass):
@@ -218,9 +244,9 @@ def create_function(self, pickled_array):
218244

219245

220246
class BaseAngularErrorModifier(object):
221-
subclasses: dict[str, object] = {}
247+
subclasses: dict[str, type["BaseAngularErrorModifier"]] = {}
222248

223-
def __init__(self, pull_dict):
249+
def __init__(self, pull_dict: dict[str, Any]) -> None:
224250
self.season = pull_dict["season"]
225251
self.floor = BaseFloorClass.create(pull_dict)
226252
self.pull_dict = pull_dict
@@ -252,7 +278,7 @@ def create(
252278
floor_name="static_floor",
253279
aem_name="no_modifier",
254280
**kwargs,
255-
):
281+
) -> "BaseAngularErrorModifier":
256282
pull_dict = dict()
257283
pull_dict["season"] = season
258284
pull_dict["e_pdf_dict"] = e_pdf_dict
@@ -278,15 +304,16 @@ def pull_correct_dynamic(self, data, params):
278304

279305
def create_spatial_cache(self, cut_data, SoB_pdf):
280306
if len(inspect.getfullargspec(SoB_pdf)[0]) == 2:
281-
SoB = dict()
282-
for gamma in get_gamma_support_points(precision=self.precision):
283-
SoB[gamma] = np.log(SoB_pdf(cut_data, gamma))
307+
SoB = LazyDict(
308+
get_gamma_support_points(precision=self.precision),
309+
lambda gamma: np.log(SoB_pdf(cut_data, gamma)),
310+
)
284311
else:
285312
SoB = SoB_pdf(cut_data)
286313
return SoB
287314

288315
def estimate_spatial(self, gamma, spatial_cache):
289-
if isinstance(spatial_cache, dict):
316+
if isinstance(spatial_cache, Mapping):
290317
return self.estimate_spatial_dynamic(gamma, spatial_cache)
291318
else:
292319
return spatial_cache

flarestack/core/astro.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Function taken from IceCube astro package.
33
"""
44

5+
import numexpr
56
import numpy as np
67

78

@@ -33,3 +34,27 @@ def angular_distance(lon1, lat1, lon2, lat2):
3334
cd = np.cos(lon2 - lon1)
3435

3536
return np.arctan2(np.hypot(c2 * sd, c1 * s2 - s1 * c2 * cd), s1 * s2 + c1 * c2 * cd)
37+
38+
39+
# Fast angular distance using numexpr to avoid creating large temporary arrays
40+
fast_angular_distance = numexpr.NumExpr(
41+
"arccos(sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(fmod(lon2 - lon1 + pi, 2 * pi) - pi))",
42+
signature=[
43+
("lon1", np.float64),
44+
("lat1", np.float64),
45+
("lon2", np.float64),
46+
("lat2", np.float64),
47+
("pi", np.float64),
48+
],
49+
)
50+
51+
# NB: numexpr implements fmod but not mod, and mod is equivalent to abs(fmod(...))
52+
in_ra_window = numexpr.NumExpr(
53+
"abs(abs(fmod(lon1 - lon2 + pi, 2 * pi)) - pi) < dPhi",
54+
signature=[
55+
("lon1", np.float64),
56+
("lon2", np.float64),
57+
("pi", np.float64),
58+
("dPhi", np.float64),
59+
],
60+
)

flarestack/core/energy_pdf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class EnergyPDF(object):
6565
A base class for Energy PDFs.
6666
"""
6767

68-
subclasses: dict[str, object] = {}
68+
subclasses: "dict[str, type[EnergyPDF]]" = {}
6969

7070
def __init__(self, e_pdf_dict):
7171
"""
@@ -103,7 +103,7 @@ def decorator(subclass):
103103
return decorator
104104

105105
@classmethod
106-
def create(cls, e_pdf_dict):
106+
def create(cls, e_pdf_dict) -> "EnergyPDF":
107107
e_pdf_dict = read_e_pdf_dict(e_pdf_dict)
108108

109109
e_pdf_name = e_pdf_dict["energy_pdf_name"]

0 commit comments

Comments
 (0)