ctools 2.1.0.dev
Loading...
Searching...
No Matches
obsutils.py
Go to the documentation of this file.
1# ==========================================================================
2# Utility functions for observation handling
3#
4# Copyright (C) 2011-2020 Juergen Knoedlseder
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19# ==========================================================================
20import math
21import gammalib
22import ctools
23import cscripts
24
25
26# ===================== #
27# Simulate observations #
28# ===================== #
29def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0,
30 emin=None, emax=None, nbins=0, onsrc=None, onrad=0.2, addbounds=False,
31 binsz=0.05, npix=200, proj='TAN', coord='GAL', nthreads=0):
32 """
33 Simulate events for all observations in the container
34
35 Simulate events for all observations using ctobssim. If the number of
36 energy bins is positive, the events are binned into a counts cube using
37 ctbin. If multiple observations are simulated, the counts cube is a
38 stacked cube and the corresponding response cubes are computed using
39 ctexpcube, ctpsfcube, ctbkgcube and optionally ctedispcube. The response
40 cubes are attached to the first observation in the container, which
41 normally is the observation with the counts cube.
42
43 Parameters
44 ----------
45 obs : `~gammalib.GObservations`
46 Observation container without events
47 log : bool, optional
48 Create log file(s)
49 debug : bool, optional
50 Create console dump?
51 chatter : int, optional
52 Chatter level
53 edisp : bool, optional
54 Apply energy dispersion?
55 seed : int, optional
56 Seed value for simulations
57 emin : float, optional
58 Minimum energy of counts cube for binned (TeV)
59 emax : float, optional
60 Maximum energy of counts cube for binned (TeV)
61 nbins : int, optional
62 Number of energy bins (0=unbinned)
63 onsrc : str, optional
64 Name of source for On region (None if no On/Off obs. is used)
65 onrad : float, optional
66 Radius for On region (deg)
67 addbounds : bool, optional
68 Add boundaries at observation energies
69 binsz : float, optional
70 Pixel size for binned simulation (deg/pixel)
71 npix : int, optional
72 Number of pixels in X and Y for binned simulation
73 proj : str, optional
74 Projection for binned simulation
75 coord : str, optional
76 Coordinate system for binned simulation
77 nthreads : str, optional
78 Number of parallel processes for On/Off spectra computation (0=all available CPUs)
79
80 Returns
81 -------
82 obs : `~gammalib.GObservations`
83 Observation container filled with simulated events
84 """
85 # Allocate ctobssim application and set parameters
86 obssim = ctools.ctobssim(obs)
87 obssim['seed'] = seed
88 obssim['edisp'] = edisp
89 obssim['nthreads'] = nthreads
90 obssim['chatter'] = chatter
91 obssim['debug'] = debug
92
93 # Optionally open the log file
94 if log:
95 obssim.logFileOpen()
96
97 # Run ctobssim application. This will loop over all observations in the
98 # container and simulation the events for each observation. Note that
99 # events are not added together, they still apply to each observation
100 # separately.
101 obssim.run()
102
103 # Binned option?
104 if nbins > 0:
105
106 # If energy boundaries are not given then determine the minimum and
107 # the maximum energies from all observations and use these values
108 # as energy boundaries. The energy boundaries are given in TeV.
109 if emin == None or emax == None:
110 emin = 1.0e30
111 emax = 0.0
112 for run in obssim.obs():
113 emin = min(run.events().ebounds().emin().TeV(), emin)
114 emax = max(run.events().ebounds().emax().TeV(), emax)
115
116 # If a On source is specified then create On/Off observations
117 if onsrc != None:
118
119 # Extract source position from model
120 model = obssim.obs().models()[onsrc]
121 ra = model['RA'].value()
122 dec = model['DEC'].value()
123
124 # Allocate csphagen application and set parameters
125 phagen = cscripts.csphagen(obssim.obs())
126 phagen['inmodel'] = 'NONE'
127 phagen['srcname'] = onsrc
128 phagen['ebinalg'] = 'LOG'
129 phagen['emin'] = emin
130 phagen['emax'] = emax
131 phagen['enumbins'] = nbins
132 phagen['srcshape'] = 'CIRCLE'
133 phagen['coordsys'] = 'CEL'
134 phagen['ra'] = ra
135 phagen['dec'] = dec
136 phagen['rad'] = onrad
137 phagen['stack'] = False
138 phagen['inexclusion'] = 'NONE'
139 phagen['bkgmethod'] = 'REFLECTED'
140 phagen['nthreads'] = nthreads
141
142 # Optionally open the log file
143 if log:
144 phagen.logFileOpen()
145
146 # Run csphagen application
147 phagen.run()
148
149 # Make a deep copy of the observation that will be returned
150 # (the csphagen object will go out of scope one the function is
151 # left)
152 obs = phagen.obs().copy()
153
154 # ... otherwise use binned observations
155 else:
156
157 # Allocate ctbin application and set parameters
158 binning = ctools.ctbin(obssim.obs())
159 binning['ebinalg'] = 'LOG'
160 binning['emin'] = emin
161 binning['emax'] = emax
162 binning['enumbins'] = nbins
163 binning['usepnt'] = True # Use pointing for map centre
164 binning['nxpix'] = npix
165 binning['nypix'] = npix
166 binning['binsz'] = binsz
167 binning['coordsys'] = coord
168 binning['proj'] = proj
169 binning['nthreads'] = nthreads
170 binning['chatter'] = chatter
171 binning['debug'] = debug
172
173 # Optionally open the log file
174 if log:
175 binning.logFileOpen()
176
177 # Run ctbin application. This will loop over all observations in
178 # the container and bin the events in counts maps
179 binning.run()
180
181 # If we have multiple input observations then create stacked response
182 # cubes and append them to the observation
183 if len(obssim.obs()) > 1:
184
185 # Get counts cube. The counts cube is needed to obtained a
186 # proper background cube.
187 cntcube = binning.cube()
188
189 # Get stacked response
190 response = get_stacked_response(obssim.obs(), cntcube,
191 edisp=edisp,
192 addbounds=addbounds,
193 log=log, debug=debug,
194 chatter=chatter)
195
196 # Set stacked response
197 if edisp:
198 binning.obs()[0].response(response['expcube'],
199 response['psfcube'],
200 response['edispcube'],
201 response['bkgcube'])
202 else:
203 binning.obs()[0].response(response['expcube'],
204 response['psfcube'],
205 response['bkgcube'])
206
207 # Set new models
208 binning.obs().models(response['models'])
209
210 # Make a deep copy of the observation that will be returned
211 # (the ctbin object will go out of scope one the function is
212 # left)
213 obs = binning.obs().copy()
214
215 else:
216
217 # Make a deep copy of the observation that will be returned
218 # (the ctobssim object will go out of scope one the function is
219 # left)
220 obs = obssim.obs().copy()
221
222 # Delete the simulation
223 del obssim
224
225 # Return observation container
226 return obs
227
228
229# ======================= #
230# Set one CTA observation #
231# ======================= #
232def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.98, \
233 emin=0.1, emax=100.0, rad=5.0, \
234 instrument='CTA', irf='South_50h', caldb='prod2', \
235 obsid='000000', mjdref=51544.5):
236 """
237 Set a single CTA observation
238
239 The function sets a single CTA observation containing an empty CTA
240 event list. By looping over this function CTA observations can be
241 added to the observation container.
242
243 Parameters
244 ----------
245 pntdir : `~gammalib.GSkyDir`
246 Pointing direction
247 tstart : float, optional
248 Start time (s)
249 duration : float, optional
250 Duration of observation (s)
251 deadc : float, optional
252 Deadtime correction factor
253 emin : float, optional
254 Minimum event energy (TeV)
255 emax : float, optional
256 Maximum event energy (TeV)
257 rad : float, optional
258 ROI radius used for analysis (deg)
259 instrument : str, optional
260 Name of Cherenkov Telescope
261 irf : str, optional
262 Instrument response function
263 caldb : str, optional
264 Calibration database path
265 obsid : str, optional
266 Observation identifier
267
268 Returns
269 -------
270 obs : `~gammalib.GCTAObservation`
271 CTA observation
272 """
273 # Allocate CTA observation
274 obs = gammalib.GCTAObservation()
275
276 # Set mission
277 mission = gammalib.toupper(instrument)
278 obs.instrument(mission)
279
280 # Set CTA calibration database
281 db = gammalib.GCaldb()
282 if (gammalib.dir_exists(caldb)):
283 db.rootdir(caldb)
284 else:
285 db.open(mission, caldb)
286
287 # Set pointing direction for CTA observation
288 pnt = gammalib.GCTAPointing()
289 pnt.dir(pntdir)
290 obs.pointing(pnt)
291
292 # Set ROI
293 roi = gammalib.GCTARoi()
294 instdir = gammalib.GCTAInstDir()
295 instdir.dir(pntdir)
296 roi.centre(instdir)
297 roi.radius(rad)
298
299 # Set GTI
300 ref = gammalib.GTimeReference(mjdref,'s','TT','LOCAL')
301 gti = gammalib.GGti(ref)
302 gti.append(gammalib.GTime(tstart, ref),
303 gammalib.GTime(tstart+duration, ref))
304
305 # Set energy boundaries
306 ebounds = gammalib.GEbounds(gammalib.GEnergy(emin, 'TeV'),
307 gammalib.GEnergy(emax, 'TeV'))
308
309 # Allocate event list
310 events = gammalib.GCTAEventList()
311
312 # Set ROI, GTI and energy boundaries for event list
313 events.roi(roi)
314 events.gti(gti)
315 events.ebounds(ebounds)
316
317 # Set the event list as the events for CTA observation
318 obs.events(events)
319
320 # Set instrument response for CTA observation
321 obs.response(irf, db)
322
323 # Set ontime, livetime, and deadtime correction factor for CTA observation
324 obs.ontime(duration)
325 obs.livetime(duration*deadc)
326 obs.deadc(deadc)
327 obs.id(obsid)
328
329 # Return CTA observation
330 return obs
331
332
333# ============================ #
334# Set list of CTA observations #
335# ============================ #
336def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.98, \
337 emin=0.1, emax=100.0, rad=5.0, \
338 irf='South_50h', caldb='prod2'):
339 """
340 Returns an observation container filled with a list of CTA observations
341
342 The list is defined by the obsdeflist parameter which is a dictionnary
343 containing the mandatory keywords 'ra' and 'dec' that specify the
344 pointing direction for a given observation. Optional keyword give control
345 over other observation proposerties, such as duration, deadtime correction,
346 energy range, etc. If an optional keyword is not specified, the function
347 keyword is used instead.
348
349 Parameters
350 ----------
351 obsdeflist : list of dict
352 Observation definition list
353 tstart : float, optional
354 Start time (s)
355 duration : float, optional
356 Duration of observation (s)
357 deadc : float, optional
358 Deadtime correction factor
359 emin : float, optional
360 Minimum event energy (TeV)
361 emax : float, optional
362 Maximum event energy (TeV)
363 rad : float, optional
364 ROI radius used for analysis (deg)
365 irf : str, optional
366 Instrument response function
367 caldb : str, optional
368 Calibration database path
369
370 Returns
371 -------
372 obs : `~gammalib.GObservations`
373 Observation container filled with CTA observation
374 """
375 # Initialise empty observation container
376 obs = gammalib.GObservations()
377
378 # Initialise first time and identifier
379 obs_start = tstart
380 obs_id = 1
381
382 # Loop over observation definition list
383 for obsdef in obsdeflist:
384
385 # Set pointing direction
386 pntdir = gammalib.GSkyDir()
387 pntdir.radec_deg(obsdef['ra'], obsdef['dec'])
388
389 # Generate identifier string
390 obsid = '%6.6d' % obs_id
391
392 # Set one CTA observation
393 obs_cta = set_obs(pntdir,
394 tstart=obs_start,
395 duration=obsdef.setdefault('duration', duration),
396 deadc=obsdef.setdefault('deadc', deadc),
397 emin=obsdef.setdefault('emin', emin),
398 emax=obsdef.setdefault('emax', emax),
399 rad=obsdef.setdefault('rad', rad),
400 irf=obsdef.setdefault('irf', irf),
401 caldb=obsdef.setdefault('caldb', caldb),
402 obsid=obsid)
403
404 # Append to container
405 obs.append(obs_cta)
406
407 # Update start time and identifier
408 obs_start += duration
409 obs_id += 1
410
411 # Return observation container
412 return obs
413
414
415# ============================ #
416# Set CTA observation patterns #
417# ============================ #
418def set_obs_patterns(pattern, ra=83.6331, dec=22.0145, offset=1.5):
419 """
420 Sets a number of standard patterns
421
422 Parameters
423 ----------
424 pattern : str
425 Observation pattern ("single", "four")
426 ra : float, optional
427 Right Ascension of pattern centre (deg)
428 dec : float, optional
429 Declination of pattern centre (deg)
430 offset : float, optional
431 Offset from pattern centre (deg)
432
433 Returns
434 -------
435 obsdeflist : list
436 Observation definition list
437 """
438 # Initialise observation definition list
439 obsdeflist = []
440
441 # If the pattern is a single observation then append the Right Ascension
442 # and Declination to the observation definition list
443 if pattern == 'single':
444 obsdef = {'ra': ra, 'dec': dec}
445 obsdeflist.append(obsdef)
446
447 # ... otherwise, if the pattern is four observations then append four
448 # observations offset by a certain amount from the pattern centre to the
449 # observation definition list
450 elif pattern == 'four':
451
452 # Set pattern centre
453 centre = gammalib.GSkyDir()
454 centre.radec_deg(ra, dec)
455
456 # Append pointings
457 for phi in [0.0, 90.0, 180.0, 270.0]:
458 pntdir = centre.copy()
459 pntdir.rotate_deg(phi, offset)
460 obsdeflist.append({'ra': pntdir.ra_deg(), 'dec': pntdir.dec_deg()})
461
462 # ... otherwise raise an exception since we have an unknown pattern
463 else:
464 msg = 'Observation pattern "%s" not recognized.' % (pattern)
465 raise RuntimeError(msg)
466
467 # Return observation definition list
468 return obsdeflist
469
470
471# ====================================================== #
472# Set observation container filled with CTA observations #
473# ====================================================== #
474def set_observations(ra, dec, rad, tstart, duration, emin, emax, irf, caldb,
475 deadc=0.98, pattern='single', offset=1.5):
476 """
477 Set an observation container filled with CTA observations
478
479 Parameters
480 ----------
481 ra : float
482 Right Ascension of pattern centre (deg)
483 dec : float
484 Declination of pattern centre (deg)
485 rad : float
486 ROI radius used for analysis (deg)
487 tstart : float
488 Start time of observation (s)
489 duration : float
490 Duration of each observation (s)
491 emin : float, optional
492 Minimum event energy (TeV)
493 emax : float, optional
494 Maximum event energy (TeV)
495 irf : str
496 Instrument response function
497 caldb : str
498 Calibration database path
499 deadc : float, optional
500 Deadtime correction factor
501 pattern : str, optional
502 Observation pattern ("single", "four")
503 offset : float, optional
504 Offset from pattern centre (deg)
505
506 Returns
507 -------
508 obs : `~gammalib.GObservations()
509 Observation container
510 """
511 # Setup observation definition list
512 obsdeflist = set_obs_patterns(pattern, offset=offset, ra=ra, dec=dec)
513
514 # Create list of observations
515 obs = set_obs_list(obsdeflist, tstart=tstart, duration=duration,
516 emin=emin, emax=emax, rad=rad, irf=irf, caldb=caldb,
517 deadc=deadc)
518
519 # Return observation container
520 return obs
521
522
523# ==================== #
524# Get stacked response #
525# ==================== #
526def get_stacked_response(obs, cntcube, edisp=False, addbounds=False,
527 log=False, debug=False, chatter=2):
528 """
529 Get stacked response cubes
530
531 The number of energies bins are set to at least 30 bins per decade, unless
532 the counts cube has more energy bins per decade.
533
534 Parameters
535 ----------
536 obs : `~gammalib.GObservations`
537 Observation container
538 cntcube : `~gammalib.GCTAEventCube`
539 Counts cube
540 edisp : bool, optional
541 Apply energy dispersion?
542 addbounds : bool, optional
543 Add boundaries at observation energies
544 log : bool, optional
545 Create log file(s)
546 debug : bool, optional
547 Create console dump?
548 chatter : int, optional
549 Chatter level
550
551 Returns
552 -------
553 result : dict
554 Dictionary of response cubes
555 """
556 # Derive binning parameters from counts cube
557 xref = cntcube.counts().projection().crval(0)
558 yref = cntcube.counts().projection().crval(1)
559 binsz = cntcube.counts().projection().cdelt(1)
560 coordsys = cntcube.counts().projection().coordsys()
561 proj = cntcube.counts().projection().code()
562 nxpix = cntcube.nx()
563 nypix = cntcube.ny()
564 emin = cntcube.emin().TeV()
565 emax = cntcube.emax().TeV()
566 ebins = cntcube.ebins()
567
568 # Translate coordinate system
569 if coordsys == 'EQU':
570 coordsys = 'CEL'
571
572 # Set energy limits, with larger etrue limits for the case that energy
573 # dispersion is requested
574 if edisp:
575 emin *= 0.5
576 emax *= 1.5
577
578 # Set number of energy bins to at least 30 per energy decade
579 enumbins = int((math.log10(emax) - math.log10(emin)) * 30.0)
580 if ebins > enumbins:
581 enumbins = ebins
582
583 # Compute spatial binning for point spread function and energy dispersion
584 # cubes. The spatial binning is 10 times coarser than the spatial binning
585 # of the exposure and background cubes. At least 2 spatial are required.
586 psf_binsz = 10.0 * binsz
587 psf_nxpix = max(nxpix // 10, 2) # Make sure result is int
588 psf_nypix = max(nypix // 10, 2) # Make sure result is int
589
590 # Create exposure cube
591 expcube = ctools.ctexpcube(obs)
592 expcube['incube'] = 'NONE'
593 expcube['ebinalg'] = 'LOG'
594 expcube['xref'] = xref
595 expcube['yref'] = yref
596 expcube['binsz'] = binsz
597 expcube['nxpix'] = nxpix
598 expcube['nypix'] = nypix
599 expcube['enumbins'] = enumbins
600 expcube['emin'] = emin
601 expcube['emax'] = emax
602 expcube['coordsys'] = coordsys
603 expcube['proj'] = proj
604 expcube['addbounds'] = addbounds
605 expcube['debug'] = debug
606 expcube['chatter'] = chatter
607 if log:
608 expcube.logFileOpen()
609 expcube.run()
610
611 # Create point spread function cube
612 psfcube = ctools.ctpsfcube(obs)
613 psfcube['incube'] = 'NONE'
614 psfcube['ebinalg'] = 'LOG'
615 psfcube['xref'] = xref
616 psfcube['yref'] = yref
617 psfcube['binsz'] = psf_binsz
618 psfcube['nxpix'] = psf_nxpix
619 psfcube['nypix'] = psf_nypix
620 psfcube['enumbins'] = enumbins
621 psfcube['emin'] = emin
622 psfcube['emax'] = emax
623 psfcube['coordsys'] = coordsys
624 psfcube['proj'] = proj
625 psfcube['addbounds'] = addbounds
626 psfcube['debug'] = debug
627 psfcube['chatter'] = chatter
628 if log:
629 psfcube.logFileOpen()
630 psfcube.run()
631
632 # Create background cube. Note that we use the same energy binning as
633 # for the counts cube since no interpolation should be actually done
634 # for the background cube.
635 bkgcube = ctools.ctbkgcube(obs)
636 bkgcube.cntcube(cntcube)
637 bkgcube['debug'] = debug
638 bkgcube['chatter'] = chatter
639 if log:
640 bkgcube.logFileOpen()
641 bkgcube.run()
642
643 # If energy dispersion is requested then create energy dispersion cube
644 if edisp:
645 edispcube = ctools.ctedispcube(obs)
646 edispcube['incube'] = 'NONE'
647 edispcube['ebinalg'] = 'LOG'
648 edispcube['xref'] = xref
649 edispcube['yref'] = yref
650 edispcube['binsz'] = psf_binsz
651 edispcube['nxpix'] = psf_nxpix
652 edispcube['nypix'] = psf_nypix
653 edispcube['enumbins'] = enumbins
654 edispcube['emin'] = emin
655 edispcube['emax'] = emax
656 edispcube['coordsys'] = coordsys
657 edispcube['proj'] = proj
658 edispcube['addbounds'] = addbounds
659 edispcube['debug'] = debug
660 edispcube['chatter'] = chatter
661 if log:
662 edispcube.logFileOpen()
663 edispcube.run()
664
665 # Build response dictionary
666 response = {}
667 response['expcube'] = expcube.expcube().copy()
668 response['psfcube'] = psfcube.psfcube().copy()
669 response['bkgcube'] = bkgcube.bkgcube().copy()
670 response['models'] = bkgcube.models().copy()
671 if edisp:
672 response['edispcube'] = edispcube.edispcube().copy()
673
674 # Return response cubes
675 return response
676
677
678# ================================= #
679# Get stacked observation container #
680# ================================= #
681def get_stacked_obs(cls, obs, nthreads=0):
682 """
683 Bin an observation and return an observation container with a single
684 binned observation
685
686 Parameters
687 ----------
688 cls : `~ctools.cscript`
689 cscript class
690 obs : `~gammalib.GObservations`
691 Observation container
692 nthreads : str, optional
693 Number of parallel processes for On/Off spectra computation (0=all available CPUs)
694
695 Returns
696 -------
697 obs : `~gammalib.GObservations`
698 Observation container where the first observation is a binned observation
699 """
700 # Write header
701 if cls._logExplicit():
702 cls._log.header3('Binning events')
703
704 # Bin events
705 cntcube = ctools.ctbin(obs)
706 cntcube['usepnt'] = False
707 cntcube['ebinalg'] = 'LOG'
708 cntcube['xref'] = cls['xref'].real()
709 cntcube['yref'] = cls['yref'].real()
710 cntcube['binsz'] = cls['binsz'].real()
711 cntcube['nxpix'] = cls['nxpix'].integer()
712 cntcube['nypix'] = cls['nypix'].integer()
713 cntcube['enumbins'] = cls['enumbins'].integer()
714 cntcube['emin'] = cls['emin'].real()
715 cntcube['emax'] = cls['emax'].real()
716 cntcube['coordsys'] = cls['coordsys'].string()
717 cntcube['proj'] = cls['proj'].string()
718 cntcube['nthreads'] = nthreads
719 cntcube.run()
720
721 # Store counts cube so that we can use it to build the background
722 # cube
723 cube = cntcube.cube()
724
725 # Write header
726 if cls._logExplicit():
727 cls._log.header3('Creating stacked response')
728
729 # Get stacked response
730 response = get_stacked_response(obs, cube, edisp=cls['edisp'].boolean())
731
732 # Retrieve a new oberservation container
733 new_obs = cntcube.obs().copy()
734
735 # Set stacked response
736 if cls['edisp'].boolean():
737 new_obs[0].response(response['expcube'], response['psfcube'],
738 response['edispcube'], response['bkgcube'])
739 else:
740 new_obs[0].response(response['expcube'], response['psfcube'],
741 response['bkgcube'])
742
743 # Get new models
744 models = response['models']
745
746 # Set models for new oberservation container
747 new_obs.models(models)
748
749 # Return new oberservation container
750 return new_obs
751
752
753# ================================ #
754# Get On/Off observation container #
755# ================================ #
756def get_onoff_obs(cls, obs, nthreads=0, ra = None, dec = None, srcname = ''):
757 """
758 Create On/Off observations container from given observations
759
760 Parameters
761 ----------
762 cls : `~ctools.cscript`
763 cscript class
764 obs : `~gammalib.GObservations`
765 Observation container
766 nthreads : str, optional
767 Number of parallel processes for On/Off spectra computation (0=all available CPUs)
768 ra : float, optional
769 R.A. of source region centre
770 dec : float, optional
771 Dec. of source region centre
772 srcname : str, optional
773 Source name
774
775 Returns
776 -------
777 onoff_obs : `~gammalib.GObservations`
778 Observation container with On/Off observations
779 """
780 # Write header
781 if cls._logExplicit():
782 cls._log.header3('Creating On/Off observations')
783
784 # Initialise inmodel and use_model_bkg
785 inmodel = 'NONE'
786 use_model_bkg = True
787
788 # Set inmodel, srcname, and use_model_bkg if they are available
789 if cls.has_par('inmodel') and obs.models().size() == 0:
790 if cls['inmodel'].is_valid():
791 inmodel = cls['inmodel'].value()
792 if srcname == '' and cls.has_par('srcname'):
793 srcname = cls['srcname'].value()
794 if cls.has_par('use_model_bkg'):
795 use_model_bkg = cls['use_model_bkg'].boolean()
796
797 # Create On/Off observations
798 phagen = cscripts.csphagen(obs)
799 phagen['inmodel'] = inmodel
800 phagen['srcname'] = srcname
801 phagen['emin'] = cls['emin'].real()
802 phagen['emax'] = cls['emax'].real()
803 phagen['enumbins'] = cls['enumbins'].integer()
804 phagen['ebinalg'] = 'LOG'
805 phagen['srcshape'] = cls['srcshape'].string()
806 # User has specified custom centre for the source region
807 if ra != None and dec != None:
808 phagen['coordsys'] = 'CEL'
809 phagen['ra'] = ra
810 phagen['dec'] = dec
811 # Otherwise use default in class
812 else:
813 phagen['coordsys'] = cls['coordsys'].string()
814 if cls['coordsys'].string() == 'CEL':
815 phagen['ra'] = cls['xref'].real()
816 phagen['dec'] = cls['yref'].real()
817 elif cls['coordsys'].string() == 'GAL':
818 phagen['glon'] = cls['xref'].real()
819 phagen['glat'] = cls['yref'].real()
820 if cls['srcshape'].string() == 'CIRCLE':
821 phagen['rad'] = cls['rad'].real()
822 elif cls['srcshape'].string() == 'RECT':
823 phagen['width'] = cls['width'].real()
824 phagen['height'] = cls['height'].real()
825 phagen['posang'] = cls['posang'].real()
826 phagen['bkgmethod'] = cls['bkgmethod'].string()
827 if cls['bkgmethod'].string() == 'REFLECTED':
828 phagen['bkgregmin'] = cls['bkgregmin'].integer()
829 phagen['use_model_bkg'] = use_model_bkg
830 phagen['maxoffset'] = cls['maxoffset'].real()
831 phagen['stack'] = True
832 phagen['etruemin'] = cls['etruemin'].real()
833 phagen['etruemax'] = cls['etruemax'].real()
834 phagen['etruebins'] = cls['etruebins'].integer()
835 phagen['chatter'] = cls['chatter'].integer()
836 phagen['clobber'] = cls['clobber'].boolean()
837 phagen['debug'] = cls['debug'].boolean()
838 phagen['nthreads'] = nthreads
839
840 # Set exclusion map
841 # Initialise exclusion map flag to False
842 use_excl_map = False
843 # Initialise inexclusion par flag to False
844 use_inexclusion = False
845 # Do we have valid exclusion map in memory?
846 if hasattr(cls, 'exclusion_map'):
847 exclusion_map = cls.exclusion_map()
848 if exclusion_map is not None:
849 # Set use exclusion map flag to True
850 use_excl_map = True
851 # Set exclusion map
852 phagen.exclusion_map(exclusion_map)
853 # If we do not have valid exclusion map in memory ...
854 if not use_excl_map:
855 # ... do we have an inxeclusion parameter?
856 if cls.has_par('inexclusion'):
857 # If the inexclusion parameter is valid
858 if cls['inexclusion'].is_valid():
859 # Set use inexclusion flag to True
860 use_inexclusion = True
861 # Set inexclusion parameter
862 inexclusion = cls['inexclusion'].value()
863 phagen['inexclusion'] = inexclusion
864 # If there is no valid exclusion map in memory
865 # nor valid inexclusion parameter
866 if not use_excl_map and not use_inexclusion:
867 # Set inexclusion for csphagen to None
868 phagen['inexclusion'] = 'NONE'
869
870 # Run csphagen
871 phagen.run()
872
873 # Clone resulting observation container
874 onoff_obs = phagen.obs().copy()
875
876 # On/Off observations are created with CSTAT as default statistic
877 # We will change this to the user choice
878 if cls['statistic'].string() != 'DEFAULT':
879 for observation in onoff_obs:
880 observation.statistic(cls['statistic'].string())
881
882 # Return On/Off oberservation container
883 return onoff_obs
884
885
886# ========================================= #
887# Calculate residuals from counts and model #
888# ========================================= #
889def residuals(cls, counts, model):
890 """
891 Calculate residuals given counts and models, according to algorithm
892 specified by user.
893
894 Can handle GSkyMap or GNdarray objects
895
896
897 Parameters
898 ----------
899 cls : `~ctools.cscript`
900 cscript class
901 counts : `~gammalib.GSkyMap/~gammalib.GNdarray'
902 Data counts
903 model : `~gammalib.GSkyMap/~gammalib.GNdarray'
904 Model counts
905
906 Returns
907 -------
908 residuals : `~gammalib.GSkyMap/~gammalib.GNdarray'
909 Residuals
910 """
911 # Find type of objects we are manipulating and set size to iterate later
912 if counts.classname() == 'GNdarray':
913 nelem = counts.size()
914 elif counts.classname() == 'GSkyMap':
915 nelem = counts.npix()
916 else:
917 msg = 'cscripts.obsutils.residuals only handles ' \
918 + 'gammalib.GNdarray or gammalib.GSkyMap objects.\n'
919 raise RuntimeError(msg)
920
921 # Copy counts to initialise residuals
922 residuals = counts.copy()
923
924 # Get residual map algorithm type
925 algorithm = cls['algorithm'].string()
926
927 # Subtract
928 if algorithm == 'SUB':
929 residuals -= model
930
931 # Subtract and divide by model
932 elif algorithm == 'SUBDIV':
933 residuals -= model
934 residuals /= model
935
936 # Subtract and divide by sqrt of model
937 elif algorithm == 'SUBDIVSQRT':
938 residuals -= model
939 residuals /= model.sqrt()
940
941 # Calculate significance from Li&Ma
942 elif algorithm == 'SIGNIFICANCE':
943
944 # Compute sign
945 sign = (residuals - model).sign()
946
947 # Loop over every bin
948 for i in range(nelem):
949
950 # If the model value > 0.0 do the computation as normal ...
951 model_val = model[i]
952 if model_val > 0.0:
953
954 # If the data value is also > 0 then compute the
955 # significance^2 and save it ...
956 data_val = residuals[i]
957 if data_val > 0.0:
958 log_val = math.log(data_val / model_val)
959 residuals[i] = (data_val * log_val) + model_val - data_val
960 if residuals[i] < 0.0: # See glitch issue #2765
961 residuals[i] = 0.0
962
963 # ... otherwise compute the reduced value of the above
964 # expression. This is necessary to avoid computing log(0).
965 else:
966 residuals[i] = model_val
967
968 # ... otherwise hard-code the significance to 0
969 else:
970 residuals[i] = 0.0
971
972 # Compute significance map
973 residuals *= 2.0
974 residuals = residuals.sqrt()
975 residuals *= sign
976
977 # Raise exception if algorithm is unknown
978 else:
979 raise TypeError('Algorithm "' + algorithm + '" not known')
980
981 # Return
982 return residuals
983
984
985# ================= #
986# Create counts map #
987# ================= #
988def create_counts_cube(cls, obs):
989 """
990 Create counts cube from observations
991
992 Parameters
993 ----------
994 cls : `~ctools.cscript`
995 cscript class
996 obs : `~gammalib.GObservations`
997 Observation container
998
999 Returns
1000 -------
1001 cntcube : `~gammalib.GCTAEventCube`
1002 Counts cube
1003 """
1004 # Initialise ctbin
1005 ctbin = ctools.ctbin(obs)
1006
1007 # Set parameters
1008 ctbin['xref'] = cls['xref'].real()
1009 ctbin['yref'] = cls['yref'].real()
1010 ctbin['proj'] = cls['proj'].string()
1011 ctbin['coordsys'] = cls['coordsys'].string()
1012 ctbin['ebinalg'] = cls['ebinalg'].string()
1013 ctbin['nxpix'] = cls['nxpix'].integer()
1014 ctbin['nypix'] = cls['nypix'].integer()
1015 ctbin['binsz'] = cls['binsz'].real()
1016 if cls['ebinalg'].string() == 'FILE':
1017 ctbin['ebinfile'] = cls['ebinfile'].filename().file()
1018 else:
1019 ctbin['enumbins'] = cls['enumbins'].integer()
1020 ctbin['emin'] = cls['emin'].real()
1021 ctbin['emax'] = cls['emax'].real()
1022 if cls['ebinalg'].string() == 'POW':
1023 ctbin['ebingamma'] = cls['ebingamma'].real()
1024 ctbin['chatter'] = cls['chatter'].integer()
1025 ctbin['clobber'] = cls['clobber'].boolean()
1026 ctbin['debug'] = cls['debug'].boolean()
1027
1028 # Run ctbin
1029 ctbin.run()
1030
1031 # Retrieve counts cube
1032 cntcube = ctbin.cube().copy()
1033
1034 # Return counts cube
1035 return cntcube
const GCTAEventCube & cube(const int &index=0) const
Return event cube at index.
Definition ctbin.cpp:460
virtual void run(void)
Run ctool.
Definition ctool.cpp:257
set_obs_patterns(pattern, ra=83.6331, dec=22.0145, offset=1.5)
Definition obsutils.py:418
set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.98, emin=0.1, emax=100.0, rad=5.0, instrument='CTA', irf='South_50h', caldb='prod2', obsid='000000', mjdref=51544.5)
Definition obsutils.py:235
set_observations(ra, dec, rad, tstart, duration, emin, emax, irf, caldb, deadc=0.98, pattern='single', offset=1.5)
Definition obsutils.py:475
residuals(cls, counts, model)
Definition obsutils.py:889
create_counts_cube(cls, obs)
Definition obsutils.py:988
get_stacked_response(obs, cntcube, edisp=False, addbounds=False, log=False, debug=False, chatter=2)
Definition obsutils.py:527
sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0, emin=None, emax=None, nbins=0, onsrc=None, onrad=0.2, addbounds=False, binsz=0.05, npix=200, proj='TAN', coord='GAL', nthreads=0)
Definition obsutils.py:31
get_onoff_obs(cls, obs, nthreads=0, ra=None, dec=None, srcname='')
Definition obsutils.py:756
get_stacked_obs(cls, obs, nthreads=0)
Definition obsutils.py:681
set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.98, emin=0.1, emax=100.0, rad=5.0, irf='South_50h', caldb='prod2')
Definition obsutils.py:338