예제 #1
0
class GenericOGIPLike(PluginPrototype):
    def __init__(self, name, phafile, bkgfile, rspfile, arffile=None):

        self.name = name

        # Check that all file exists
        notExistant = []

        inputFiles = [phafile, bkgfile, rspfile]

        for i in range(3):

            # The file could contain a {#} specification, like spectrum.pha{3},
            # which indicate the 3rd spectrum in the spectrum.pha file

            inputFiles[i] = file_utils.sanitize_filename(inputFiles[i].split("{")[0])

            if not file_utils.file_existing_and_readable(inputFiles[i]):
                raise IOError("File %s does not exist or is not readable" % (inputFiles[i]))

        phafile, bkgfile, rspfile = inputFiles

        # Check the arf, if provided
        if arffile is not None:

            arffile = file_utils.sanitize_filename(arffile.split("{")[0])

            if not file_utils.file_existing_and_readable(arffile):
                raise IOError("File %s does not exist or is not readable" % (arffile))

        self.phafile = OGIPPHA(phafile, filetype="observed")
        self.exposure = self.phafile.getExposure()
        self.bkgfile = OGIPPHA(bkgfile, filetype="background")
        self.response = Response(rspfile, arffile)

        # Start with an empty mask (the user will overwrite it using the
        # setActiveMeasurement method)
        self.mask = numpy.asarray(numpy.ones(self.phafile.getRates().shape), numpy.bool)

        # Get the counts for this spectrum
        self.counts = self.phafile.getRates()[self.mask] * self.exposure

        # Check that counts is positive
        idx = self.counts < 0

        if numpy.sum(idx) > 0:
            warnings.warn(
                "The observed spectrum for %s " % self.name + "has negative channels! Fixing those to zero.",
                RuntimeWarning,
            )
            self.counts[idx] = 0

        pass

        # Get the background counts for this spectrum
        self.bkgCounts = self.bkgfile.getRates()[self.mask] * self.exposure

        # Check that bkgCounts is positive
        idx = self.bkgCounts < 0

        if numpy.sum(idx) > 0:
            warnings.warn(
                "The background spectrum for %s " % self.name + "has negative channels! Fixing those to zero.",
                RuntimeWarning,
            )
            self.bkgCounts[idx] = 0

        # Check that the observed counts are positive

        idx = self.counts < 0

        if numpy.sum(idx) > 0:
            raise RuntimeError("Negative counts in observed spectrum %s. Data are corrupted." % (phafile))

        # Keep a copy which will never be modified
        self.counts_backup = numpy.array(self.counts, copy=True)
        self.bkgCounts_backup = numpy.array(self.bkgCounts, copy=True)

        # Effective area correction is disabled by default, i.e.,
        # the nuisance parameter is fixed to 1
        self.nuisanceParameters = {}
        self.nuisanceParameters["InterCalib"] = Parameter("InterCalib", 1, min_value=0.9, max_value=1.1, delta=0.01)
        self.nuisanceParameters["InterCalib"].fix = True

    pass

    def useIntercalibrationConst(self, factorLowBound=0.9, factorHiBound=1.1):
        self.nuisanceParameters["InterCalib"].free()
        self.nuisanceParameters["InterCalib"].set_bounds(factorLowBound, factorHiBound)

        # Check that the parameter is within the provided bounds
        value = self.nuisanceParameters["InterCalib"].value

        if value < factorLowBound:
            warnings.warn(
                "The intercalibration constant was %s, lower than the provided lower bound." % (value, factorLowBound)
                + " Setting it equal to the lower bound"
            )

            self.nuisanceParameters["InterCalib"].setValue(float(factorLowBound))

        if value > factorHiBound:
            warnings.warn(
                "The intercalibration constant was %s, larger than the provided hi bound." % (value, factorHiBound)
                + " Setting it equal to the hi bound"
            )

            self.nuisanceParameters["InterCalib"].setValue(float(factorHiBound))

    def fixIntercalibrationConst(self, value=None):

        if value is not None:
            # Fixing the constant to the provided value
            self.nuisanceParameters["InterCalib"].setValue(float(value))

        else:

            # Do nothing, i.e., leave the constant to the value
            # it currently has
            pass

        self.nuisanceParameters["InterCalib"].fix()

    def setActiveMeasurements(self, *args):
        """Set the measurements to be used during the analysis.
        Use as many ranges as you need,
        specified as 'emin-emax'. Energies are in keV. Example:

        setActiveMeasurements('10-12.5','56.0-100.0')

        which will set the energy range 10-12.5 keV and 56-100 keV to be
        used in the analysis"""

        # To implelemnt this we will use an array of boolean index,
        # which will filter
        # out the non-used channels during the logLike

        # Now build the mask: values for which the mask is 0 will be masked
        mask = numpy.zeros(self.phafile.getRates().shape)

        for arg in args:
            ee = map(float, arg.replace(" ", "").split("-"))
            emin, emax = sorted(ee)
            idx1 = self.response.energyToChannel(emin)
            idx2 = self.response.energyToChannel(emax)
            mask[idx1 : idx2 + 1] = True
        pass
        self.mask = numpy.array(mask, numpy.bool)

        self.counts = self.counts_backup[self.mask]
        self.bkgCounts = self.bkgCounts_backup[self.mask]

        print("Now using %s channels out of %s" % (numpy.sum(self.mask), self.phafile.getRates().shape[0]))

    pass

    def get_name(self):
        """
        Return a name for this dataset (likely set during the constructor)
        """
        return self.name

    pass

    def set_model(self, likelihoodModel):
        """
        Set the model to be used in the joint minimization.
        """
        self.likelihoodModel = likelihoodModel

        nPointSources = self.likelihoodModel.get_number_of_point_sources()

        # This is a wrapper which iterates over all the point sources and get
        # the fluxes
        # We assume there are no extended sources, since the GBM cannot handle them

        def diffFlux(energies):
            fluxes = self.likelihoodModel.get_point_source_fluxes(0, energies)

            # If we have only one point source, this will never be executed
            for i in range(1, nPointSources):
                fluxes += self.likelihoodModel.get_point_source_fluxes(i, energies)

            return fluxes

        self.diffFlux = diffFlux

        # The following integrates the diffFlux function using Simpson's rule
        # This assume that the intervals e1,e2 are all small, which is guaranteed
        # for any reasonable response matrix, given that e1 and e2 are Monte-Carlo
        # energies. It also assumes that the function is smooth in the interval
        # e1 - e2 and twice-differentiable, again reasonable on small intervals for
        # decent models. It might fail for models with too sharp features, smaller
        # than the size of the monte carlo interval.

        def integral(e1, e2):
            # Simpson's rule

            return (e2 - e1) / 6.0 * (self.diffFlux(e1) + 4 * self.diffFlux((e1 + e2) / 2.0) + self.diffFlux(e2))

        self.response.setFunction(diffFlux, integral)

    pass

    def inner_fit(self):

        # Effective area correction
        if self.nuisanceParameters["InterCalib"].free:

            # A true fit would be an overkill, and slow
            # Just sample a 100 values and choose the minimum
            values = numpy.linspace(
                self.nuisanceParameters["InterCalib"].minValue, self.nuisanceParameters["InterCalib"].maxValue, 100
            )

            # I do not use getLogLike so I can compute only once the folded model
            # (which is not going to change during the inner fit)

            folded = self.getFoldedModel()

            modelCounts = folded * self.exposure

            def fitfun(cons):

                self.nuisanceParameters["InterCalib"].setValue(cons)

                return (-1) * self._computeLogLike(
                    self.nuisanceParameters["InterCalib"].value * modelCounts + self.bkgCounts
                )

            logLval = map(fitfun, values)
            idx = numpy.argmax(logLval)
            self.nuisanceParameters["InterCalib"].setValue(values[idx])
            # return logLval[idx]

            # Now refine with minuit

            parameters = collections.OrderedDict()
            parameters[(self.name, "InterCalib")] = self.nuisanceParameters["InterCalib"]
            minimizer = minimization.MinuitMinimizer(fitfun, parameters)
            bestFit, mlogLmin = minimizer.minimize()

            return mlogLmin * (-1)

        else:

            return self.get_log_like()

    def getFoldedModel(self):

        # Get the folded model for this spectrum
        # (this is the rate predicted, in cts/s)

        return self.response.convolve()[self.mask]

    def getModelAndData(self):

        e1, e2 = (self.response.ebounds[:, 0], self.response.ebounds[:, 1])

        return (
            self.response.convolve()[self.mask] * self.exposure + self.bkgCounts,
            e1[self.mask],
            e2[self.mask],
            self.counts,
        )

    def display(self):

        # Plot the counts spectrum with residuals

        model, e1, e2, counts = self.getModelAndData()

        # Try to automagically decide a good constant number
        # of counts, based on the input

        total = numpy.sum(counts)

        trials = [50, 25, 15, 5]

        choice = None

        for t in trials:

            if total / float(t) >= 10:
                choice = t

                break

        # If no choice worked, use the minimum, which is 5 counts
        # per bin

        if choice is None:
            choice = 5

        # Bin the data by constant counts

        binner = Binner.Binner(e1, e2, counts)

        ne1, ne2, nc, newModel = binner.byConstantCounts(choice, model)

        # Now plot the results

        fig = plt.figure()

        gs = gridspec.GridSpec(2, 1, height_ratios=[2, 1])
        gs.update(hspace=0)

        sub = plt.subplot(gs[0])

        # Plot data

        ec = (ne2 + ne1) / 2.0
        de = (ne2 - ne1) / 2.0

        y = nc / (de * 2.0) / self.exposure
        dy = numpy.sqrt(nc) / (de * 2.0) / self.exposure

        sub.errorbar(ec, y, xerr=de, yerr=dy, fmt=".", capsize=0)

        # Plot model

        xx = numpy.append(ne1, [ne2[-1]])

        yy = newModel / (2.0 * de) / self.exposure

        yyy = numpy.append(yy, yy[-1])

        sub.step(xx, yyy, where="post")

        sub.set_xscale("log")
        sub.set_yscale("log", nonposy="clip")

        sub.set_ylabel(r"Counts keV$^{-1}$ s$^{-1}$")

        # Now plot residuals
        sub1 = plt.subplot(gs[1])

        res = (yy - y) / dy

        sub1.axhline(0, linestyle="--")
        sub1.errorbar(ec, res, yerr=1.0, xerr=de, fmt=".", capsize=0)

        sub1.set_xscale("log")

        # Match the x axis

        sub.set_xticks([])

        sub.set_xlim([(ec - de).min(), (ec + de).max()])
        sub1.set_xlim([(ec - de).min(), (ec + de).max()])

        sub1.set_xlabel("Energy (keV)")

        return fig

    def _getModelCounts(self):

        # Get the folded model for this spectrum (this is the rate predicted,
        # in cts/s)

        folded = self.getFoldedModel()

        # Model is folded+background (i.e., we assume negligible errors on the
        # background)
        modelCounts = self.nuisanceParameters["InterCalib"].value * folded * self.exposure + self.bkgCounts

        return modelCounts

    def _computeLogLike(self, modelCounts):

        idx = modelCounts > 0

        return numpy.sum(
            -modelCounts[idx] + self.counts[idx] * numpy.log(modelCounts[idx]) - logfactorial(self.counts[idx])
        )

    def get_log_like(self):
        """
        Return the value of the log-likelihood with the current values for the
        parameters
        """

        modelCounts = self._getModelCounts()

        logLike = self._computeLogLike(modelCounts)

        return logLike

    def get_nuisance_parameters(self):
        """
        Return a list of nuisance parameter names. Return an empty list if there
        are no nuisance parameters
        """
        return self.nuisanceParameters.keys()

    pass
