def fit(self): numberOfParameters = len(self.samples) gMinuit = TMinuit(numberOfParameters) if self.method == 'logLikelihood': # set function for minimisation gMinuit.SetFCN(self.logLikelihood) gMinuit.SetMaxIterations(1000000000000) # set Minuit print level # printlevel = -1 quiet (also suppress all warnings) # = 0 normal # = 1 verbose # = 2 additional output giving intermediate results. # = 3 maximum output, showing progress of minimizations. gMinuit.SetPrintLevel(-1) # Error definition: 1 for chi-squared, 0.5 for negative log likelihood # SETERRDEF<up>: Sets the value of UP (default value= 1.), defining parameter errors. # Minuit defines parameter errors as the change in parameter value required to change the function value by UP. # Normally, for chisquared fits UP=1, and for negative log likelihood, UP=0.5. gMinuit.SetErrorDef(0.5) # error flag for functions passed as reference.set to as 0 is no error errorFlag = Long(2) N_min = 0 N_max = self.fit_data_collection.max_n_data() * 2 param_index = 0 # MNPARM # Implements one parameter definition: # mnparm(k, cnamj, uk, wk, a, b, ierflg) # K (external) parameter number # CNAMK parameter name # UK starting value # WK starting step size or uncertainty # A, B lower and upper physical parameter limits # and sets up (updates) the parameter lists. # Output: IERFLG =0 if no problems # >0 if MNPARM unable to implement definition for sample in self.samples: # all samples but data if self.n_distributions > 1: gMinuit.mnparm( param_index, sample, self.normalisation[self.distributions[0]][sample], 10.0, N_min, N_max, errorFlag) else: gMinuit.mnparm(param_index, sample, self.normalisation[sample], 10.0, N_min, N_max, errorFlag) param_index += 1 arglist = array('d', 10 * [0.]) # minimisation strategy: 1 standard, 2 try to improve minimum (a bit slower) arglist[0] = 2 # minimisation itself # SET STRategy<level>: Sets the strategy to be used in calculating first and second derivatives and in certain minimization methods. # In general, low values of <level> mean fewer function calls and high values mean more reliable minimization. # Currently allowed values are 0, 1 (default), and 2. gMinuit.mnexcm("SET STR", arglist, 1, errorFlag) gMinuit.Migrad() gMinuit.mnscan( ) # class for minimization using a scan method to find the minimum; allows for user interaction: set/change parameters, do minimization, change parameters, re-do minimization etc. gMinuit.mnmatu(1) # prints correlation matrix (always needed) self.module = gMinuit self.performedFit = True if not self.module: raise Exception( 'No fit results available. Please run fit method first') results = {} param_index = 0 for sample in self.samples: temp_par = Double(0) temp_err = Double(0) self.module.GetParameter(param_index, temp_par, temp_err) if (math.isnan(temp_err)): self.logger.warning( 'Template fit error is NAN, setting to sqrt(N).') temp_err = math.sqrt(temp_par) # gMinuit.Command("SCAn %i %i %i %i" % ( param_index, 100, N_min, N_total ) ); # scan = gMinuit.GetPlot() # results[sample] = ( temp_par, temp_err, scan ) results[sample] = (temp_par, temp_err) param_index += 1 # # gMinuit.Command("CONtour 1 2 3 50") # gMinuit.SetErrorDef(1) # results['contour'] = [gMinuit.Contour(100, 0, 1)] # gMinuit.SetErrorDef(4) # results['contour'].append(gMinuit.Contour(100, 0, 1)) self.results = results
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
def signalfit(data_hist, signalfunction, signalname, process): binning = HistBinsToList(data_hist) data_x = HistToList(data_hist) data_error = HistErrorList(data_hist) parfunction = signalfunction.GetNumberFreeParameters() partot = signalfunction.GetNumberFreeParameters() print partot ### the fucntion used for TMinuit def fcn(npar, gin, f, par, ifag): L = 0 # calculate likelihood, input par[0] is the N_B, par[1] is N_C, par[2] is N_L for ibin in range(len(binning)): #if (data_x[ibin] ==0): # continue bincen = binning[ibin] mu_x = 0 data = data_x[ibin] if data_error[ibin] == 0: continue #if data<0.1: # continue if signalname == "CrystalBall": if par[3] < 0: mu_x = 0 else: t = (bincen - par[2]) / (par[3]) if (par[0] < 0): t = -t absAlpha = abs(par[0]) if (t >= -absAlpha): mu_x = par[4] * exp(-0.5 * t * t) else: nDivAlpha = par[1] / absAlpha AA = exp(-0.5 * absAlpha * absAlpha) B = nDivAlpha - absAlpha arg = nDivAlpha / (B - t) mu_x = par[4] * (arg**par[1]) if signalname == "CrystalBallGaus": if par[3] < 0: mu_x = 0 else: t = (bincen - par[2]) / (par[3]) if (par[0] < 0): t = -t absAlpha = abs(par[0]) if (t >= -absAlpha): mu_x = par[4] * exp(-0.5 * t * t + exp(-(bincen - par[5])**2 / (2 * par[6]**2))) else: nDivAlpha = par[1] / absAlpha AA = exp(-0.5 * absAlpha * absAlpha) B = nDivAlpha - absAlpha arg = nDivAlpha / (B - t) mu_x = par[4] * (arg**par[1] + exp(-(bincen - par[5])**2 / (2 * par[6]**2))) #print mu_x, data, data_error[ibin] #L = L + mu_x - data*log(mu_x) L = L + ((mu_x - data) / data_error[ibin])**2 f[0] = L # initialize the TMinuit object arglist_p = 10 * [0] arglist = array.array('d') arglist.fromlist(arglist_p) ierflag = Long(0) maxiter = 1000000000 arglist_p = [1] gMinuit = TMinuit(partot) gMinuit.mnexcm('SET PRIntout', arglist, 0, ierflag) gMinuit.SetPrintLevel(1) gMinuit.SetErrorDef(1.0) gMinuit.SetFCN(fcn) arglist_p = [2] arglist = array.array('d') arglist.fromlist(arglist_p) gMinuit.mnexcm('SET STRategy', arglist, 1, ierflag) arglist_p = [maxiter, 0.0000001] arglist = array.array('d') arglist.fromlist(arglist_p) gMinuit.mnexcm('MIGrad', arglist, 2, ierflag) gMinuit.SetMaxIterations(maxiter) # initialize fitting the variables vstart = [125.0] * partot step = [0.1] * partot upper = [1000000] * partot lower = [-100] * partot varname = [] lower[3] = 0 lower[4] = 0 lower[1] = 0 vstart[4] = data_hist.Integral() if process == "signal": vstart[2] = 125 lower[2] = 110 upper[2] = 140 vstart[3] = 10 lower[3] = 2 upper[3] = 25 if len(vstart) > 5: vstart[5] = 125 lower[5] = 110 upper[5] = 140 vstart[6] = 10 lower[6] = 5 upper[6] = 20 if process == "z": vstart[2] = 90 lower[2] = 70 upper[2] = 110 vstart[3] = 10 lower[3] = 2 upper[3] = 30 if len(vstart) > 5: vstart[5] = 90 lower[5] = 70 upper[5] = 110 vstart[6] = 10 lower[6] = 2 upper[6] = 30 for i in range(parfunction): varname.append("p" + str(i)) for i in range(partot): gMinuit.mnparm(i, varname[i], vstart[i], step[i], lower[i], upper[i], ierflag) # fitting procedure migradstat = gMinuit.Command('MIGrad ' + str(maxiter) + ' ' + str(0.001)) #improvestat = gMinuit.Command('IMProve ' + str(maxiter) + ' ' + str(0.01)) for i in range(partot): arglist_p.append(i + 1) arglist = array.array('d') arglist.fromlist(arglist_p) gMinuit.mnmnos() # get fitting parameters fitval_p = [Double(0)] * partot fiterr_p = [Double(0)] * partot errup_p = [Double(0)] * partot errdown_p = [Double(0)] * partot eparab_p = [Double(0)] * partot gcc_p = [Double(0)] * partot fmin_p = [Double(0)] fedm_p = [Double(0)] errdef_p = [Double(0)] npari_p = Long(0) nparx_p = Long(0) istat_p = Long(0) fitval = array.array('d') fiterr = array.array('d') errup = array.array('d') errdown = array.array('d') eparab = array.array('d') gcc = array.array('d') for i in range(partot): gMinuit.GetParameter(i, fitval_p[i], fiterr_p[i]) fitval.append(fitval_p[i]) fiterr.append(fiterr_p[i]) errup.append(errup_p[i]) errdown.append(errdown_p[i]) eparab.append(eparab_p[i]) gcc.append(gcc_p[i]) gMinuit.mnstat(fmin_p[0], fedm_p[0], errdef_p[0], npari_p, nparx_p, istat_p) for p in range(signalfunction.GetNumberFreeParameters()): signalfunction.SetParameter(p, fitval[p]) print "fit uncert", fiterr_p[p] signalfunction.SetChisquare(fmin_p[0]) print fmin_p[0] return fitval[partot - 1], fitval[partot - 2]
def bkgfit(data_hist, bkgfunction, bkgname, doFloatZ=False, signal_hist=None, z_hist=None): isBkgPlusZFit = False isSpuriousFit = False binning = HistBinsToList(data_hist) data_x = HistToList(data_hist) data_error = HistErrorList(data_hist) z_x = [] signal_x = [] if z_hist != None: isBkgPlusZFit = True z_x = HistToList(z_hist) if signal_hist != None: isSpuriousFit = True signal_x = HistToList(signal_hist) parfunction = bkgfunction.GetNumberFreeParameters() partot = bkgfunction.GetNumberFreeParameters() + 2 ### the fucntion used for TMinuit def fcn(npar, gin, f, par, ifag): L = 0 # calculate likelihood, input par[0] is the N_B, par[1] is N_C, par[2] is N_L for ibin in range(len(binning)): if (data_x[ibin] < 0.5): continue bincen = binning[ibin] bkg = 0 data = data_x[ibin] if bkgname == "BernsteinO2": bkg = (par[0] * (1 - (bincen - fit_start) / fit_range)**2 + 2 * par[1] * (1 - (bincen - fit_start) / fit_range) * ((bincen - fit_start) / fit_range) + par[2] * ((bincen - fit_start) / fit_range)**2) if bkgname == "BernsteinO3": bkg = par[0] * (1 - ( (bincen - fit_start) / fit_range))**3 + par[1] * ( 3 * ((bincen - fit_start) / fit_range) * (1 - ((bincen - fit_start) / fit_range))**2) + par[2] * ( 3 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))) + par[3] * ( (bincen - fit_start) / fit_range)**3 if bkgname == "BernsteinO4": bkg = par[0] * (1 - ( (bincen - fit_start) / fit_range))**4 + par[1] * ( 4 * ((bincen - fit_start) / fit_range) * (1 - ((bincen - fit_start) / fit_range))**3) + par[2] * ( 6 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))**2 ) + par[3] * ( 4 * ((bincen - fit_start) / fit_range)**3 * (1 - ((bincen - fit_start) / fit_range))) + par[4] * ( (bincen - fit_start) / fit_range)**4 if bkgname == "BernsteinO5": bkg = par[0] * (1 - ( (bincen - fit_start) / fit_range))**5 + par[1] * (5 * ( (bincen - fit_start) / fit_range) * (1 - ( (bincen - fit_start) / fit_range))**4) + par[2] * ( 10 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))**3 ) + par[3] * (10 * ( (bincen - fit_start) / fit_range)**3 * (1 - ( (bincen - fit_start) / fit_range ))**2) + par[4] * (5 * ( (bincen - fit_start) / fit_range)**4 * (1 - ((bincen - fit_start) / fit_range))) + par[5] * ( (bincen - fit_start) / fit_range)**5 if bkgname == "BernsteinO6": bkg = (par[0] * (1 - ((bincen - fit_start) / fit_range))**6 + par[1] * (6 * ((bincen - fit_start) / fit_range)**1 * (1 - ((bincen - fit_start) / fit_range))**5) + par[2] * (15 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))**4) + par[3] * (20 * ((bincen - fit_start) / fit_range)**3 * (1 - ((bincen - fit_start) / fit_range))**3) + par[4] * (15 * ((bincen - fit_start) / fit_range)**4 * (1 - ((bincen - fit_start) / fit_range))**2) + par[5] * (6 * ((bincen - fit_start) / fit_range)**5 * (1 - ((bincen - fit_start) / fit_range))**1) + par[6] * ((bincen - fit_start) / fit_range)**6) if bkgname == "ExpoBernsteinO2": try: bkg = exp(par[0] * (bincen - fit_start) / fit_range) * ( par[1] * (1 - (bincen - fit_start) / fit_range)**2 + 2 * par[2] * (1 - (bincen - fit_start) / fit_range) * ((bincen - fit_start) / fit_range) + par[3] * ((bincen - fit_start) / fit_range)**2) except OverflowError: bkg = 0 if bkgname == "ExpoBernsteinO3": try: bkg = exp(par[0] * (bincen - fit_start) / fit_range) * ( par[1] * (1 - ((bincen - fit_start) / fit_range))**3 + par[2] * (3 * ((bincen - fit_start) / fit_range) * (1 - ((bincen - fit_start) / fit_range))**2) + par[3] * (3 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))) + par[4] * ((bincen - fit_start) / fit_range)**3) except OverflowError: bkg = 0 if bkgname == "ExpoBernsteinO4": try: bkg = exp(par[0] * (bincen - fit_start) / fit_range) * ( par[1] * (1 - ((bincen - fit_start) / fit_range))**4 + par[2] * (4 * ((bincen - fit_start) / fit_range) * (1 - ((bincen - fit_start) / fit_range))**3) + par[3] * (6 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))**2) + par[4] * (4 * ((bincen - fit_start) / fit_range)**3 * (1 - ((bincen - fit_start) / fit_range))) + par[5] * ((bincen - fit_start) / fit_range)**4) except OverflowError: bkg = 0 if bkgname == "ExpoBernsteinO5": try: bkg = exp(par[0] * (bincen - fit_start) / fit_range) * ( par[1] * (1 - ((bincen - fit_start) / fit_range))**5 + par[2] * (5 * ((bincen - fit_start) / fit_range) * (1 - ((bincen - fit_start) / fit_range))**4) + par[3] * (10 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))**3) + par[4] * (10 * ((bincen - fit_start) / fit_range)**3 * (1 - ((bincen - fit_start) / fit_range))**2) + par[5] * (5 * ((bincen - fit_start) / fit_range)**4 * (1 - ((bincen - fit_start) / fit_range))) + par[6] * ((bincen - fit_start) / fit_range)**5) except OverflowError: bkg = 0 if bkgname == "ExpoPolO2": bkg = exp(-(par[0] + par[1] * ((bincen - fit_start) / fit_range) + par[2] * ((bincen - fit_start) / fit_range)**2)) if bkgname == "ExpoPolO3": bkg = exp(-(par[0] + par[1] * ((bincen - fit_start) / fit_range) + par[2] * ((bincen - fit_start) / fit_range)**2 + par[3] * ((bincen - fit_start) / fit_range)**3)) if bkgname == "ExpoPolO4": bkg = exp(-(par[0] + par[1] * ((bincen - fit_start) / fit_range) + par[2] * ((bincen - fit_start) / fit_range)**2 + par[3] * ((bincen - fit_start) / fit_range)**3 + par[4] * ((bincen - fit_start) / fit_range)**4)) mu_x = bkg #if isBkgPlusZFit: # mu_x = mu_x + (par[partot-1] *z_x[ibin]) if isSpuriousFit: mu_x = mu_x + par[partot - 2] * signal_x[ibin] #L = L + mu_x - data*log(mu_x) L = L + ((mu_x - data) / data_error[ibin])**2 f[0] = L # initialize the TMinuit object arglist_p = 10 * [0] arglist = array.array('d') arglist.fromlist(arglist_p) ierflag = Long(0) maxiter = 1000000000 arglist_p = [1] gMinuit = TMinuit(partot) gMinuit.mnexcm('SET PRIntout', arglist, 0, ierflag) gMinuit.SetPrintLevel(1) gMinuit.SetErrorDef(1.0) gMinuit.SetFCN(fcn) arglist_p = [2] arglist = array.array('d') arglist.fromlist(arglist_p) gMinuit.mnexcm('SET STRategy', arglist, 1, ierflag) arglist_p = [maxiter, 0.0000001] arglist = array.array('d') arglist.fromlist(arglist_p) gMinuit.mnexcm('MIGrad', arglist, 2, ierflag) gMinuit.SetMaxIterations(maxiter) # initialize fitting the variables vstart = [100.0] * partot # start alpha_z with 1 vstart[partot - 1] = 1.0 vstart[partot - 2] = 0 step = [0.1] * partot upper = [100000] * partot lower = [0.1] * partot varname = [] if "ExpoPol" in bkgname: upper = [1000] * partot lower = [-1000] * partot if "ExpoBernstein" in bkgname: vstart[0] = -1 upper[0] = 0 lower[0] = -10 for i in range(parfunction): varname.append("p" + str(i)) varname.append("alpha_sig") varname.append("alpha_z") if doFloatZ: vstart[partot - 1] = 1.0 upper[partot - 1] = 2 lower[partot - 1] = 0 step[partot - 1] = 0.01 if isSpuriousFit: upper[partot - 2] = 10.0 lower[partot - 2] = -10.0 step[partot - 2] = 0.1 vstart[partot - 2] = 1 for i in range(partot): gMinuit.mnparm(i, varname[i], vstart[i], step[i], lower[i], upper[i], ierflag) if not isSpuriousFit: vstart[partot - 2] = 0 gMinuit.FixParameter(partot - 2) if not doFloatZ: lower[partot - 1] = 1 upper[partot - 1] = 1 gMinuit.FixParameter(partot - 1) if not isBkgPlusZFit: vstart[partot - 1] = 0.0 gMinuit.FixParameter(partot - 1) # fitting procedure migradstat = gMinuit.Command('MIGrad ' + str(maxiter) + ' ' + str(0.001)) improvestat = gMinuit.Command('IMProve ' + str(maxiter) + ' ' + str(0.01)) for i in range(partot): arglist_p.append(i + 1) arglist = array.array('d') arglist.fromlist(arglist_p) #gMinuit.mnmnos() # get fitting parameters fitval_p = [Double(0)] * partot fiterr_p = [Double(0)] * partot errup_p = [Double(0)] * partot errdown_p = [Double(0)] * partot eparab_p = [Double(0)] * partot gcc_p = [Double(0)] * partot fmin_p = [Double(0)] fedm_p = [Double(0)] errdef_p = [Double(0)] npari_p = Long(0) nparx_p = Long(0) istat_p = Long(0) fitval = array.array('d') fiterr = array.array('d') errup = array.array('d') errdown = array.array('d') eparab = array.array('d') gcc = array.array('d') for i in range(partot): gMinuit.GetParameter(i, fitval_p[i], fiterr_p[i]) fitval.append(fitval_p[i]) fiterr.append(fiterr_p[i]) errup.append(errup_p[i]) errdown.append(errdown_p[i]) eparab.append(eparab_p[i]) gcc.append(gcc_p[i]) gMinuit.mnstat(fmin_p[0], fedm_p[0], errdef_p[0], npari_p, nparx_p, istat_p) for p in range(bkgfunction.GetNumberFreeParameters()): bkgfunction.SetParameter(p, fitval[p]) print "fit uncert", fiterr_p[p] bkgfunction.SetChisquare(fmin_p[0]) return fitval[partot - 1], fitval[partot - 2]
class MinimizerROOTTMinuit(MinimizerBase): def __init__(self, parameter_names, parameter_values, parameter_errors, function_to_minimize, tolerance=1e-9, errordef=MinimizerBase.ERRORDEF_CHI2, strategy=1): self._strategy = strategy self._par_bounds = np.array([None] * len(parameter_names)) self._par_fixed = np.array([False] * len(parameter_names)) self.reset() # sets self.__gMinuit and caches to None super(MinimizerROOTTMinuit, self).__init__(parameter_names=parameter_names, parameter_values=parameter_values, parameter_errors=parameter_errors, function_to_minimize=function_to_minimize, tolerance=tolerance, errordef=errordef) # -- private methods def _save_state(self): if self._par_val is None: self._save_state_dict["par_val"] = self._par_val else: self._save_state_dict["par_val"] = np.array(self._par_val) if self._par_err is None: self._save_state_dict["par_err"] = self._par_err else: self._save_state_dict["par_err"] = np.array(self._par_err) self._save_state_dict['par_fixed'] = np.array(self._par_fixed) self._save_state_dict['gMinuit'] = self.__gMinuit super(MinimizerROOTTMinuit, self)._save_state() def _load_state(self): self.reset() self._par_val = self._save_state_dict["par_val"] if self._par_val is not None: self._par_val = np.array(self._par_val) self._par_err = self._save_state_dict["par_err"] if self._par_err is not None: self._par_err = np.array(self._par_err) self._par_fixed = np.array(self._save_state_dict['par_fixed']) self.__gMinuit = self._save_state_dict['gMinuit'] # call the function to propagate the changes to the nexus: self._func_handle(*self.parameter_values) super(MinimizerROOTTMinuit, self)._load_state() def _recreate_gMinuit(self): self.__gMinuit = TMinuit(self.num_pars) self.__gMinuit.SetPrintLevel(-1) self.__gMinuit.mncomd("SET STRATEGY {}".format(self._strategy), ctypes.c_int(0)) self.__gMinuit.SetFCN(self._minuit_fcn) self.__gMinuit.SetErrorDef(self._err_def) # set gMinuit parameters error_code = ctypes.c_int(0) for _pid, (_pn, _pv, _pe) in enumerate( zip(self._par_names, self._par_val, self._par_err)): self.__gMinuit.mnparm(_pid, _pn, _pv, 0.1 * _pe, 0, 0, error_code) err_code = ctypes.c_int(0) # set fixed parameters for _par_id, _pf in enumerate(self._par_fixed): if _pf: self.__gMinuit.mnfixp(_par_id, err_code) # set parameter limits for _par_id, _pb in enumerate(self._par_bounds): if _pb is not None: _lo_lim, _up_lim = _pb self.__gMinuit.mnexcm( "SET LIM", arr('d', [_par_id + 1, _lo_lim, _up_lim]), 3, error_code) def _get_gMinuit(self): if self.__gMinuit is None: self._recreate_gMinuit() return self.__gMinuit def _migrad(self, max_calls=6000): # need to set the FCN explicitly before every call self._get_gMinuit().SetFCN(self._minuit_fcn) error_code = ctypes.c_int(0) self._get_gMinuit().mnexcm("MIGRAD", arr('d', [max_calls, self.tolerance]), 2, error_code) def _minuit_fcn(self, number_of_parameters, derivatives, f, parameters, internal_flag): """ This is actually a function called in *ROOT* and acting as a C wrapper for our `FCN`, which is implemented in Python. This function is called by `Minuit` several times during a fitters. It doesn't return anything but modifies one of its arguments (*f*). This is *ugly*, but it's how *ROOT*'s ``TMinuit`` works. Its argument structure is fixed and determined by `Minuit`: **number_of_parameters** : int The number of parameters of the current fitters **derivatives** : C array If the user chooses to calculate the first derivative of the function inside the `FCN`, this value should be written here. This interface to `Minuit` ignores this derivative, however, so calculating this inside the `FCN` has no effect (yet). **f** : C array The desired function value is in f[0] after execution. **parameters** : C array A C array of parameters. Is cast to a Python list **internal_flag** : int A flag allowing for different behaviour of the function. Can be any integer from 1 (initial run) to 4(normal run). See `Minuit`'s specification. """ # Retrieve the parameters from the C side of ROOT and # store them in a Python list -- resource-intensive # for many calls, but can't be improved (yet?) parameter_list = np.frombuffer(parameters, dtype=float, count=self.num_pars) # call the Python implementation of FCN. f[0] = self._func_wrapper(*parameter_list) def _calculate_asymmetric_parameter_errors(self): self._get_gMinuit().mnmnos() _asymm_par_errs = np.zeros(shape=(self.num_pars, 2)) for _n in range(self.num_pars): _number = Long(_n) _eplus = ctypes.c_double(0) _eminus = ctypes.c_double(0) _eparab = ctypes.c_double(0) _gcc = ctypes.c_double(0) self._get_gMinuit().mnerrs(_number, _eplus, _eminus, _eparab, _gcc) _asymm_par_errs[_n, 0] = _eminus.value _asymm_par_errs[_n, 1] = _eplus.value self.minimize() return _asymm_par_errs def _get_fit_info(self, info): '''Retrieves other info from `Minuit`. **info** : string Information about the fit to retrieve. This can be any of the following: - ``'fcn'``: `FCN` value at minimum, - ``'edm'``: estimated distance to minimum - ``'err_def'``: `Minuit` error matrix status code - ``'status_code'``: `Minuit` general status code ''' # declare vars in which to retrieve other info fcn_at_min = ctypes.c_double(0) edm = ctypes.c_double(0) err_def = ctypes.c_double(0) n_var_param = ctypes.c_int(0) n_tot_param = ctypes.c_int(0) status_code = ctypes.c_int(0) # Tell TMinuit to update the variables declared above self.__gMinuit.mnstat(fcn_at_min, edm, err_def, n_var_param, n_tot_param, status_code) if info == 'fcn': return fcn_at_min.value elif info == 'edm': return edm.value elif info == 'err_def': return err_def.value elif info == 'status_code': return status_code.value else: raise ValueError("Unknown fit info: %s" % info) # -- public properties @property def hessian(self): if not self.did_fit: return None if self._hessian is None: _submat = self._remove_zeroes_for_fixed(self.cov_mat) _submat_inv = 2.0 * self.errordef * np.linalg.inv(_submat) self._hessian = self._fill_in_zeroes_for_fixed(_submat_inv) return self._hessian.copy() @property def cov_mat(self): if not self.did_fit: return None if self._par_cov_mat is None: _n_pars_total = self.num_pars _tmp_mat_array = arr('d', [0.0] * (_n_pars_total**2)) # get parameter covariance matrix from TMinuit self._get_gMinuit().mnemat(_tmp_mat_array, _n_pars_total) # reshape into 2D array _sub_cov_mat = np.asarray(np.reshape( _tmp_mat_array, (_n_pars_total, _n_pars_total)), dtype=np.float) _num_pars_free = np.sum(np.invert(self._par_fixed)) _sub_cov_mat = _sub_cov_mat[:_num_pars_free, :_num_pars_free] self._par_cov_mat = self._fill_in_zeroes_for_fixed(_sub_cov_mat) return self._par_cov_mat.copy() @property def hessian_inv(self): if not self.did_fit: return None if self._hessian_inv is None: self._hessian_inv = self.cov_mat / (2.0 * self.errordef) return self._hessian_inv.copy() @property def parameter_values(self): return self._par_val.copy() @parameter_values.setter def parameter_values(self, new_values): self._par_val = np.array(new_values) self.reset() @property def parameter_errors(self): return self._par_err.copy() @parameter_errors.setter def parameter_errors(self, new_errors): _err_array = np.array(new_errors) if not np.all(_err_array > 0): raise ValueError("All parameter errors must be > 0! Received: %s" % new_errors) self._par_err = _err_array self.reset() # -- private "properties" # -- public methods def reset(self): super(MinimizerROOTTMinuit, self).reset() self.__gMinuit = None def set(self, parameter_name, parameter_value): if parameter_name not in self._par_names: raise ValueError("No parameter named '%s'!" % (parameter_name, )) _par_id = self.parameter_names.index(parameter_name) self._par_val[_par_id] = parameter_value self.reset() def fix(self, parameter_name): # set local flag _par_id = self.parameter_names.index(parameter_name) if self._par_fixed[_par_id]: return # par is already fixed self._par_fixed[_par_id] = True if self.__gMinuit is not None: # also update Minuit instance err_code = ctypes.c_int(0) self.__gMinuit.mnfixp(_par_id, err_code) # self.__gMinuit.mnexcm("FIX", # arr('d', [_par_id+1]), 1, error_code) self._invalidate_cache() def is_fixed(self, parameter_name): _par_id = self.parameter_names.index(parameter_name) return self._par_fixed[_par_id] def release(self, parameter_name): # set local flag _par_id = self.parameter_names.index(parameter_name) if not self._par_fixed[_par_id]: return # par is already released self._par_fixed[_par_id] = False if self.__gMinuit is not None: # also update Minuit instance self.__gMinuit.mnfree(-_par_id - 1) # self.__gMinuit.mnexcm("RELEASE", # arr('d', [_par_id+1]), 1, error_code) self._invalidate_cache() def limit(self, parameter_name, parameter_bounds): assert len(parameter_bounds) == 2 if parameter_bounds[0] is None or parameter_bounds[1] is None: raise MinimizerROOTTMinuitException( "Cannot define one-sided parameter limits when using the ROOT TMinuit Minimizer." ) # set local flag _par_id = self.parameter_names.index(parameter_name) if self._par_bounds[_par_id] == parameter_bounds: return # same limits already set if self._par_val[_par_id] < parameter_bounds[0]: self.set(parameter_name, parameter_bounds[0]) elif self._par_val[_par_id] > parameter_bounds[1]: self.set(parameter_name, parameter_bounds[1]) self._par_bounds[_par_id] = parameter_bounds if self.__gMinuit is not None: _lo_lim, _up_lim = self._par_bounds[_par_id] # also update Minuit instance error_code = ctypes.c_int(0) self.__gMinuit.mnexcm("SET LIM", arr('d', [_par_id + 1, _lo_lim, _up_lim]), 3, error_code) self._did_fit = False self._invalidate_cache() def unlimit(self, parameter_name): # set local flag _par_id = self.parameter_names.index(parameter_name) if self._par_bounds[_par_id] is None: return # parameter is already unlimited self._par_bounds[_par_id] = None if self.__gMinuit is not None: # also update Minuit instance error_code = ctypes.c_int(0) self.__gMinuit.mnexcm("SET LIM", arr('d', [_par_id + 1]), 1, error_code) self._did_fit = False self._invalidate_cache() def minimize(self, max_calls=6000): if np.all(self._par_fixed): raise MinimizerROOTTMinuitException( "Cannot perform a fit if all parameters are fixed!") self._migrad(max_calls=max_calls) # retrieve fitters parameters self._par_val = np.zeros(self.num_pars) self._par_err = np.zeros(self.num_pars) _pv, _pe = ctypes.c_double(0), ctypes.c_double(0) for _par_id in six.moves.range(0, self.num_pars): self.__gMinuit.GetParameter(_par_id, _pv, _pe) # retrieve fit result self._par_val[_par_id] = _pv.value self._par_err[_par_id] = _pe.value self._did_fit = True def contour(self, parameter_name_1, parameter_name_2, sigma=1.0, **minimizer_contour_kwargs): if not self.did_fit: raise MinimizerROOTTMinuitException( "Need to perform a fit before calling contour()!") _numpoints = minimizer_contour_kwargs.pop("numpoints", 100) if minimizer_contour_kwargs: raise MinimizerROOTTMinuitException( "Unknown parameters: {}".format(minimizer_contour_kwargs)) _id_1 = self.parameter_names.index(parameter_name_1) _id_2 = self.parameter_names.index(parameter_name_2) self.__gMinuit.SetErrorDef(sigma**2) _t_graph = self.__gMinuit.Contour(_numpoints, _id_1, _id_2) self.__gMinuit.SetErrorDef(self._err_def) _x_buffer, _y_buffer = _t_graph.GetX(), _t_graph.GetY() _N = _t_graph.GetN() _x = np.frombuffer(_x_buffer, dtype=float, count=_N) _y = np.frombuffer(_y_buffer, dtype=float, count=_N) self._func_handle(*self.parameter_values) return ContourFactory.create_xy_contour((_x, _y), sigma) def profile(self, parameter_name, bins=21, bound=2, args=None, subtract_min=False): if not self.did_fit: raise MinimizerROOTTMinuitException( "Need to perform a fit before calling profile()!") MAX_ITERATIONS = 6000 _error_code = ctypes.c_int(0) _minuit_id = Long(self.parameter_names.index(parameter_name) + 1) _par_min = ctypes.c_double(0) _par_err = ctypes.c_double(0) self.__gMinuit.GetParameter(_minuit_id - 1, _par_min, _par_err) _par_min = _par_min.value _par_err = _par_err.value _x = np.linspace(start=_par_min - bound * _par_err, stop=_par_min + bound * _par_err, num=bins, endpoint=True) self.__gMinuit.mnexcm("FIX", arr('d', [_minuit_id]), 1, _error_code) _y = np.zeros(bins) for i in range(bins): self.__gMinuit.mnexcm("SET PAR", arr('d', [_minuit_id, Double(_x[i])]), 2, _error_code) self.__gMinuit.mnexcm("MIGRAD", arr('d', [MAX_ITERATIONS, self.tolerance]), 2, _error_code) _y[i] = self._get_fit_info("fcn") self.__gMinuit.mnexcm("RELEASE", arr('d', [_minuit_id]), 1, _error_code) self._migrad() self.__gMinuit.mnexcm("SET PAR", arr('d', [_minuit_id, Double(_par_min)]), 2, _error_code) if subtract_min: _y -= self.function_value return np.asarray((_x, _y))
def main(): global freqs_hz, pv, sigq, sigu parser = OptionParser(usage) parser.add_option("-r", "--rm", type="float", dest="rm", default=0., help="Estimate of the RM") parser.add_option("-n", "--norm", action="store_true", dest="norm", default=False, help="Verbose mode") parser.add_option("-b", "--baseline", action="store_true", dest="baseline", default=False, help="Fit for a baseline") parser.add_option("-f", "--nofit", action="store_true", dest="nofit", default=False, help="Don't fit for the RM with Minuit") parser.add_option("-c", "--clean", action="store_true", dest="clean", default=False, help="Clean the baseline") (opts, args) = parser.parse_args() #if opts.help: # print full_usage arch = psrchive.Archive_load(sys.argv[1]) arch.tscrunch() arch.bscrunch(2) #arch.fscrunch(2) arch.dedisperse() arch.remove_baseline() arch.convert_state('Stokes') arch.centre_max_bin() #arch.rotate(0.5) data = arch.get_data() MJD = arch.get_first_Integration().get_epoch().strtempo() w = arch.get_weights().sum(0) I = data[:,0,:,:].mean(0) Q = data[:,1,:,:].mean(0) U = data[:,2,:,:].mean(0) #V = data[:,3,:,:].mean(0) I = ma.masked_array(I) Q = ma.masked_array(Q) U = ma.masked_array(U) #V = ma.masked_array(V) I[w==0] = np.ma.masked Q[w==0] = np.ma.masked U[w==0] = np.ma.masked # Get Freqs nchan = arch.get_nchan() freqs = np.zeros(nchan) for i in range(nchan): freqs[i] = arch.get_Profile(0,0,i).get_centre_frequency() ## Determine phase range arch.fscrunch() arch.tscrunch() arch.pscrunch() prof = arch.get_Profile(0,0,0) #print prof.get_amps() lowbin, hibin = get_pulse_range(arch) #lowbin, hibin = 458,531 #lowbin, hibin = 20,120 if opts.clean: for n in range(nchan): if n%20: continue baseline_idx = np.append(np.arange(0, lowbin), np.arange(hibin, arch.get_nbin())) baseline_dat = I[n][baseline_idx] print n, baseline_idx, baseline_dat poly = np.polyfit(baseline_idx, baseline_dat, 1) p = np.poly1d(poly) pylab.plot(I[n]) I[n] = I[n] - p(np.arange(0, arch.get_nbin())) pylab.plot(I[n]) pylab.show() for ii, wx in enumerate(w): I[ii] *= wx/np.max(w) Q[ii] *= wx/np.max(w) U[ii] *= wx/np.max(w) #V[ii] *= wx if opts.norm: print "Will normalize by the rms of the noise" scale = np.std(I, axis=1) / np.max(np.std(I, axis=1)) for ii, s in enumerate(scale): I[ii,:] /= s Q[ii,:] /= s U[ii,:] /= s #Q = Q / scale #U = U / scale freq_lo = freqs[0] freq_hi = freqs[-1] lowphase = lowbin / float(arch.get_nbin()) hiphase = hibin / float(arch.get_nbin()) #lowphase= 0.48 #hiphase = 0.515 #lowphase = 0.515 #hiphase = 0.54 print "Pulse phase window: ", lowphase, hiphase #pylab.plot(np.sum(I), axis) #pylab.show() # 2D plot f = pylab.figure() pylab.subplot(321) #print np.sum(I,axis=0) #pylab.plot(np.sum(I, axis=0)) pylab.plot(prof.get_amps()) pylab.xlim([0, arch.get_nbin()]) f.text( .5, 0.95, r'%s'%os.path.split(sys.argv[1])[1], horizontalalignment='center') pylab.subplot(323) pylab.axis([0,1,freq_lo,freq_hi]) pylab.imshow(I,extent=(0,1,freq_lo,freq_hi), origin='lower', aspect='auto', vmax=I.max()/1.5, vmin=I.min()/1.5) pylab.xlabel('Pulse phase') pylab.ylabel('Frequency (MHz)') # Compute errors of Q and U sigq = np.std(Q[:,lowbin:hibin], axis=1) / np.sqrt(hibin-lowbin) sigu = np.std(U[:,lowbin:hibin], axis=1) / np.sqrt(hibin-lowbin) #sigq = np.std(Q[:,:], axis=1) #sigu = np.std(U[:,:], axis=1) pylab.plot([lowphase,lowphase], [freq_lo, freq_hi], 'r--') pylab.plot([hiphase,hiphase], [freq_lo, freq_hi], 'r--') freqs_hz = freqs * 1e6 # Select phase range lowbin = int(lowphase * arch.get_nbin()) hibin = int(hiphase * arch.get_nbin()) dat = Q + 1j * U pv = dat[:,lowbin:hibin].mean(1) # Select phase range and average #rhos = np.arange(-90000, 90000, 10) rhos = np.arange(-2000, 2000, 1) res = np.absolute(getL(rhos, pv)) # Plot I f(RM) pylab.subplot(324) pylab.plot(rhos, res, 'b-') pylab.xlabel('RM') pylab.ylabel('I') # Plot Q pylab.subplot(325) pylab.errorbar(freqs, np.real(pv), yerr=sigq, ls='None') pylab.plot(freqs, np.real(pv), 'bo') pylab.xlabel('Frequency (MHz)') pylab.ylabel('Q') # Plot U pylab.subplot(326) pylab.errorbar(freqs, np.imag(pv), yerr=sigu, ls='None') pylab.plot(freqs, np.imag(pv), 'bo') pylab.xlabel('Frequency (MHz)') pylab.ylabel('U') # Should get the initial RM if opts.rm: initial_RM = -opts.rm else: initial_RM = -rhos[np.argmax(res)] print "Will use initial RM", initial_RM initial_L = getL(np.array([initial_RM]), pv) / (hibin-lowbin) if not opts.nofit: # Minuit part gMinuit = TMinuit(5) #gMinuit.SetErrorDef(1) # 1-Sigma Error gMinuit.SetErrorDef(4) # 2-Sigma Error gMinuit.SetFCN(fcn) arglist = arr('d', 2*[0.01]) ierflg = Long(0) #arglist[0] = 1 #gMinuit.mnexcm("SET ERR", arglist ,1,ierflg) # Set initial parameter values for fit vstart = arr( 'd', (np.real(initial_L), np.imag(initial_L), initial_RM) ) #vstart = arr( 'd', (2*np.real(initial_L), 2*np.imag(initial_L), initial_RM) ) # Set step size for fit step = arr( 'd', (0.001, 0.001, 0.001) ) # Define the parameters for the fit gMinuit.mnparm(0, "Qf", vstart[0], step[0], 0,0,ierflg) gMinuit.mnparm(1, "Uf", vstart[1], step[1], 0,0,ierflg) gMinuit.mnparm(2, "RM", vstart[2], step[2], 0,0,ierflg) gMinuit.mnparm(3, "a", 0.0, step[2], 0,0,ierflg) gMinuit.mnparm(4, "b", 0.0, step[2], 0,0,ierflg) #gMinuit.FixParameter(2) if not opts.baseline: gMinuit.FixParameter(3) gMinuit.FixParameter(4) arglist[0] = 6000 # Number of calls to FCN before giving up. arglist[1] = 0.1 # Tolerance gMinuit.mnexcm("MIGRAD", arglist ,2,ierflg) amin, edm, errdef = Double(0.18), Double(0.19), Double(1.0) nvpar, nparx, icstat = Long(1), Long(2), Long(3) gMinuit.mnstat(amin,edm,errdef,nvpar,nparx,icstat); gMinuit.mnmnos(); gMinuit.mnprin(3,amin); finalQ, finalQ_err = Double(0), Double(0) finalU, finalU_err = Double(0), Double(0) finalRM, finalRM_err = Double(0), Double(0) A, A_err = Double(0), Double(0) B, B_err = Double(0), Double(0) print "\n\n" gMinuit.GetParameter(0, finalQ, finalQ_err) gMinuit.GetParameter(1, finalU, finalU_err) gMinuit.GetParameter(2, finalRM, finalRM_err) gMinuit.GetParameter(3, A, A_err) gMinuit.GetParameter(4, B, B_err) finalRM *= -1. print finalQ, finalU line1 = "Final RM: %.1f"%(finalRM) + "(+/-) %.1f "%(finalRM_err) + " (2-sig) " line2 = "Chi**2r = %.2f"%(get_chi2((finalQ,finalU,-finalRM,A,B)) / ( 2*pv.size - 3 - 1 -2)) print line1 print line2 f.text( .5, 0.92, line1 + line2, horizontalalignment='center') print MJD, np.mean(freqs), finalRM, finalRM_err, get_chi2((finalQ,finalU,finalRM,A,B)), 2*pv.count() # Plot best fit model finalRM *= -1. pylab.subplot(325) pylab.plot(freqs, np.real( -A+getPV(finalQ+1j*finalU, finalRM) )) pylab.subplot(326) pylab.plot(freqs, np.imag( -B+getPV(finalQ+1j*finalU, finalRM) )) pylab.show()
class MinimizerROOTTMinuit(MinimizerBase): def __init__(self, parameter_names, parameter_values, parameter_errors, function_to_minimize, strategy = 1): self._par_names = parameter_names self._strategy = strategy self._func_handle = function_to_minimize self._err_def = 1.0 self._tol = 0.001 # initialize the minimizer parameter specification self._minimizer_param_dict = {} assert len(parameter_names) == len(parameter_values) == len(parameter_errors) self._par_val = [] self._par_err = [] self._par_fixed_mask = [] self._par_limits = [] for _pn, _pv, _pe in zip(parameter_names, parameter_values, parameter_errors): self._par_val.append(_pv) self._par_err.append(_pe) self._par_fixed_mask.append(False) self._par_limits.append(None) # TODO: limits/fixed parameters self.__gMinuit = None # cache for calculations self._hessian = None self._hessian_inv = None self._fval = None self._par_cov_mat = None self._par_cor_mat = None self._par_asymm_err = None self._fmin_struct = None self._pars_contour = None self._min_result_stale = True self._printed_inf_cost_warning = False # -- private methods # def _invalidate_cache(self): # self._par_val = None # self._par_err = None # self._hessian = None # self._hessian_inv = None # self._fval = None # self._par_cov_mat = None # self._par_cor_mat = None # self._par_asymm_err_dn = None # self._par_asymm_err_up = None # self._fmin_struct = None # self._pars_contour = None def _recreate_gMinuit(self): self.__gMinuit = TMinuit(self.n_pars) self.__gMinuit.SetPrintLevel(-1) self.__gMinuit.mncomd("SET STRATEGY {}".format(self._strategy), Long(0)) self.__gMinuit.SetFCN(self._minuit_fcn) self.__gMinuit.SetErrorDef(self._err_def) # set gMinuit parameters error_code = Long(0) for _pid, (_pn, _pv, _pe) in enumerate(zip(self._par_names, self._par_val, self._par_err)): self.__gMinuit.mnparm(_pid, _pn, _pv, 0.1 * _pe, 0, 0, error_code) err_code = Long(0) # set fixed parameters for _par_id, _pf in enumerate(self._par_fixed_mask): if _pf: self.__gMinuit.mnfixp(_par_id, err_code) # set parameter limits for _par_id, _pl in enumerate(self._par_limits): if _pl is not None: _lo_lim, _up_lim = _pl self.__gMinuit.mnexcm("SET LIM", arr('d', [_par_id + 1, _lo_lim, _up_lim]), 3, error_code) def _get_gMinuit(self): if self.__gMinuit is None: self._recreate_gMinuit() return self.__gMinuit def _migrad(self, max_calls=6000): # need to set the FCN explicitly before every call self._get_gMinuit().SetFCN(self._minuit_fcn) error_code = Long(0) self._get_gMinuit().mnexcm("MIGRAD", arr('d', [max_calls, self.tolerance]), 2, error_code) def _hesse(self, max_calls=6000): # need to set the FCN explicitly before every call self._get_gMinuit().SetFCN(self._minuit_fcn) error_code = Long(0) self._get_gMinuit().mnexcm("HESSE", arr('d', [max_calls]), 1, error_code) def _minuit_fcn(self, number_of_parameters, derivatives, f, parameters, internal_flag): """ This is actually a function called in *ROOT* and acting as a C wrapper for our `FCN`, which is implemented in Python. This function is called by `Minuit` several times during a fitters. It doesn't return anything but modifies one of its arguments (*f*). This is *ugly*, but it's how *ROOT*'s ``TMinuit`` works. Its argument structure is fixed and determined by `Minuit`: **number_of_parameters** : int The number of parameters of the current fitters **derivatives** : C array If the user chooses to calculate the first derivative of the function inside the `FCN`, this value should be written here. This interface to `Minuit` ignores this derivative, however, so calculating this inside the `FCN` has no effect (yet). **f** : C array The desired function value is in f[0] after execution. **parameters** : C array A C array of parameters. Is cast to a Python list **internal_flag** : int A flag allowing for different behaviour of the function. Can be any integer from 1 (initial run) to 4(normal run). See `Minuit`'s specification. """ # Retrieve the parameters from the C side of ROOT and # store them in a Python list -- resource-intensive # for many calls, but can't be improved (yet?) parameter_list = np.frombuffer(parameters, dtype=float, count=self.n_pars) # call the Python implementation of FCN. f[0] = self._func_wrapper(*parameter_list) def _insert_zeros_for_fixed(self, submatrix): """ Takes the partial error matrix (submatrix) and adds rows and columns with 0.0 where the fixed parameters should go. """ _mat = submatrix # reduce the matrix before inserting zeros _n_pars_free = self.n_pars_free _mat = _mat[0:_n_pars_free,0:_n_pars_free] _fparam_ids = [_par_id for _par_id, _p in enumerate(self._par_fixed_mask) if _p] for _id in _fparam_ids: _mat = np.insert(np.insert(_mat, _id, 0., axis=0), _id, 0., axis=1) return _mat # -- public properties def get_fit_info(self, info): '''Retrieves other info from `Minuit`. **info** : string Information about the fit to retrieve. This can be any of the following: - ``'fcn'``: `FCN` value at minimum, - ``'edm'``: estimated distance to minimum - ``'err_def'``: `Minuit` error matrix status code - ``'status_code'``: `Minuit` general status code ''' # declare vars in which to retrieve other info fcn_at_min = Double(0) edm = Double(0) err_def = Double(0) n_var_param = Long(0) n_tot_param = Long(0) status_code = Long(0) # Tell TMinuit to update the variables declared above self.__gMinuit.mnstat(fcn_at_min, edm, err_def, n_var_param, n_tot_param, status_code) if info == 'fcn': return fcn_at_min elif info == 'edm': return edm elif info == 'err_def': return err_def elif info == 'status_code': try: return D_MATRIX_ERROR[status_code] except: return status_code @property def n_pars(self): return len(self.parameter_names) @property def n_pars_free(self): return len([_p for _p in self._par_fixed_mask if not _p]) @property def errordef(self): return self._err_def @errordef.setter def errordef(self, err_def): assert err_def > 0 self._err_def = err_def if self.__gMinuit is not None: self.__gMinuit.set_errordef(err_def) self._min_result_stale = True @property def tolerance(self): return self._tol @tolerance.setter def tolerance(self, tolerance): assert tolerance > 0 self._tol = tolerance self._min_result_stale = True @property def hessian(self): # TODO: cache this return 2.0 * self.errordef * np.linalg.inv(self.cov_mat) @property def cov_mat(self): if self._min_result_stale: raise MinimizerROOTTMinuitException("Cannot get cov_mat: Minimizer result is outdated.") if self._par_cov_mat is None: _n_pars_total = self.n_pars _n_pars_free = self.n_pars_free _tmp_mat_array = arr('d', [0.0]*(_n_pars_total**2)) # get parameter covariance matrix from TMinuit self.__gMinuit.mnemat(_tmp_mat_array, _n_pars_total) # reshape into 2D array _sub_cov_mat = np.asarray( np.reshape( _tmp_mat_array, (_n_pars_total, _n_pars_total) ) ) self._par_cov_mat = self._insert_zeros_for_fixed(_sub_cov_mat) return self._par_cov_mat @property def cor_mat(self): if self._min_result_stale: raise MinimizerROOTTMinuitException("Cannot get cor_mat: Minimizer result is outdated.") if self._par_cor_mat is None: _cov_mat = self.cov_mat # TODO: use CovMat object! # Note: for zeros on cov_mat diagonals (which occur for fixed parameters) -> overwrite with 1.0 _sqrt_diag = np.array([_err if _err>0 else 1.0 for _err in np.sqrt(np.diag(_cov_mat))]) self._par_cor_mat = np.asarray(_cov_mat) / np.outer(_sqrt_diag, _sqrt_diag) return self._par_cor_mat @property def hessian_inv(self): return self.cov_mat / 2.0 / self.errordef @property def parameter_values(self): return self._par_val @property def parameter_errors(self): return self._par_err @property def parameter_names(self): return self._par_names # -- private "properties" # -- public methods def fix(self, parameter_name): # set local flag _par_id = self.parameter_names.index(parameter_name) if self._par_fixed_mask[_par_id]: return # par is already fixed self._par_fixed_mask[_par_id] = True if self.__gMinuit is not None: # also update Minuit instance err_code = Long(0) self.__gMinuit.mnfixp(_par_id, err_code) # self.__gMinuit.mnexcm("FIX", # arr('d', [_par_id+1]), 1, error_code) self._min_result_stale = True def fix_several(self, parameter_names): for _pn in parameter_names: self.fix(_pn) def release(self, parameter_name): # set local flag _par_id = self.parameter_names.index(parameter_name) if not self._par_fixed_mask[_par_id]: return # par is already released self._par_fixed_mask[_par_id] = False if self.__gMinuit is not None: # also update Minuit instance self.__gMinuit.mnfree(-_par_id-1) # self.__gMinuit.mnexcm("RELEASE", # arr('d', [_par_id+1]), 1, error_code) self._min_result_stale = True def release_several(self, parameter_names): for _pn in parameter_names: self.release(_pn) def limit(self, parameter_name, parameter_bounds): assert len(parameter_bounds) == 2 # set local flag _par_id = self.parameter_names.index(parameter_name) if self._par_limits[_par_id] == parameter_bounds: return # same limits already set self._par_limits[_par_id] = parameter_bounds if self.__gMinuit is not None: _lo_lim, _up_lim = self._par_limits[_par_id] # also update Minuit instance error_code = Long(0) self.__gMinuit.mnexcm("SET LIM", arr('d', [_par_id+1, _lo_lim, _up_lim]), 3, error_code) self._min_result_stale = True def unlimit(self, parameter_name): # set local flag _par_id = self.parameter_names.index(parameter_name) if self._par_limits[_par_id] is None: return # parameter is already unlimited self._par_limits[_par_id] = None if self.__gMinuit is not None: # also update Minuit instance error_code = Long(0) self.__gMinuit.mnexcm("SET LIM", arr('d', [_par_id+1]), 1, error_code) self._min_result_stale = True def minimize(self, max_calls=6000): self._migrad(max_calls=max_calls) # retrieve fitters parameters self._par_val = [] self._par_err = [] _pv, _pe = Double(0), Double(0) for _par_id in six.moves.range(0, self.n_pars): self.__gMinuit.GetParameter(_par_id, _pv, _pe) # retrieve fitresult self._par_val.append(float(_pv)) self._par_err.append(float(_pe)) self._min_result_stale = False def contour(self, parameter_name_1, parameter_name_2, sigma=1.0, **minimizer_contour_kwargs): if self.__gMinuit is None: raise MinimizerROOTTMinuitException("Need to perform a fit before calling contour()!") _numpoints = minimizer_contour_kwargs.pop("numpoints", 100) if minimizer_contour_kwargs: raise MinimizerROOTTMinuitException("Unknown parameters: {}".format(minimizer_contour_kwargs)) _id_1 = self.parameter_names.index(parameter_name_1) _id_2 = self.parameter_names.index(parameter_name_2) self.__gMinuit.SetErrorDef(sigma ** 2) _t_graph = self.__gMinuit.Contour(_numpoints, _id_1, _id_2) self.__gMinuit.SetErrorDef(self._err_def) _x_buffer, _y_buffer = _t_graph.GetX(), _t_graph.GetY() _N = _t_graph.GetN() _x = np.frombuffer(_x_buffer, dtype=float, count=_N) _y = np.frombuffer(_y_buffer, dtype=float, count=_N) self._func_handle(*self.parameter_values) return ContourFactory.create_xy_contour((_x, _y), sigma) def profile(self, parameter_name, bins=21, bound=2, args=None, subtract_min=False): if self.__gMinuit is None: raise MinimizerROOTTMinuitException("Need to perform a fit before calling profile()!") MAX_ITERATIONS = 6000 _error_code = Long(0) _minuit_id = Long(self.parameter_names.index(parameter_name) + 1) _par_min = Double(0) _par_err = Double(0) self.__gMinuit.GetParameter(_minuit_id - 1, _par_min, _par_err) _x = np.linspace(start=_par_min - bound * _par_err, stop=_par_min + bound * _par_err, num=bins, endpoint=True) self.__gMinuit.mnexcm("FIX", arr('d', [_minuit_id]), 1, _error_code) _y = np.zeros(bins) for i in range(bins): self.__gMinuit.mnexcm("SET PAR", arr('d', [_minuit_id, Double(_x[i])]), 2, _error_code) self.__gMinuit.mnexcm("MIGRAD", arr('d', [MAX_ITERATIONS, self.tolerance]), 2, _error_code) _y[i] = self.get_fit_info("fcn") self.__gMinuit.mnexcm("RELEASE", arr('d', [_minuit_id]), 1, _error_code) self._migrad() self.__gMinuit.mnexcm("SET PAR", arr('d', [_minuit_id, Double(_par_min)]), 2, _error_code) return np.asarray((_x, _y))