Commit e18c2a06cfe988b28605caef20621daba795dbaa

Authored by Iftach Sadeh
Committed by Jürgen Knödlseder
1 parent fd7ab6ac

add _sim_evt_excess

- add `_set_src_prefactor` function, which sets the test prefactor while checking that the limits are not violated
- add safety check as part of `_predict_flux`, to avoid division by zero
- add `_sim_evt_excess` function, which is used to increase the test source flux in case the optional conditions on the number of source counts are not met
cscripts/cssens.par
@@ -28,19 +28,22 @@ outfile, f, a, sensitivity.fits,,, "Output sensitivity FITS file" @@ -28,19 +28,22 @@ outfile, f, a, sensitivity.fits,,, "Output sensitivity FITS file"
28 # 28 #
29 # Script parameters 29 # Script parameters
30 #================== 30 #==================
31 -offset, r, h, 0.0,,, "Source offset angle (deg)"  
32 -duration, r, a, 180000.0,,, "Effective exposure time (s)"  
33 -rad, r, a, 5.0,,, "Radius of ROI (deg)"  
34 -emin, r, a, 0.03163,,, "Lower energy limit (TeV)"  
35 -emax, r, a, 125.8925,,, "Upper energy limit (TeV)"  
36 -bins, i, a, 18,,, "Number of energy bins for sensitivity computation"  
37 -enumbins, i, h, 0,,, "Number of energy bins for binned analysis (0=unbinned)"  
38 -npix, i, h, 200,,, "Number of pixels for binned analysis"  
39 -binsz, r, h, 0.05,,, "Pixel size for binned analysis (deg/pixel)"  
40 -type, s, h, Differential,Differential|Integral,, "Sensitivity type"  
41 -sigma, r, h, 5.0,,, "Significance threshold (Gaussian sigma)"  
42 -max_iter, i, h, 50,,, "Maximum number of iterations"  
43 -statistic, s, h, DEFAULT,DEFAULT|CSTAT|WSTAT|CHI2,, "Optimization statistic" 31 +offset, r, h, 0.0,,, "Source offset angle (deg)"
  32 +duration, r, a, 180000.0,,, "Effective exposure time (s)"
  33 +rad, r, a, 5.0,,, "Radius of ROI (deg)"
  34 +emin, r, a, 0.03163,,, "Lower energy limit (TeV)"
  35 +emax, r, a, 125.8925,,, "Upper energy limit (TeV)"
  36 +bins, i, a, 18,,, "Number of energy bins for sensitivity computation"
  37 +enumbins, i, h, 0,,, "Number of energy bins for binned analysis (0=unbinned)"
  38 +npix, i, h, 200,,, "Number of pixels for binned analysis"
  39 +binsz, r, h, 0.05,,, "Pixel size for binned analysis (deg/pixel)"
  40 +type, s, h, Differential,Differential|Integral,, "Sensitivity type"
  41 +sigma, r, h, 5.0,,, "Significance threshold (Gaussian sigma)"
  42 +max_iter, i, h, 50,,, "Maximum number of iterations"
  43 +statistic, s, h, DEFAULT,DEFAULT|CSTAT|WSTAT|CHI2,, "Optimization statistic"
  44 +min_src_events, i, h, -1,,, "minimal number of source counts"
  45 +non_source_uncert_frac, r, h, -1,,, "fraction of no-source counts serving as minimal threshold for source counts"
  46 +non_source_uncert_rad, r, h, 0.33,,, "radius for circular region in which to integrate no-source counts for threshold"
44 47
45 # 48 #
46 # Standard parameters 49 # Standard parameters
cscripts/cssens.py
@@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
20 # ========================================================================== 20 # ==========================================================================
21 import sys 21 import sys
22 import math 22 import math
  23 +import statistics
