class BandFitExtended(object): def __init__(self, which, energy_band, roi): """ extendedsource which index of source energy_band ROIEnergyBand object to fit. bands all energy bands in roi. """ self.energy_band = energy_band self.bands = self.energy_band.bands self.all_bands = roi.bands self.which = which self.all_mybands = roi.dsm.bgmodels[self.which].bands # list of the mybands corresponding to energy_bands self.mybands = [] # Create a lsit of myband object corresponding to the bands # in the energy_band. for eb_band in self.bands: for band, myband in zip(self.all_bands, self.all_mybands): if eb_band == band: self.mybands.append(myband) break def bandLikelihoodExtended(self, parameters, band, myband): new_counts = parameters[0] * myband.er old_counts = band.bg_counts[self.which] tot_term = (band.bg_all_counts + band.ps_all_counts + myband.overlaps * (new_counts - old_counts)) * band.phase_factor pix_term = ( band.pix_counts * np.log(band.bg_all_pix_counts + band.ps_all_pix_counts + myband.es_pix_counts * (new_counts - old_counts))).sum() if band.has_pixels else 0. return tot_term - pix_term def energyBandLikelihoodExtended(self, parameters, m): m.set_parameters(parameters) return sum( self.bandLikelihoodExtended([b.expected(m)], b, mb) for b, mb in zip(self.bands, self.mybands)) def normUncertaintyExtended(self): tot = 0 for b, mb in zip(self.bands, self.mybands): if not b.has_pixels: continue my_pix_counts = mb.es_pix_counts * b.expected(self.m) * mb.er all_pix_counts = b.bg_all_pix_counts + b.ps_all_pix_counts - mb.es_pix_counts * b.bg_counts[ self.which] + my_pix_counts tot += (b.pix_counts * (my_pix_counts / all_pix_counts)**2).sum() return tot**-0.5 def fit(self, saveto=None): bad_fit = False self.m = PowerLaw(free=[True, False], e0=(self.energy_band.emin * self.energy_band.emax)**0.5) # fix index to 2 f = self.energyBandLikelihoodExtended self.fit = fmin(f, self.m.get_parameters(), disp=0, full_output=1, args=(self.m, )) def upper_limit(): flux_copy = self.m[0] zp = self.energyBandLikelihoodExtended(np.asarray([-20]), self.m) # NB -- the 95% upper limit is calculated by assuming the likelihood is peaked at # 0 flux and finding the flux at which it has fallen by 1.35; this is a two-sided # 90% limit, or a one-sided 95% limit -- that's how it works, right? def f95(parameters): return abs( self.energyBandLikelihoodExtended(parameters, self.m) - zp - 1.35) # for some reason, can't get fsolve to work here. good ol' fmin to the rescue self.energy_band.uflux = 10**fmin(f95, np.asarray([-11.75]), disp=0)[0] self.energy_band.lflux = None self.energy_band.flux = None self.m[0] = flux_copy # if flux below a certain level, set an upper limit if self.m[0] < 1e-20: bad_fit = True upper_limit() else: try: err = self.normUncertaintyExtended() except: bad_fit = True err = 0 self.energy_band.flux = self.m[0] self.energy_band.uflux = self.energy_band.flux * (1 + err) self.energy_band.lflux = max(self.energy_band.flux * (1 - err), 1e-30) if saveto is not None: for b, mb in zip(self.bands, self.mybands): b.__dict__[saveto] = (b.expected(self.m) * mb.er if not bad_fit else -1) if bad_fit: self.energy_band.ts = 0 else: null_ll = sum( self.bandLikelihoodExtended([0], b, mb) for b, mb in zip(self.bands, self.mybands)) alt_ll = sum( self.bandLikelihoodExtended([b.expected(self.m) * mb.er], b, mb) for b, mb in zip(self.bands, self.mybands)) self.energy_band.ts = 2 * (null_ll - alt_ll)