Skip to content

Commit c1f3ab9

Browse files
authored
Merge pull request #126 from fact-project/reweighting
Directly calc weights for given obstime
2 parents 91273b4 + 641d439 commit c1f3ab9

File tree

3 files changed

+172
-56
lines changed

3 files changed

+172
-56
lines changed

fact/VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.23.0
1+
0.24.0

fact/analysis/statistics.py

+170-54
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import numpy as np
22
import astropy.units as u
3+
from functools import partial
34

45
POINT_SOURCE_FLUX_UNIT = (1 / u.GeV / u.s / u.m**2).unit
56
FLUX_UNIT = POINT_SOURCE_FLUX_UNIT / u.sr
67

8+
PDG_COSMIC_RAY_FLUX = 1.8e4 * FLUX_UNIT
9+
PDG_COSMIC_RAY_E_REF = 1 * u.GeV
10+
PDG_COSMIC_RAY_INDEX = -2.7
11+
712

813
@u.quantity_input
914
def random_power(
1015
spectral_index,
1116
e_min: u.TeV,
1217
e_max: u.TeV,
1318
size,
14-
e_ref: u.TeV=1 * u.TeV,
19+
e_ref: u.TeV = 1 * u.TeV,
1520
) -> u.TeV:
1621
r'''
1722
Draw random numbers from a power law distribution
@@ -98,7 +103,7 @@ def power_law_exponential_cutoff(
98103
e_ref: Quantity[energy]
99104
The reference energy
100105
'''
101-
tau = np.exp(energy / e_cutoff)
106+
tau = np.exp(-energy / e_cutoff)
102107
return power_law(energy, flux_normalization, spectral_index, e_ref) * tau
103108

104109

@@ -175,63 +180,152 @@ def li_ma_significance(n_on, n_off, alpha=0.2):
175180

176181

