コード例 #1
0
ファイル: minuit.py プロジェクト: mahmoud-lsw/gammatools
class Minuit(object):
    """A wrapper class to initialize a minuit object with a numpy array.

    Positional args:
        myFCN : A python callable
        params : An array (or other python sequence) of parameters

    Keyword args:

        limits [None] : a nested sequence of (lower_limit,upper_limit) for each parameter.
        steps [[.1]*npars] : Estimated errors for the parameters, used as an initial step size.
        tolerance [.001] : Tolerance to test for convergence.  Minuit considers convergence to be
                            when the estimated distance to the minimum (edm) is <= .001*up*tolerance,
                            or 5e-7 by default.
        up [.5]  : Change in the objective function that determines 1-sigma error.  .5 if 
                          the objective function is -log(Likelihood), 1 for chi-squared.
        max_calls [10000] : Maximum number of calls to the objective function.
        param_names ['p0','p1',...] : a list of names for the parameters
        args [()] : a tuple of extra arguments to pass to myFCN and gradient.
        gradient [None] : a function that takes a list of parameters and returns a list of 
                          first derivatives of myFCN.  Assumed to take the same args as myFCN.
        force_gradient [0] : Set to 1 to force Minuit to use the user-provided gradient function.
        strategy[1] : Strategy for minuit to use, from 0 (fast) to 2 (safe) 
        fixed [False, False, ...] : If passed, an array of all the parameters to fix
    """
    def __init__(self, myFCN, params, **kwargs):

        from ROOT import TMinuit, Long, Double

        self.limits = np.zeros((len(params), 2))
        self.steps = .04 * np.ones(
            len(params))  # about 10 percent in log10 space
        self.tolerance = .001
        self.maxcalls = 10000
        self.printMode = 0
        self.up = 0.5
        self.param_names = ['p%i' % i for i in xrange(len(params))]
        self.erflag = Long()
        self.npars = len(params)
        self.args = ()
        self.gradient = None
        self.force_gradient = 0
        self.strategy = 1
        self.fixed = np.zeros_like(params)
        self.__dict__.update(kwargs)

        self.params = np.asarray(params, dtype='float')
        self.fixed = np.asarray(self.fixed, dtype=bool)
        self.fcn = FCN(myFCN,
                       self.params,
                       args=self.args,
                       gradient=self.gradient)
        self.fval = self.fcn.fval
        self.minuit = TMinuit(self.npars)
        self.minuit.SetPrintLevel(self.printMode)
        self.minuit.SetFCN(self.fcn)
        if self.gradient:
            self.minuit.mncomd('SET GRA %i' % (self.force_gradient),
                               self.erflag)
        self.minuit.mncomd('SET STR %i' % self.strategy, Long())

        for i in xrange(self.npars):

            if self.limits[i][0] is None: self.limits[i][0] = 0.0
            if self.limits[i][1] is None: self.limits[i][1] = 0.0

            self.minuit.DefineParameter(i, self.param_names[i], self.params[i],
                                        self.steps[i], self.limits[i][0],
                                        self.limits[i][1])

        self.minuit.SetErrorDef(self.up)

        for index in np.where(self.fixed)[0]:
            self.minuit.FixParameter(int(index))

    def minimize(self, method='MIGRAD'):

        from ROOT import TMinuit, Long, Double

        self.minuit.mncomd(
            '%s %i %f' % (method, self.maxcalls, self.tolerance), self.erflag)
        for i in xrange(self.npars):
            val, err = Double(), Double()
            self.minuit.GetParameter(i, val, err)
            self.params[i] = val
        self.fval = self.fcn.fcn(self.params)
        return (self.params, self.fval)

    def errors(self, method='HESSE'):
        """method ['HESSE']   : how to calculate the errors; Currently, only 'HESSE' works."""
        if not np.any(self.fixed):
            mat = np.zeros(self.npars**2)
            if method == 'HESSE':
                self.minuit.mnhess()
            else:
                raise Exception("Method %s not recognized." % method)
            self.minuit.mnemat(mat, self.npars)
            return mat.reshape((self.npars, self.npars))
        else:
            # Kind of ugly, but for fixed parameters, you need to expand out the covariance matrix.
            nf = int(np.sum(~self.fixed))
            mat = np.zeros(nf**2)
            if method == 'HESSE':
                self.minuit.mnhess()
            else:
                raise Exception("Method %s not recognized." % method)
            self.minuit.mnemat(mat, nf)

            # Expand out the covariance matrix.
            cov = np.zeros((self.npars, self.npars))
            cov[np.outer(~self.fixed, ~self.fixed)] = np.ravel(mat)
            return cov

    def uncorrelated_minos_error(self):
        """ Kind of a kludge, but a compromise between the speed of
            HESSE errors and the accuracy of MINOS errors.  It accounts
            for non-linearities in the likelihood by varying each function
            until the value has fallen by the desired amount using hte
            MINOS code. But does not account for correlations between
            the parameters by fixing all other parameters during the
            minimzation. 
            
            Again kludgy, but what is returned is an effective covariance
            matrix. The diagonal errors are calcualted by averaging the
            upper and lower errors and the off diagonal terms are set
            to 0. """

        cov = np.zeros((self.npars, self.npars))

        # fix all parameters
        for i in range(self.npars):
            self.minuit.FixParameter(int(i))

        # loop over free paramters getting error
        for i in np.where(self.fixed == False)[0]:
            self.minuit.Release(int(i))

            # compute error
            self.minuit.mnmnos()
            low, hi, parab, gcc = ROOT.Double(), ROOT.Double(), ROOT.Double(
            ), ROOT.Double()

            # get error
            self.minuit.mnerrs(int(i), low, hi, parab, gcc)
            cov[i, i] = ((abs(low) + abs(hi)) / 2.0)**2
            self.minuit.FixParameter(int(i))

        for i, fixed in enumerate(self.fixed):
            if fixed:
                self.minuit.FixParameter(int(i))
            else:
                self.minuit.Release(int(i))

        return cov