23 import gammalib 24 import gammalib
24 import ctools 25 import ctools
25 from cscripts import obsutils 26 from cscripts import obsutils
@@ -418,15 +419,7 @@ class cssens(ctools.csobservation): @@ -418,15 +419,7 @@ class cssens(ctools.csobservation):
418 # Write header for iteration into logger 419 # Write header for iteration into logger
419 self._log_header2(gammalib.EXPLICIT, 'Iteration '+str(iterations)) 420 self._log_header2(gammalib.EXPLICIT, 'Iteration '+str(iterations))
420 421
421 - # Create a copy of the test models, set the normalisation parameter  
422 - # of the test source in the models, and append the models to the  
423 - # observation. "crab_prefactor" is the prefactor that corresponds  
424 - # to a flux of 1 Crab.  
425 - models = test_model.copy()  
426 - prefactor = modutils.normalisation_parameter(models[self._srcname])  
427 - crab_prefactor = prefactor.value() * crab_unit  
428 - prefactor.value(crab_prefactor * test_crab_flux)  
429 - self.obs().models(models) 422 + crab_prefactor = self._set_src_prefactor(test_model, crab_unit, test_crab_flux)
430 423
431 # Simulate events for the models. "sim" holds an observation 424 # Simulate events for the models. "sim" holds an observation
432 # container with observations containing the simulated events. 425 # container with observations containing the simulated events.
@@ -560,6 +553,31 @@ class cssens(ctools.csobservation): @@ -560,6 +553,31 @@ class cssens(ctools.csobservation):
560 ' Test ended after %d iterations.' % max_iter) 553 ' Test ended after %d iterations.' % max_iter)
561 break 554 break
562 555
  556 + n_bck_evts = None
  557 + for iterations in range(max_iter):
  558 + pass_evt_cut, n_bck_evts = self._sim_evt_excess(
  559 + enumbins=enumbins,
  560 + binsz=binsz,
  561 + npix=npix,
  562 + n_bck_evts=n_bck_evts,
  563 + )
  564 + if pass_evt_cut:
  565 + break
  566 +
  567 + # increase the test flux if the cuts failed
  568 + correct = 1 + ratio_precision
  569 + crab_flux = correct * crab_flux
  570 + photon_flux = correct * photon_flux
  571 + energy_flux = correct * energy_flux
  572 + sensitivity = correct * sensitivity
  573 +
  574 + _ = self._set_src_prefactor(test_model, crab_unit, crab_flux)
  575 +
  576 + # Write results into logger
  577 + name = 'Iteration %d of source count check' % iterations
  578 + value = 'Fit=%9.4f mCrab Sens=%e erg/cm2/s' % (crab_flux*1000.0, sensitivity)
  579 + self._log_value(gammalib.TERSE, name, value)
  580 +