177182
@u.quantity_input
178-
def calc_weight_power_to_logparabola(
183+
def calc_weights_powerlaw(
179184
energy: u.TeV,
185+
obstime: u.hour,
186+
n_events,
187+
e_min: u.TeV,
188+
e_max: u.TeV,
180189
simulated_index,
181-
target_a,
182-
target_b,
190+
scatter_radius: u.m,
191+
target_index,
192+
flux_normalization: (POINT_SOURCE_FLUX_UNIT, FLUX_UNIT),
183193
e_ref: u.TeV = 1 * u.TeV,
194+
sample_fraction=1,
195+
viewcone=None,
184196
):
185197
'''
186-
Reweight simulated events from a simulated power law with
187-
spectral index `spectral_index` to a curved power law with parameters `a` and `b`
198+
Calculate event weights, so that simulated
199+
events are reweighted to a physical power law flux
200+
'''
188201

189-
phi(E) = phi_0 (E / E_ref)^(a + b * log10(E / E_ref))
202+
phi_sim = calc_simulated_flux_normalization(
203+
obstime=obstime,
204+
n_events=n_events,
205+
e_min=e_min,
206+
e_max=e_max,
207+
e_ref=e_ref,
208+
simulated_index=simulated_index,
209+
scatter_radius=scatter_radius,
210+
viewcone=viewcone,
211+
)
190212

191-
Parameters
192-
----------
193-
energy: float or array-like
194-
Energy of the events
195-
simulated_index: float
196-
Spectral index of the simulated power law
197-
target_a: float
198-
Parameter `a` of the target curved power law
199-
target_b: float
200-
Parameter `b` of the target curved power law
213+
e = (energy / e_ref).to_value(u.dimensionless_unscaled)
214+
weights = flux_normalization / phi_sim * e**(target_index - simulated_index)
215+
return weights.to(u.dimensionless_unscaled) / sample_fraction
216+
217+
218+
@u.quantity_input
219+
def calc_weights_logparabola(
220+
energy: u.TeV,
221+
obstime: u.hour,
222+
n_events,
223+
e_min: u.TeV,
224+
e_max: u.TeV,
225+
simulated_index,
226+
scatter_radius: u.m,
227+
target_a,
228+
target_b,
229+
flux_normalization: (POINT_SOURCE_FLUX_UNIT, FLUX_UNIT),
230+
e_ref: u.TeV = 1 * u.TeV,
231+
sample_fraction=1,
232+
viewcone=None,
233+
):
201234
'''
202-
return (energy / e_ref) ** (
203-
target_a + target_b * np.log10(energy / e_ref) - simulated_index
235+
Calculate event weights, so that simulated
236+
events are reweighted to a physical power law flux
237+
'''
238+
239+
phi_sim = calc_simulated_flux_normalization(
240+
obstime=obstime,
241+
n_events=n_events,
242+
e_min=e_min,
243+
e_max=e_max,
244+
e_ref=e_ref,
245+
simulated_index=simulated_index,
246+
scatter_radius=scatter_radius,
247+
viewcone=viewcone,
204248
)
205249

250+
e = (energy / e_ref).to_value(u.dimensionless_unscaled)
251+
exp = target_a + np.log10(e) * target_b - simulated_index
252+
weights = flux_normalization / phi_sim * e**exp
253+
return weights.to(u.dimensionless_unscaled) / sample_fraction
254+
206255

207256
@u.quantity_input
208-
def calc_weight_change_index(
257+
def calc_weights_exponential_cutoff(
209258
energy: u.TeV,
259+
obstime: u.hour,
260+
n_events,
261+
e_min: u.TeV,
262+
e_max: u.TeV,
210263
simulated_index,
264+
scatter_radius: u.m,
211265
target_index,
266+
target_e_cutoff,
267+
flux_normalization: (POINT_SOURCE_FLUX_UNIT, FLUX_UNIT),
212268
e_ref: u.TeV = 1 * u.TeV,
269+
sample_fraction=1,
270+
viewcone=None,
213271
):
214272
'''
215-
Reweight simulated events from one power law index to another
273+
Calculate event weights, so that simulated
274+
events are reweighted to a physical power law flux with exponential cutoff
275+
'''
216276

217-
Parameters
218-
----------
219-
energy: float or array-like
220-
Energy of the events
221-
simulated_index: float
222-
Spectral index of the simulated power law
223-
target_index: float
224-
Spectral index of the target power law
277+
phi_sim = calc_simulated_flux_normalization(
278+
obstime=obstime,
279+
n_events=n_events,
280+
e_min=e_min,
281+
e_max=e_max,
282+
e_ref=e_ref,
283+
simulated_index=simulated_index,
284+
scatter_radius=scatter_radius,
285+
viewcone=viewcone,
286+
)
287+
288+
e = (energy / e_ref).to_value(u.dimensionless_unscaled)
289+
tau = np.exp(-energy / target_e_cutoff)
290+
weights = flux_normalization / phi_sim * e**(target_index - simulated_index) * tau
291+
return weights.to(u.dimensionless_unscaled) / sample_fraction
292+
293+
294+
def calc_simulated_flux_normalization(
295+
obstime: u.hour,
296+
n_events,
297+
e_min: u.TeV,
298+
e_max: u.TeV,
299+
simulated_index,
300+
scatter_radius: u.m,
301+
e_ref: u.TeV,
302+
viewcone=None,
303+
):
304+
'''
305+
Calculate the flux normalization for simulated events drawn
306+
from a power law for a certain observation time.
225307
'''
226-
return (energy / e_ref) ** (target_index - simulated_index)
308+
if viewcone is not None:
309+
solid_angle = 2 * np.pi * (1 - np.cos(viewcone)) * u.sr
310+
else:
311+
solid_angle = 1
312+
313+
A = np.pi * scatter_radius**2
314+
315+
delta = e_max**(simulated_index + 1) - e_min**(simulated_index + 1)
316+
317+
nom = (simulated_index + 1) * e_ref**simulated_index * n_events
318+
denom = (A * obstime * solid_angle) * delta
319+
320+
return nom / denom
227321

228322

229323
@u.quantity_input
230324
def calc_gamma_obstime(
231325
n_events,
232326
spectral_index,
233327
flux_normalization: (FLUX_UNIT, POINT_SOURCE_FLUX_UNIT),
234-
max_impact: u.m,
328+
scatter_radius: u.m,
235329
e_min: u.TeV,
236330
e_max: u.TeV,
237331
e_ref: u.TeV = 1 * u.TeV,
@@ -267,8 +361,8 @@ def calc_gamma_obstime(
267361
Spectral index of the simulated power law, including the sign,
268362
so typically -2.7 or -2
269363
flux_normalization: float
270-
Flux normalization of the simulated power law
271-
max_impact: Quantity[length]
364+
Flux normalization of the target power law
365+
scatter_radius: Quantity[length]
272366
Maximal simulated impact
273367
e_min: Quantity[energy]
274368
Mimimal simulated energy
@@ -280,16 +374,18 @@ def calc_gamma_obstime(
280374
if spectral_index >= -1:
281375
raise ValueError('spectral_index must be < -1')
282376

283-
numerator = n_events * (spectral_index + 1)
284-
285-
A = max_impact**2 * np.pi
286-
t1 = A * flux_normalization * e_ref
287-
t2 = (e_max / e_ref)**(spectral_index + 1)
288-
t3 = (e_min / e_ref)**(spectral_index + 1)
289-
290-
denominator = t1 * (t2 - t3)
377+
t_ref = 1 * u.hour
378+
phi_sim = calc_simulated_flux_normalization(
379+
obstime=t_ref,
380+
n_events=n_events,
381+
e_min=e_min,
382+
e_max=e_max,
383+
e_ref=e_ref,
384+
simulated_index=spectral_index,
385+
scatter_radius=scatter_radius,
386+
)
291387

292-
return numerator / denominator
388+
return (t_ref * phi_sim / flux_normalization).to(u.hour)
293389

294390

295391
@u.quantity_input
@@ -311,20 +407,20 @@ def power_law_integral(
311407
res = flux_normalization * e_ref / int_index * e_term
312408

313409
if flux_normalization.unit.is_equivalent(FLUX_UNIT):
314-
return res.to(1 / u.m**2 / u.s / u.sr)
315-
return res.to(1 / u.m**2 / u.s)
410+
return res.to(FLUX_UNIT)
411+
return res.to(POINT_SOURCE_FLUX_UNIT)
316412

317413

318414
@u.quantity_input
319415
def calc_proton_obstime(
320416
n_events,
321417
spectral_index,
322-
max_impact: u.m,
418+
scatter_radius: u.m,
323419
viewcone: u.deg,
324420
e_min: u.TeV,
325421
e_max: u.TeV,
326-
flux_normalization: FLUX_UNIT = 1.8e4 * FLUX_UNIT,
327-
e_ref: u.GeV=1 * u.GeV,
422+
flux_normalization: FLUX_UNIT = PDG_COSMIC_RAY_FLUX,
423+
e_ref: u.GeV = PDG_COSMIC_RAY_E_REF,
328424
) -> u.s:
329425
'''
330426
Calculate the equivalent observation time for a proton montecarlo set
@@ -335,7 +431,7 @@ def calc_proton_obstime(
335431
Number of simulated events
336432
spectral_index: float
337433
Spectral index of the simulated power law
338-
max_impact: float
434+
scatter_radius: float
339435
Maximal simulated impact in m
340436
viewcone: float
341437
Viewcone in degrees
@@ -348,11 +444,31 @@ def calc_proton_obstime(
348444
Default value is (29.2) of
349445
http://pdg.lbl.gov/2016/reviews/rpp2016-rev-cosmic-rays.pdf
350446
'''
351-
area = np.pi * max_impact**2
352-
solid_angle = 2 * np.pi * (1 - np.cos(viewcone)) * u.sr
447+
if spectral_index >= -1:
448+
raise ValueError('spectral_index must be < -1')
353449

354-
expected_integral_flux = power_law_integral(
355-
flux_normalization, spectral_index, e_min, e_max, e_ref
450+
t_ref = 1 * u.hour
451+
phi_sim = calc_simulated_flux_normalization(
452+
obstime=t_ref,
453+
n_events=n_events,
454+
e_min=e_min,
455+
e_max=e_max,
456+
e_ref=e_ref,
457+
simulated_index=spectral_index,
458+
scatter_radius=scatter_radius,
459+
viewcone=viewcone,
356460
)
357461

358-
return n_events / area / solid_angle / expected_integral_flux
462+
return (t_ref * phi_sim / flux_normalization).to(u.hour)
463+
464+
465+
calc_weights_cosmic_rays = partial(
466+
calc_weights_powerlaw,
467+
target_index=PDG_COSMIC_RAY_INDEX,
468+
flux_normalization=PDG_COSMIC_RAY_FLUX,
469+
e_ref=PDG_COSMIC_RAY_E_REF,
470+
)
471+
calc_weights_cosmic_rays.__doc__ = '''
472+
Calculate event weights, so that simulated
473+
events are reweighted to the PDG cosmic rays spectrum
474+
'''

tests/test_analysis.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def test_proton_obstime():
88
t = calc_proton_obstime(
99
n_events=n_simulated,
1010
spectral_index=-2.7,
11-
max_impact=400 * u.m,
11+
scatter_radius=400 * u.m,
1212
viewcone=5 * u.deg,
1313
e_min=100 * u.GeV,
1414
e_max=200 * u.TeV,

0 commit comments

Comments
 (0)