コード例 #2
0
class minuitSolver():
    def __init__(self, fcn, pars, parerrors, parnames, ndof, maxpars=50):

        if len(pars) > maxpars:
            raise MinuitError("More than 50 parameters, increase maxpars")
        self.__minuit = TMinuit(maxpars)
        self.minuitCommand("SET PRI -1")
        # Hold on to fcn or python will kill it after passing to TMinuit
        self.__fcn = fcn
        self.__minuit.SetFCN(fcn)
        self.__pars = pars
        self.__parerrors = parerrors
        self.__parnames = parnames
        self.__setParameters()
        self.__ndof = ndof
        return

    def __setParameters(self):
        for par, parerror, parname, i in zip(self.__pars, self.__parerrors,
                                             self.__parnames,
                                             range(len(self.__pars))):
            ierflg = self.__minuit.DefineParameter(i, parname, par, parerror,
                                                   0.0, 0.0)
        if ierflg != 0:
            message = "Minuit define parameter error: " + str(ierflg)
            raise MinuitError(message)
        return

    def minuitCommand(self, command):
        errorcode = self.__minuit.Command(command)
        if errorcode != 0:
            message = "Minuit command " + command + " failed: " + str(
                errorcode)
            raise MinuitError(message)
        return

    def solve(self, lBlobel=True):
        self.__setParameters()
        self.minuitCommand("MIGRAD")
        return

    def getChisq(self):
        hstat = self.__getStat()
        return hstat["min"]

    def getNdof(self):
        return self.__ndof

    def __printPars(self, par, parerrors, parnames, ffmt=".4f"):
        for ipar in range(len(par)):
            name = parnames[ipar]
            print("{0:>15s}:".format(name), end=" ")
            fmtstr = "{0:10" + ffmt + "} +/- {1:10" + ffmt + "}"
            print(fmtstr.format(par[ipar], parerrors[ipar]))
        return

    def printResults(self, ffmt=".4f", cov=False, corr=False):
        print("\nMinuit least squares")
        print("\nResults after minuit fit")
        hstat = self.__getStat()
        chisq = hstat["min"]
        ndof = self.__ndof
        fmtstr = "\nChi^2= {0:" + ffmt + "} for {1:d} d.o.f, Chi^2/d.o.f= {2:" + ffmt + "}, P-value= {3:" + ffmt + "}"
        print(
            fmtstr.format(chisq, ndof, chisq / float(ndof),
                          TMath.Prob(chisq, ndof)))
        fmtstr = "Est. dist. to min: {0:.3e}, minuit status: {1}"
        print(fmtstr.format(hstat["edm"], hstat["status"]))
        print("\nFitted parameters and errors")
        print("           Name       Value          Error")
        pars = self.getPars()
        parerrors = self.getParErrors()
        self.__printPars(pars, parerrors, self.__parnames, ffmt=ffmt)
        if cov:
            self.printCovariances()
        if corr:
            self.printCorrelations()
        return

    def __printMatrix(self, m, ffmt):
        mshape = m.shape
        print("{0:>10s}".format(""), end=" ")
        for i in range(mshape[0]):
            print("{0:>10s}".format(self.__parnames[i]), end=" ")
        print()
        for i in range(mshape[0]):
            print("{0:>10s}".format(self.__parnames[i]), end=" ")
            for j in range(mshape[1]):
                fmtstr = "{0:10" + ffmt + "}"
                print(fmtstr.format(m[i, j]), end=" ")
            print()
        return

    def printCovariances(self):
        print("\nCovariance matrix:")
        self.__printMatrix(self.getCovariancematrix(), ".3e")
        return

    def printCorrelations(self):
        print("\nCorrelation matrix:")
        self.__printMatrix(self.getCorrelationmatrix(), ".3f")
        return

    def getPars(self):
        pars, parerrors = self.__getPars()
        return pars

    def getUparv(self):
        pars = self.getPars()
        parv = matrix(pars)
        parv.shape = (len(pars), 1)
        return parv

    def getParErrors(self):
        pars, parerrors = self.__getPars()
        return parerrors

    def __getPars(self):
        pars = []
        parerrors = []
        for ipar in range(len(self.__pars)):
            par = c_double()
            pare = c_double()
            ivarbl = self.__minuit.GetParameter(ipar, par, pare)
            if ivarbl < 0:
                message = "Parameter " + str(ipar) + " not defined"
                raise MinuitError(message)
            pars.append(par.value)
            parerrors.append(pare.value)
        return pars, parerrors

    def getCovariancematrix(self):
        npar = len(self.__pars)
        covm = array(npar**2 * [0.0], dtype="double")
        self.__minuit.mnemat(covm, npar)
        covm.shape = (npar, npar)
        return covm

    def getCorrelationmatrix(self):
        covm = self.getCovariancematrix()
        corrm = covm.copy()
        npar = len(self.__pars)
        for i in range(npar):
            for j in range(npar):
                corrm[i, j] = covm[i, j] / sqrt(covm[i, i] * covm[j, j])
        return corrm

    def __getStat(self):
        fmin = c_double()
        fedm = c_double()
        errdef = c_double()
        npari = c_int()
        nparx = c_int()
        istat = c_int()
        self.__minuit.mnstat(fmin, fedm, errdef, npari, nparx, istat)
        hstat = {
            "min": fmin.value,
            "edm": fedm.value,
            "errdef": errdef.value,
            "npari": npari.value,
            "nparx": nparx.value,
            "status": istat.value
        }
        return hstat