예제 #2
0
class FermiGBMLike(pluginPrototype):
  
  def __init__(self,name,phafile,bkgfile,rspfile):
    '''
    If the input files are PHA2 files, remember to specify the spectrum number, for example:
    FermiGBMLike("GBM","spectrum.pha{2}","bkgfile.bkg{2}","rspfile.rsp{2}")
    to load the second spectrum, second background spectrum and second response.
    '''
    
    self.name                 = name
    
    #Check that all file exists
    notExistant               = []
    
    if(not os.path.exists(phafile.split("{")[0])):
      notExistant.append(phafile.split("{")[0])
    
    if(not os.path.exists(bkgfile.split("{")[0])):
      notExistant.append(bkgfile.split("{")[0])
    
    if(not os.path.exists(rspfile.split("{")[0])):
      notExistant.append(rspfile.split("{")[0])
    
    if(len(notExistant)>0):
      
      for nt in notExistant:
        print("File %s does not exists!" %(nt))
      
      raise IOError("One or more input file do not exist!")
    
    self.phafile              = OGIPPHA(phafile,filetype='observed')
    self.exposure             = self.phafile.getExposure()
    self.bkgfile              = OGIPPHA(bkgfile,filetype="background")
    self.response             = Response(rspfile)    
    
    #Start with an empty mask (the user will overwrite it using the
    #setActiveMeasurement method)
    self.mask                 = numpy.asarray(
                                    numpy.ones(self.phafile.getRates().shape),
                                    numpy.bool)
    
    #Get the counts for this spectrum
    self.counts               = ( self.phafile.getRates()[self.mask]
                                  * self.exposure )
    
    #Check that counts is positive
    idx                       = (self.counts < 0)
    
    if(numpy.sum(idx) > 0):
      
      warnings.warn("The observed spectrum for %s " % self.name + 
                    "has negative channels! Fixing those to zero.", 
                     RuntimeWarning)
      self.counts[idx]        = 0
    
    pass
    
    #Get the background counts for this spectrum
    self.bkgCounts            = ( self.bkgfile.getRates()[self.mask]
                                  * self.exposure )
    
    
    #Check that bkgCounts is positive
    idx                       = (self.bkgCounts < 0)
    
    if(numpy.sum(idx) > 0):
      
      warnings.warn("The background spectrum for %s " % self.name + 
                    "has negative channels! Fixing those to zero.", 
                     RuntimeWarning)
      self.bkgCounts[idx]     = 0
    
    #Check that the observed counts are positive
    
    idx = self.counts < 0
    
    if numpy.sum( idx ) > 0:
       
       raise RuntimeError("Negative counts in observed spectrum %s. Data are corrupted." % ( phafile ))
    
    #Keep a copy which will never be modified
    self.counts_backup        = numpy.array(self.counts,copy=True)
    self.bkgCounts_backup     = numpy.array(self.bkgCounts,copy=True)
    
    #Effective area correction is disabled by default, i.e.,
    #the nuisance parameter is fixed to 1    
    self.nuisanceParameters       = {}
    self.nuisanceParameters['InterCalib'] = Parameter("InterCalib",1,0.9,1.1,0.01,fixed=True,nuisance=True)
    
  pass
  
  def useIntercalibrationConst(self,factorLowBound=0.9,factorHiBound=1.1):
    self.nuisanceParameters['InterCalib'].free()
    self.nuisanceParameters['InterCalib'].setBounds(factorLowBound,factorHiBound)
    
    #Check that the parameter is within the provided bounds
    value                     = self.nuisanceParameters['InterCalib'].value
    
    if( value < factorLowBound ):
            
      warnings.warn("The intercalibration constant was %s, lower than the provided lower bound." %(value,factorLowBound) +
                    " Setting it equal to the lower bound")
      
      self.nuisanceParameters['InterCalib'].setValue(float(factorLowBound))

      
      
    if( value > factorHiBound):
      
      warnings.warn("The intercalibration constant was %s, larger than the provided hi bound." %(value,factorHiBound) +
                    " Setting it equal to the hi bound")
      
      self.nuisanceParameters['InterCalib'].setValue(float(factorHiBound))
    
  def fixIntercalibrationConst(self,value=None):
    
    if(value is not None):
      #Fixing the constant to the provided value
      self.nuisanceParameters['InterCalib'].setValue(float(value))
    
    else:
    
      #Do nothing, i.e., leave the constant to the value
      #it currently has
      pass
      
    self.nuisanceParameters['InterCalib'].fix()
  
  def setActiveMeasurements(self,*args):
    '''Set the measurements to be used during the analysis. 
    Use as many ranges as you need,
    specified as 'emin-emax'. Energies are in keV. Example:
    
    setActiveMeasurements('10-12.5','56.0-100.0')
    
    which will set the energy range 10-12.5 keV and 56-100 keV to be 
    used in the analysis'''
    
    #To implelemnt this we will use an array of boolean index, 
    #which will filter
    #out the non-used channels during the logLike
    
    #Now build the mask: values for which the mask is 0 will be masked
    mask                      = numpy.zeros(self.phafile.getRates().shape)
    
    for arg in args:
      ee                      = map(float,arg.replace(" ","").split("-"))
      emin,emax               = sorted(ee)
      idx1                    = self.response.energyToChannel(emin)
      idx2                    = self.response.energyToChannel(emax)
      mask[idx1:idx2+1]       = True
    pass
    self.mask                 = numpy.array(mask,numpy.bool)
    
    self.counts               = self.counts_backup[self.mask]
    self.bkgCounts            = self.bkgCounts_backup[self.mask]
    
    print("Now using %s channels out of %s" %( numpy.sum(self.mask),
                                               self.phafile.getRates().shape[0]
                                              ) )
  pass
  
  def getName(self):
    '''
    Return a name for this dataset (likely set during the constructor)
    '''
    return self.name
  pass
  
  def setModel(self,likelihoodModel):
    '''
    Set the model to be used in the joint minimization.
    '''
    self.likelihoodModel         = likelihoodModel
    
    nPointSources                = self.likelihoodModel.getNumberOfPointSources()
    
    #This is a wrapper which iterates over all the point sources and get
    #the fluxes
    #We assume there are no extended sources, since the GBM cannot handle them
    
    def diffFlux(energies):
      
      fluxes                     = self.likelihoodModel.getPointSourceFluxes(0,energies)
      
      #If we have only one point source, this will never be executed
      for i in range(1, nPointSources):
        fluxes                  += self.likelihoodModel.getPointSourceFluxes(i,energies)
      
      return fluxes
    
    self.diffFlux                = diffFlux
    
    #The following integrates the diffFlux function using Simpson's rule
    #This assume that the intervals e1,e2 are all small, which is guaranteed
    #for any reasonable response matrix, given that e1 and e2 are Monte-Carlo
    #energies. It also assumes that the function is smooth in the interval
    #e1 - e2 and twice-differentiable, again reasonable on small intervals for
    #decent models. It might fail for models with too sharp features, smaller
    #than the size of the monte carlo interval.
    
    def integral(e1,e2):
      
      #Simpson's rule
      
      return (e2 - e1) / 6.0 * ( self.diffFlux(e1) 
                                 + 4 * self.diffFlux( (e1+e2) / 2.0 )
                                 + self.diffFlux(e2) )
    
    self.response.setFunction( diffFlux,
                               integral)
  pass

  def innerFit(self):
        
    #Effective area correction
    if(self.nuisanceParameters['InterCalib'].isFree()):
            
      #A true fit would be an overkill, and slow
      #Just sample a 100 values and choose the minimum
      values                  = numpy.linspace(self.nuisanceParameters['InterCalib'].minValue,
                                               self.nuisanceParameters['InterCalib'].maxValue,
                                               100)
      
      
      #I do not use getLogLike so I can compute only once the folded model
      #(which is not going to change during the inner fit)
      
      folded                  = self.getFoldedModel()
      
      modelCounts             = folded * self.exposure
      
      def fitfun(cons):
        
        self.nuisanceParameters['InterCalib'].setValue( cons )
        
        return (-1) * self._computeLogLike(self.nuisanceParameters['InterCalib'].value * modelCounts + self.bkgCounts)
            
      logLval                 = map(fitfun, values)
      idx                     = numpy.argmax(logLval)
      self.nuisanceParameters['InterCalib'].setValue(values[idx])
      #return logLval[idx]
      
      #Now refine with minuit
      
      parameters              = collections.OrderedDict()
      parameters[ (self.name, 'InterCalib') ]      = self.nuisanceParameters['InterCalib']
      minimizer               = minimization.iMinuitMinimizer(fitfun, parameters)
      bestFit, mlogLmin       = minimizer.minimize()
      
      return mlogLmin * (-1)
      
    else:
      
      return self.getLogLike()
  
  def getFoldedModel(self):
    
    #Get the folded model for this spectrum 
    #(this is the rate predicted, in cts/s)    
    
    return self.response.convolve()[self.mask]
  
  def getModelAndData(self):
    
    e1,e2                     = (self.response.ebounds[:,0],
                                 self.response.ebounds[:,1])
    
    return ( self.response.convolve()[self.mask] * self.exposure 
           + self.bkgCounts, 
             e1[self.mask],
             e2[self.mask],
             self.counts )
  
  def _getModelCounts(self):
    
    #Get the folded model for this spectrum (this is the rate predicted, 
    #in cts/s)
    
    folded                    = self.getFoldedModel()
    
    #Model is folded+background (i.e., we assume negligible errors on the 
    #background)
    modelCounts               = self.nuisanceParameters['InterCalib'].value * folded * self.exposure + self.bkgCounts
    
    return modelCounts
  
  def _computeLogLike(self, modelCounts):
    
    return numpy.sum(- modelCounts 
                     + self.counts * numpy.log(modelCounts)
                     - logfactorial(self.counts) )
  
  def getLogLike(self):
    '''
    Return the value of the log-likelihood with the current values for the
    parameters
    '''
    
    modelCounts               = self._getModelCounts()
    
    logLike                   = self._computeLogLike( modelCounts )
    
    return logLike
      
  def getNuisanceParameters(self):
    '''
    Return a list of nuisance parameter names. Return an empty list if there
    are no nuisance parameters
    '''
    return self.nuisanceParameters.keys()
  pass