563 # Write fit results into logger 581 # Write fit results into logger
564 self._log_header3(gammalib.TERSE, 'Fit results') 582 self._log_header3(gammalib.TERSE, 'Fit results')
565 self._log_value(gammalib.TERSE, 'Photon flux', 583 self._log_value(gammalib.TERSE, 'Photon flux',
@@ -593,6 +611,181 @@ class cssens(ctools.csobservation): @@ -593,6 +611,181 @@ class cssens(ctools.csobservation):
593 # Return result 611 # Return result
594 return result 612 return result
595 613
  614 + #
  615 + def _set_src_prefactor(self, test_model, crab_unit, test_crab_flux):
  616 + """
  617 + Create a copy of the test models, set the normalisation parameter
  618 + of the test source in the models, and append the models to the observation.
  619 +
  620 + Parameters
  621 + ----------
  622 + test_model : `~gammalib.GModels`
  623 + Test source model
  624 + crab_unit : float
  625 + Crab unit factor
  626 + test_crab_flux : float
  627 + Test flux in Crab units (100 mCrab)
  628 +
  629 + Returns
  630 + -------
  631 + crab_prefactor : float
  632 + the prefactor that corresponds to a flux of 1 Crab.
  633 + """
  634 +
  635 + models = test_model.copy()
  636 + prefactor = modutils.normalisation_parameter(models[self._srcname])
  637 + crab_prefactor = prefactor.value() * crab_unit
  638 +
  639 + val_margin = 0.01
  640 + min_pref = prefactor.min()
  641 + max_pref = prefactor.max()
  642 +
  643 + val_now = crab_prefactor * test_crab_flux
  644 + val_now = max(val_now, min_pref * (1 + val_margin))
  645 + val_now = min(val_now, max_pref * (1 - val_margin))
  646 + crab_prefactor = val_now / test_crab_flux
  647 +
  648 + prefactor.value(val_now)
  649 +
  650 + self.obs().models(models)
  651 +
  652 + return crab_prefactor
  653 +
  654 + def _sim_evt_excess(self, enumbins, binsz, npix, n_bck_evts):
  655 + """
  656 + Return the number of excess events for the source model, compared
  657 + to all other models
  658 +
  659 + Parameters
  660 + ----------
  661 + enumbins : int
  662 + number of bins for the observation simulation
  663 + binsz : float
  664 + bin size for the observation simulation
  665 + npix : int
  666 + pixel size for the observation simulation
  667 + n_bck_evts : int
  668 + number of non-source counts (given to avoid computing the same quantity multiple times)
  669 +
  670 + Returns
  671 + -------
  672 + pass_evt_cut : bool
  673 + does the source pass the minimal excess criteria
  674 + n_bck_evts : int
  675 + number of non-source counts
  676 + """
  677 +
  678 + # number of times to simulate the observation
  679 + n_sims = 15
  680 +
  681 + # radius of integration for hte non-source background (not necessarily the
  682 + # same as self['rad'], to match the current standard for non-ctools analyses)
  683 + rad_deg = self['non_source_uncert_rad'].real()
  684 +
  685 + # minimal number of events for the source
  686 + min_src_events = self['min_src_events'].integer()
  687 + # threshold by fraction of non-source counts (5 times the assumed uncertainty
  688 + # on the background, which is taken as 1% of the non-source counts)
  689 + non_source_uncert_frac = self['non_source_uncert_frac'].real()
  690 +
  691 + # end here if no cuts have been spesified
  692 + if min_src_events <= 0 and non_source_uncert_frac <= 0:
  693 + return True
  694 +
  695 + models = self.obs().models()
  696 +
  697 + src_model = gammalib.GModels()
  698 + bck_models = gammalib.GModels()
  699 + for s in range(models.size()):
  700 + model = models[s]
  701 + if model.name() == self._srcname:
  702 + src_model.append(model)
  703 + else:
  704 + bck_models.append(model)
  705 +
  706 + src_obs = self.obs().copy()
  707 + src_obs.models(src_model)
  708 +
  709 + bck_obs = self.obs().copy()
  710 + bck_obs.models(bck_models)
  711 +
  712 + n_src_evts = 0
  713 + for is_src in [True, False]:
  714 + if not is_src:
  715 + # if the non-source fraction cut is not applied, no need
  716 + # to calculate the background rate.
  717 + # n_bck_evts will be None onlu in the firs iteration, where
  718 + # we only need to compute the non-source counts once, since the
  719 + # models dont change with the itterations.
  720 + if non_source_uncert_frac <= 0 or n_bck_evts is not None:
  721 + continue
  722 +
  723 + obs_now = src_obs if is_src else bck_obs
  724 +
  725 + n_sim_evts = []
  726 + for n_sim in range(n_sims):
  727 + seed = n_sim
  728 +
  729 + sim_now = obsutils.sim(
  730 + obs_now,
  731 + nbins=enumbins,
  732 + seed=seed,
  733 + binsz=binsz,
  734 + npix=npix,
  735 + log=self._log_clients,
  736 + debug=self['debug'].boolean(),
  737 + edisp=self['edisp'].boolean(),
  738 + nthreads=1
  739 + )
  740 +
  741 + # consider all events in the entire RoI for the minimal cource count cut
  742 + if is_src:
  743 + n_sim_evts += [len(sim_now[0].events())]
  744 + # consider only background within the thetaSqr cut for the systematic
  745 + # check, in accordance with the current CTA convention for Li&Ma calculations
  746 + else:
  747 + select = ctools.ctselect(sim_now)
  748 + select['rad'] = rad_deg
  749 + select['emin'] = 'INDEF'
  750 + select['tmin'] = 'INDEF'
  751 + select.run()
  752 +
  753 + obs = select.obs()
  754 + n_sim_evts += [len(obs[0].events())]
  755 +
  756 + if is_src:
  757 + n_src_evts = statistics.median(n_sim_evts)
  758 + else:
  759 + n_bck_evts = statistics.median(n_sim_evts)
  760 +
  761 + name = 'number of ' + ('source' if is_src else 'non-source') + ' events'
  762 + value = (
  763 + ','.join([str(i) for i in n_sim_evts])
  764 + + ' --> median is: ' + str(n_src_evts if is_src else n_bck_evts)
  765 + )
  766 + self._log_value(gammalib.TERSE, name, value)
  767 +
  768 + if n_bck_evts is None:
  769 + n_bck_evts = 0
  770 +
  771 + has_min_evts = n_src_evts >= min_src_events
  772 + is_above_bck = n_src_evts >= n_bck_evts * non_source_uncert_frac
  773 + pass_evt_cut = has_min_evts and is_above_bck
  774 +
  775 + name = 'minimal excess threshold'
  776 + value = (
  777 + 'has counts above \"min source counts('
  778 + + str(min_src_events) + ' events)\": ' + str(has_min_evts)
  779 + + ' ; pass background uncertainty threshold('
  780 + + str(max(non_source_uncert_frac, 0) * 100) + '% == '
  781 + + str(n_bck_evts * non_source_uncert_frac)
  782 + + ' events): ' + str(is_above_bck)
  783 + + ' --> pass selection: ' + str(pass_evt_cut)
  784 + )
  785 + self._log_value(gammalib.TERSE, name, value)
  786 +
  787 + return pass_evt_cut, n_bck_evts
  788 +
596 def _predict_flux(self, results, ts): 789 def _predict_flux(self, results, ts):
597 """ 790 """
598 Predict Crab flux for a given TS value 791 Predict Crab flux for a given TS value
@@ -635,9 +828,15 @@ class cssens(ctools.csobservation): @@ -635,9 +828,15 @@ class cssens(ctools.csobservation):
635 mean_xy *= norm 828 mean_xy *= norm
636 mean_xx *= norm 829 mean_xx *= norm
637 mean_yy *= norm 830 mean_yy *= norm
638 - rxy = (mean_xy - mean_x * mean_y) / \  
639 - math.sqrt((mean_xx - mean_x*mean_x) *  
640 - (mean_yy - mean_y*mean_y)) 831 +
  832 + rxy_norm = (mean_xx - mean_x * mean_x) * (mean_yy - mean_y * mean_y)
  833 + if rxy_norm < 1e-10:
  834 + rxy_norm = 1
  835 + else:
  836 + rxy_norm = math.sqrt(rxy_norm)
  837 +
  838 + rxy = (mean_xy - mean_x * mean_y) / rxy_norm
  839 +
641 regcoeff = rxy*rxy 840 regcoeff = rxy*rxy
642 841
643 # Compute regression line slope 842 # Compute regression line slope
@@ -884,4 +1083,4 @@ if __name__ == &#39;__main__&#39;: @@ -884,4 +1083,4 @@ if __name__ == &#39;__main__&#39;:
884 app = cssens(sys.argv) 1083 app = cssens(sys.argv)
885 1084
886 # Run application 1085 # Run application
887 - app.execute() 1086 - app.execute()
  1087 + app.execute()
888 \ No newline at end of file 1088 \ No newline at end of file