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 __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" ) self.__minuit.SetFCN( fcn ) self.__pars= pars self.__parerrors= parerrors self.__parnames= parnames self.__setParameters() self.__ndof= ndof return
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 fit(self): numberOfParameters = len(self.samples) gMinuit = TMinuit(numberOfParameters) if self.method == "logLikelihood": # set function for minimisation gMinuit.SetFCN(self.logLikelihood) gMinuit.SetPrintLevel(-1) # Error definition: 1 for chi-squared, 0.5 for negative log likelihood gMinuit.SetErrorDef(1) # error flag for functions passed as reference.set to as 0 is no error errorFlag = Long(2) N_total = self.normalisation[self.data_label] * 2 N_min = 0 param_index = 0 for sample in self.samples: # all samples but data gMinuit.mnparm(param_index, sample, self.normalisation[sample], 10.0, N_min, N_total, errorFlag) param_index += 1 # N_signal = self.normalisation['signal'] # gMinuit.mnparm(0, "N_signal(ttbar+single_top)", N_signal, 10.0, N_min, N_total, errorFlag) # gMinuit.mnparm(1, "bkg1", 10, 10.0, N_min, N_total, errorFlag) arglist = array("d", 10 * [0.0]) # minimisation strategy: 1 standard, 2 try to improve minimum (a bit slower) arglist[0] = 2 # minimisation itself gMinuit.mnexcm("SET STR", arglist, 1, errorFlag) gMinuit.Migrad() 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) results[sample] = (temp_par, temp_err) param_index += 1 self.results = results
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 __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 fit_mu(self, background, signal): myMinuit = TMinuit(self.npar) myMinuit.SetFCN(self.fcn) gMinuit.Command('SET PRINT -1') ierflg = Long(0) myMinuit.mnparm(0, 'mu', self.start_value_mu, self.init_step_mu, self.min_mu, self.max_mu, ierflg) arglist = array('d', (0, 0)) arglist[0] = self.max_iterations arglist[1] = self.tolerance myMinuit.mnexcm("MINIMIZE", arglist, 2, ierflg) # check TMinuit status amin, edm, errdef = Double(0.), Double(0.), Double(0.) nvpar, nparx, icstat = Long(0), Long(0), Long(0) myMinuit.mnstat(amin, edm, errdef, nvpar, nparx, icstat) # get final results p, pe = Double(0), Double(0) myMinuit.GetParameter(0, p, pe) # finalPar = float(p) # finalParErr = float(pe) # print_banner('MINUIT fit completed:') # print ' fcn@minimum = %.3g' %(amin)," error code =", ierflg, " status =", icstat # print " Results: \t value error" # print ' %s: \t%10.3e +/- %.1e '%('mu', finalPar, finalParErr) return p
# --- fcn -- called by MINUIT repeatedly with varying parameters # NOTE: the function name is set via TMinuit.SetFCN def fcn(npar, deriv, f, apar, iflag): """ meaning of parameters: npar: # of parameters deriv: array of derivatives df/dp_i(x), optional f: value of function to be minimised (typically chi2 or negLogL) apar: the array of parameters iflag: internal flag: 1 at first call, 3 at the last, 4 during minimisation """ f[0] = calcChi2(npar, apar) # --> set up MINUIT myMinuit = TMinuit(npar) # initialize TMinuit with maximum of npar parameters myMinuit.setFCN(fcn) # set func to minimize arglist = arr('d', 2*[0.01])# set error definition ieflg = Long(0) arglist[0] = 1. # 1 sigma is Delta chi^2 = 1 myMinuit.mnexcm("SET ERR", arglist, 1, ierflg) #−−>Set starting values and step sizes for parameters for i in range(0, npar): #Define the parameters for the fit myMinuit.mnparm(i, name[i], vstart[i], step[i], 0, 0, ierflg) arglist[0] = 6000 # Number of calls to FCN before giving up. arglist[1] = 0.3 # Tolerance
def run_mass_fit(self, peak_scale_initial, mass=0): self.gMinuit = TMinuit(30) self.gMinuit.SetPrintLevel(-1) self.gMinuit.SetFCN(self.Fitfcn_max_likelihood) arglist = array("d", [ 0, ] * 10) ierflg = ROOT.Long(0) arglist[0] = ROOT.Double(1) # peak_scale_initial = ROOT.Double(peak_scale_initial) tmp = array("d", [ 0, ]) self.gMinuit.mnexcm("SET NOWarnings", tmp, 0, ierflg) self.gMinuit.mnexcm("SET ERR", arglist, 1, ierflg) self.gMinuit.mnparm(0, "p1", 5e-6, 1e-7, 0, 0, ierflg) self.gMinuit.mnparm(1, "p2", 10, 10, 0, 0, ierflg) self.gMinuit.mnparm(2, "p3", -5.3, 1, 0, 0, ierflg) self.gMinuit.mnparm(3, "p4", -4e-2, 1e-2, 0, 0, ierflg) self.gMinuit.mnparm(4, "p5", peak_scale_initial, peak_scale_initial / 50, 0, 0, ierflg) self.background_fit_only = [ 0, ] * len(self.data) arglist[0] = ROOT.Double(0) arglist[1] = ROOT.Double(0) #self.exclude_regions = ((2.2, 3.3),) self.gMinuit.FixParameter(2) self.gMinuit.FixParameter(3) self.gMinuit.FixParameter(4) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) self.gMinuit.Release(2) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) self.gMinuit.Release(3) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) # Find an actual best fit #self.exclude_regions = ((0, 2), (3.3, 100),) self.gMinuit.FixParameter(0) self.gMinuit.FixParameter(1) self.gMinuit.FixParameter(2) self.gMinuit.FixParameter(3) self.gMinuit.Release(4) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) self.exclude_regions = () self.gMinuit.Release(0) self.gMinuit.Release(1) self.gMinuit.Release(2) self.gMinuit.Release(3) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) best_fit_value = ROOT.Double(0) self.gMinuit.mnstat(best_fit_value, ROOT.Double(0), ROOT.Double(0), ROOT.Long(0), ROOT.Long(0), ROOT.Long(0)) #print("Best fit value", best_fit_value) # And prepare for iterating over fit values for N injected events self.gMinuit.Release(0) self.gMinuit.Release(1) self.gMinuit.Release(2) self.gMinuit.Release(3) self.exclude_regions = () #self.data_fits = no_peak_data_fits x_values = [] y_values = [] fitted_N = ROOT.Double(0) self.gMinuit.GetParameter(4, fitted_N, ROOT.Double(0)) best_fit_likelihood = self.calc_likelihood(fitted_N) step = 5 if int(mass) >= 4000: step = 1 if int(mass) >= 5000: step = 0.2 if int(mass) >= 6000: step = 0.1 if int(mass) >= 6500: step = 0.05 N = 0 while N < 5000: start_sum = sum([math.exp(-a) for a in y_values]) fit_likelihood = self.calc_likelihood(N) x_values.append(N) y_values.append(fit_likelihood - best_fit_likelihood) probabilities = [math.exp(-a) for a in y_values] end_sum = sum(probabilities) max_prob = max(probabilities) normalised_probabilities = [a / max_prob for a in probabilities] if N / step > 50 and all( [v > 0.99 for v in normalised_probabilities]): print( "Probability=1 everywhere, probably something wrong with fit" ) print(normalised_probabilities) return None, None # if new value changes total by less than 0.1%, end loop if N > 0 and (end_sum - start_sum) / start_sum < 0.0001: print("Iterated up to {0}".format(N)) break N += step self.iflag = int(ierflg) return x_values, y_values
def main(): #The first part of the script is to input the root files# canvas = TCanvas("Canvas_d", "Canvas_d", 533, 76, 1383, 852) input_datafile = TFile(args["input_data"]) EV_data = input_datafile.Get("EventCategorizer subtask 0 stats/ExpecValue") entries_data = EV_data.GetSize() print "# of Entries in Data file:", entries_data print "Experimental Data Inputed" data_arr = [] canvas = TCanvas("Canvas_mc", "Canvas_mc", 533, 76, 1383, 852) input_mcfile = TFile(args["input_mc"]) EV_mc = input_mcfile.Get( "EventCategorizer subtask 0 stats/ExpecValue_Smeared") entries_mc = EV_mc.GetSize() print "# of Entries in MC file:", entries_mc print "MC Data Inputed" mc_arr = [] bin_arr = [] for x in range(entries_data): Data_i = EV_data.GetBinContent(x) MC_i = EV_mc.GetBinContent(x) data_arr.append(Data_i) mc_arr.append(MC_i) Bin_i = EV_data.GetBinCenter(x) bin_arr.append(Bin_i) #print Data_i #print MC_i #print data_arr #print mc_arr #print bin_arr # --> Set parameters and function to f i t name = ["c", "d"] #variable names vstart = arr('d', (1.0, 1.0)) #the initial values step = arr('d', (0.001, 0.001)) #the initial step size npar = len(name) # --> Defining the Chi-Square function to be minimized# def Chi_Induced(Data, MC, BinCenter, C, D): chi = 0. for i in range(0, entries_data): #num1 = Data[i] #num2 = D*((1-(C*BinCenter[i]))*MC[i]) #num = (num1 - num2)**2 #num = 0. #den1 = ((Data[i])**(1/2))**2 #den2 = (D*((1-(C*BinCenter[i])))*((MC[i])**(1/2)))**2 num = 2 den = 2 #den = den1 + den2 chi = chi + num / den return chi # --> set up MINUIT myMinuit = TMinuit( npar) # initialize TMinuit with maximum of npar parameters myMinuit.SetFCN(Chi_Induced) # set function to minimize ierflg = Long(0) arglist = arr('d', 2 * [0.01]) # set error definition arglist[0] = 6000 # Number of calls for FCN before gving up arglist[1] = 0.3 # Toleranceierflg = Long(0) myMinuit.mnexcm("SET ERR", arglist, 1, ierflg) for i in range(0, npar): myMinuit.mnparm(i, name[i], vstart[i], step[i], 0, 0, ierflg) myMinuit.mnexcm("MIGRAD", arglist, 1, ierflg) # execute the minimisation # --> check TMinuit status amin, edm, errdef = Double(0.), Double(0.), Double(0.) nvpar, nparx, icstat = Long(0), Long(0), Long(0) myMinuit.mnstat(amin, edm, errdef, nvpar, nparx, icstat)
def signalfit(data_hist, signalfunction, signalname): 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[3] = data_hist.GetRMS() lower[3] = data_hist.GetRMS() / 3.0 upper[3] = data_hist.GetRMS() * 3.0 vstart[3] = 125 lower[3] = 5 upper[3] = 20 vstart[4] = data_hist.Integral() vstart[2] = data_hist.GetMean() lower[2] = data_hist.GetMean() - 10 upper[2] = data_hist.GetMean() + 10 vstart[2] = 125 lower[2] = 110 upper[2] = 130 if len(vstart) > 5: vstart[5] = data_hist.GetMean() lower[5] = data_hist.GetMean() - 10 upper[5] = data_hist.GetMean() + 10 vstart[5] = 125 lower[5] = 110 upper[5] = 150 #vstart[6] = data_hist.GetRMS() #lower[6] = data_hist.GetRMS()/3.0 #upper[6] = data_hist.GetRMS()*3.0 vstart[6] = 10 lower[6] = 5 upper[6] = 20 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]
def runMinuit(numParameters): minuit = TMinuit(numParameters) minuit.SetPrintLevel(0) minuit.SetFCN(fcn) arglist = np.zeros(numParameters) + 0.01 internalFlag, arglist[0] = Long(0), 0.5 minuit.mnexcm("SET ERR", arglist, 1, internalFlag) initialValues = np.zeros(numParameters) + 0.01 steps = np.zeros(numParameters) + 0.0001 for i in xrange(numParameters): name = "epsilon%s" % i minuit.mnparm(i, name, initialValues[i], steps[i], 0, 1, internalFlag) # arglist[0] = 2 # minuit.mnexcm("SET STR", arglist, 1, internalFlag) arglist[0], arglist[1] = 10000, 0.1 minuit.mnexcm("SIMPLEX", arglist, 1, internalFlag) minuit.mnexcm("MIGRAD", arglist, 1, internalFlag) print "FIT STATUS is " +str(minuit.GetStatus()) return ratesAndErrors(numParameters, minuit)
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 __init__(self, number_of_parameters, function_to_minimize, parameter_names, start_parameters, parameter_errors, quiet=True, verbose=False): ''' Create a Minuit minimizer for a function `function_to_minimize`. Necessary arguments are the number of parameters and the function to be minimized `function_to_minimize`. The function `function_to_minimize`'s arguments must be numerical values. The same goes for its output. Another requirement is for every parameter of `function_to_minimize` to have a default value. These are then used to initialize Minuit. **number_of_parameters** : int The number of parameters of the function to minimize. **function_to_minimize** : function The function which `Minuit` should minimize. This must be a Python function with <``number_of_parameters``> arguments. **parameter_names** : tuple/list of strings The parameter names. These are used to keep track of the parameters in `Minuit`'s output. **start_parameters** : tuple/list of floats The start values of the parameters. It is important to have a good, if rough, estimate of the parameters at the minimum before starting the minimization. Wrong initial parameters can yield a local minimum instead of a global one. **parameter_errors** : tuple/list of floats An initial guess of the parameter errors. These errors are used to define the initial step size. *quiet* : boolean (optional, default: ``True``) If ``True``, suppresses all output from ``TMinuit``. *verbose* : boolean (optional, default: ``False``) If ``True``, sets ``TMinuit``'s print level to a high value, so that all output is logged. ''' #: the name of this minimizer type self.name = "ROOT::TMinuit" #: the actual `FCN` called in ``FCN_wrapper`` self.function_to_minimize = function_to_minimize #: number of parameters to minimize for self.number_of_parameters = number_of_parameters if not quiet: self.out_file = open(log_file("minuit.log"), 'a') else: self.out_file = null_file() # create a TMinuit instance for that number of parameters self.__gMinuit = TMinuit(self.number_of_parameters) # instruct Minuit to use this class's FCN_wrapper method as a FCN self.__gMinuit.SetFCN(self.FCN_wrapper) # set print level according to flag if quiet: self.set_print_level(-1000) # suppress output elif verbose: self.set_print_level(10) # detailed output else: self.set_print_level(0) # frugal output # initialize minimizer self.set_err() self.set_strategy() self.set_parameter_values(start_parameters) self.set_parameter_errors(parameter_errors) self.set_parameter_names(parameter_names) #: maximum number of iterations until ``TMinuit`` gives up self.max_iterations = M_MAX_ITERATIONS #: ``TMinuit`` tolerance self.tolerance = M_TOLERANCE
class Minuit: ''' A class for communicating with ROOT's function minimizer tool Minuit. ''' def __init__(self, number_of_parameters, function_to_minimize, parameter_names, start_parameters, parameter_errors, quiet=True, verbose=False): ''' Create a Minuit minimizer for a function `function_to_minimize`. Necessary arguments are the number of parameters and the function to be minimized `function_to_minimize`. The function `function_to_minimize`'s arguments must be numerical values. The same goes for its output. Another requirement is for every parameter of `function_to_minimize` to have a default value. These are then used to initialize Minuit. **number_of_parameters** : int The number of parameters of the function to minimize. **function_to_minimize** : function The function which `Minuit` should minimize. This must be a Python function with <``number_of_parameters``> arguments. **parameter_names** : tuple/list of strings The parameter names. These are used to keep track of the parameters in `Minuit`'s output. **start_parameters** : tuple/list of floats The start values of the parameters. It is important to have a good, if rough, estimate of the parameters at the minimum before starting the minimization. Wrong initial parameters can yield a local minimum instead of a global one. **parameter_errors** : tuple/list of floats An initial guess of the parameter errors. These errors are used to define the initial step size. *quiet* : boolean (optional, default: ``True``) If ``True``, suppresses all output from ``TMinuit``. *verbose* : boolean (optional, default: ``False``) If ``True``, sets ``TMinuit``'s print level to a high value, so that all output is logged. ''' #: the name of this minimizer type self.name = "ROOT::TMinuit" #: the actual `FCN` called in ``FCN_wrapper`` self.function_to_minimize = function_to_minimize #: number of parameters to minimize for self.number_of_parameters = number_of_parameters if not quiet: self.out_file = open(log_file("minuit.log"), 'a') else: self.out_file = null_file() # create a TMinuit instance for that number of parameters self.__gMinuit = TMinuit(self.number_of_parameters) # instruct Minuit to use this class's FCN_wrapper method as a FCN self.__gMinuit.SetFCN(self.FCN_wrapper) # set print level according to flag if quiet: self.set_print_level(-1000) # suppress output elif verbose: self.set_print_level(10) # detailed output else: self.set_print_level(0) # frugal output # initialize minimizer self.set_err() self.set_strategy() self.set_parameter_values(start_parameters) self.set_parameter_errors(parameter_errors) self.set_parameter_names(parameter_names) #: maximum number of iterations until ``TMinuit`` gives up self.max_iterations = M_MAX_ITERATIONS #: ``TMinuit`` tolerance self.tolerance = M_TOLERANCE def update_parameter_data(self, show_warnings=False): """ (Re-)Sets the parameter names, values and step size on the C++ side of Minuit. """ error_code = Long(0) try: # Set up the starting fit parameters in TMinuit for i in range(0, self.number_of_parameters): self.__gMinuit.mnparm(i, self.parameter_names[i], self.current_parameters[i], 0.1 * self.parameter_errors[i], 0, 0, error_code) # use 10% of the par. 1-sigma errors as the initial step size except AttributeError as e: if show_warnings: logger.warning("Cannot update Minuit data on the C++ side. " "AttributeError: %s" % (e, )) return error_code # Set methods ############## def set_print_level(self, print_level=P_DETAIL_LEVEL): '''Sets the print level for Minuit. *print_level* : int (optional, default: 1 (frugal output)) Tells ``TMinuit`` how much output to generate. The higher this value, the more output it generates. ''' self.__gMinuit.SetPrintLevel(print_level) # set Minuit print level self.print_level = print_level def set_strategy(self, strategy_id=1): '''Sets the strategy Minuit. *strategy_id* : int (optional, default: 1 (optimized)) Tells ``TMinuit`` to use a certain strategy. Refer to ``TMinuit``'s documentation for available strategies. ''' error_code = Long(0) # execute SET STRATEGY command self.__gMinuit.mnexcm("SET STRATEGY", arr('d', [strategy_id]), 1, error_code) def set_err(self, up_value=1.0): '''Sets the ``UP`` value for Minuit. *up_value* : float (optional, default: 1.0) This is the value by which `FCN` is expected to change. ''' # Tell TMinuit to use an up-value of 1.0 error_code = Long(0) # execute SET ERR command self.__gMinuit.mnexcm("SET ERR", arr('d', [up_value]), 1, error_code) def set_parameter_values(self, parameter_values): ''' Sets the fit parameters. If parameter_values=`None`, tries to infer defaults from the function_to_minimize. ''' if len(parameter_values) == self.number_of_parameters: self.current_parameters = parameter_values else: raise Exception("Cannot get default parameter values from the \ FCN. Not all parameters have default values given.") self.update_parameter_data() def set_parameter_names(self, parameter_names): '''Sets the fit parameters. If parameter_values=`None`, tries to infer defaults from the function_to_minimize.''' if len(parameter_names) == self.number_of_parameters: self.parameter_names = parameter_names else: raise Exception("Cannot set param names. Tuple length mismatch.") self.update_parameter_data() def set_parameter_errors(self, parameter_errors=None): '''Sets the fit parameter errors. If parameter_values=`None`, sets the error to 10% of the parameter value.''' if parameter_errors is None: # set to 0.1% of the parameter value if not self.current_parameters is None: self.parameter_errors = [max(0.1, 0.1 * par) for par in self.current_parameters] else: raise Exception("Cannot set parameter errors. No errors \ provided and no parameters initialized.") elif len(parameter_errors) != len(self.current_parameters): raise Exception("Cannot set parameter errors. \ Tuple length mismatch.") else: self.parameter_errors = parameter_errors self.update_parameter_data() # Get methods ############## def get_error_matrix(self): '''Retrieves the parameter error matrix from TMinuit. return : `numpy.matrix` ''' # set up an array of type `double' to pass to TMinuit tmp_array = arr('d', [0.0]*(self.number_of_parameters**2)) # get parameter covariance matrix from TMinuit self.__gMinuit.mnemat(tmp_array, self.number_of_parameters) # reshape into 2D array return np.asmatrix( np.reshape( tmp_array, (self.number_of_parameters, self.number_of_parameters) ) ) def get_parameter_values(self): '''Retrieves the parameter values from TMinuit. return : tuple Current `Minuit` parameter values ''' result = [] # retrieve fit parameters p, pe = Double(0), Double(0) for i in range(0, self.number_of_parameters): self.__gMinuit.GetParameter(i, p, pe) # retrieve fitresult result.append(float(p)) return tuple(result) def get_parameter_errors(self): '''Retrieves the parameter errors from TMinuit. return : tuple Current `Minuit` parameter errors ''' result = [] # retrieve fit parameters p, pe = Double(0), Double(0) for i in range(0, self.number_of_parameters): self.__gMinuit.GetParameter(i, p, pe) # retrieve fitresult result.append(float(pe)) return tuple(result) def get_parameter_info(self): '''Retrieves parameter information from TMinuit. return : list of tuples ``(parameter_name, parameter_val, parameter_error)`` ''' result = [] # retrieve fit parameters p, pe = Double(0), Double(0) for i in range(0, self.number_of_parameters): self.__gMinuit.GetParameter(i, p, pe) # retrieve fitresult result.append((self.get_parameter_name(i), float(p), float(pe))) return result def get_parameter_name(self, parameter_nr): '''Gets the name of parameter number ``parameter_nr`` **parameter_nr** : int Number of the parameter whose name to get. ''' return self.parameter_names[parameter_nr] 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 def get_chi2_probability(self, n_deg_of_freedom): ''' Returns the probability that an observed :math:`\chi^2` exceeds the calculated value of :math:`\chi^2` for this fit by chance, even for a correct model. In other words, returns the probability that a worse fit of the model to the data exists. If this is a small value (typically <5%), this means the fit is pretty bad. For values below this threshold, the model very probably does not fit the data. n_def_of_freedom : int The number of degrees of freedom. This is typically :math:`n_\text{datapoints} - n_\text{parameters}`. ''' chi2 = Double(self.get_fit_info('fcn')) ndf = Long(n_deg_of_freedom) return TMath.Prob(chi2, ndf) def get_contour(self, parameter1, parameter2, n_points=21): ''' Returns a list of points (2-tuples) representing a sampling of the :math:`1\\sigma` contour of the TMinuit fit. The ``FCN`` has to be minimized before calling this. **parameter1** : int ID of the parameter to be displayed on the `x`-axis. **parameter2** : int ID of the parameter to be displayed on the `y`-axis. *n_points* : int (optional) number of points used to draw the contour. Default is 21. *returns* : 2-tuple of tuples a 2-tuple (x, y) containing ``n_points+1`` points sampled along the contour. The first point is repeated at the end of the list to generate a closed contour. ''' self.out_file.write('\n') # entry in log-file self.out_file.write('\n') self.out_file.write('#'*(5+28)) self.out_file.write('\n') self.out_file.write('# Contour for parameters %2d, %2d #\n'\ %(parameter1, parameter2) ) self.out_file.write('#'*(5+28)) self.out_file.write('\n\n') self.out_file.flush() # # first, make sure we are at minimum self.minimize(final_fit=True, log_print_level=0) # get the TGraph object from ROOT g = self.__gMinuit.Contour(n_points, parameter1, parameter2) # extract point data into buffers xbuf, ybuf = g.GetX(), g.GetY() N = g.GetN() # generate tuples from buffers x = np.frombuffer(xbuf, dtype=float, count=N) y = np.frombuffer(ybuf, dtype=float, count=N) # return (x, y) def get_profile(self, parid, n_points=21): ''' Returns a list of points (2-tuples) the profile the :math:`\\chi^2` of the TMinuit fit. **parid** : int ID of the parameter to be displayed on the `x`-axis. *n_points* : int (optional) number of points used for profile. Default is 21. *returns* : two arrays, par. values and corresp. :math:`\\chi^2` containing ``n_points`` sampled profile points. ''' self.out_file.write('\n') # entry in log-file self.out_file.write('\n') self.out_file.write('#'*(2+26)) self.out_file.write('\n') self.out_file.write("# Profile for parameter %2d #\n" % (parid)) self.out_file.write('#'*(2+26)) self.out_file.write('\n\n') self.out_file.flush() # redirect stdout stream _redirection_target = None ## -- disable redirection completely, for now ##if log_print_level >= 0: ## _redirection_target = self.out_file with redirect_stdout_to(_redirection_target): pv = [] chi2 = [] error_code = Long(0) self.__gMinuit.mnexcm("SET PRINT", arr('d', [0.0]), 1, error_code) # no printout # first, make sure we are at minimum, i.e. re-minimize self.minimize(final_fit=True, log_print_level=0) minuit_id = Double(parid + 1) # Minuit parameter numbers start with 1 # retrieve information about parameter with id=parid pmin = Double(0) perr = Double(0) self.__gMinuit.GetParameter(parid, pmin, perr) # retrieve fitresult # fix parameter parid ... self.__gMinuit.mnexcm("FIX", arr('d', [minuit_id]), 1, error_code) # ... and scan parameter values, minimizing at each point for v in np.linspace(pmin - 3.*perr, pmin + 3.*perr, n_points): pv.append(v) self.__gMinuit.mnexcm("SET PAR", arr('d', [minuit_id, Double(v)]), 2, error_code) self.__gMinuit.mnexcm("MIGRAD", arr('d', [self.max_iterations, self.tolerance]), 2, error_code) chi2.append(self.get_fit_info('fcn')) # release parameter to back to initial value and release self.__gMinuit.mnexcm("SET PAR", arr('d', [minuit_id, Double(pmin)]), 2, error_code) self.__gMinuit.mnexcm("RELEASE", arr('d', [minuit_id]), 1, error_code) return pv, chi2 # Other methods ################ def fix_parameter(self, parameter_number): ''' Fix parameter number <`parameter_number`>. **parameter_number** : int Number of the parameter to fix. ''' error_code = Long(0) logger.info("Fixing parameter %d in Minuit" % (parameter_number,)) # execute FIX command self.__gMinuit.mnexcm("FIX", arr('d', [parameter_number+1]), 1, error_code) def release_parameter(self, parameter_number): ''' Release parameter number <`parameter_number`>. **parameter_number** : int Number of the parameter to release. ''' error_code = Long(0) logger.info("Releasing parameter %d in Minuit" % (parameter_number,)) # execute RELEASE command self.__gMinuit.mnexcm("RELEASE", arr('d', [parameter_number+1]), 1, error_code) def reset(self): '''Execute TMinuit's `mnrset` method.''' self.__gMinuit.mnrset(0) # reset TMinuit def FCN_wrapper(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 fit. 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 fit **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.number_of_parameters) # call the Python implementation of FCN. f[0] = self.function_to_minimize(*parameter_list) def minimize(self, final_fit=True, log_print_level=2): '''Do the minimization. This calls `Minuit`'s algorithms ``MIGRAD`` for minimization and, if `final_fit` is `True`, also ``HESSE`` for computing/checking the parameter error matrix.''' # Set the FCN again. This HAS to be done EVERY # time the minimize method is called because of # the implementation of SetFCN, which is not # object-oriented but sets a global pointer!!! logger.debug("Updating current FCN") self.__gMinuit.SetFCN(self.FCN_wrapper) # Run minimization algorithm (MIGRAD + HESSE) error_code = Long(0) prefix = "Minuit run on" # set the timestamp prefix # insert timestamp self.out_file.write('\n') self.out_file.write('#'*(len(prefix)+4+20)) self.out_file.write('\n') self.out_file.write("# %s " % (prefix,) + strftime("%Y-%m-%d %H:%M:%S #\n", gmtime())) self.out_file.write('#'*(len(prefix)+4+20)) self.out_file.write('\n\n') self.out_file.flush() # redirect stdout stream _redirection_target = None if log_print_level >= 0: _redirection_target = self.out_file with redirect_stdout_to(_redirection_target): self.__gMinuit.SetPrintLevel(log_print_level) # set Minuit print level logger.debug("Running MIGRAD") self.__gMinuit.mnexcm("MIGRAD", arr('d', [self.max_iterations, self.tolerance]), 2, error_code) if(final_fit): logger.debug("Running HESSE") self.__gMinuit.mnexcm("HESSE", arr('d', [self.max_iterations]), 1, error_code) # return to normal print level self.__gMinuit.SetPrintLevel(self.print_level) def minos_errors(self, log_print_level=1): ''' Get (asymmetric) parameter uncertainties from MINOS algorithm. This calls `Minuit`'s algorithms ``MINOS``, which determines parameter uncertainties using profiling of the chi2 function. returns : tuple A tuple of [err+, err-, parabolic error, global correlation] ''' # Set the FCN again. This HAS to be done EVERY # time the minimize method is called because of # the implementation of SetFCN, which is not # object-oriented but sets a global pointer!!! logger.debug("Updating current FCN") self.__gMinuit.SetFCN(self.FCN_wrapper) # redirect stdout stream _redirection_target = None if log_print_level >= 0: _redirection_target = self.out_file with redirect_stdout_to(_redirection_target): self.__gMinuit.SetPrintLevel(log_print_level) logger.debug("Running MINOS") error_code = Long(0) self.__gMinuit.mnexcm("MINOS", arr('d', [self.max_iterations]), 1, error_code) # return to normal print level self.__gMinuit.SetPrintLevel(self.print_level) output = [] errpos=Double(0) # positive parameter error errneg=Double(0) # negative parameter error err=Double(0) # parabolic error gcor=Double(0) # global correlation coefficient for i in range(0, self.number_of_parameters): self.__gMinuit.mnerrs(i, errpos, errneg, err, gcor) output.append([float(errpos),float(errneg),float(err),float(gcor)]) return output
class Minuit: ''' A class for communicating with ROOT's function minimizer tool Minuit. ''' def __init__(self, number_of_parameters, function_to_minimize, parameter_names, start_parameters, parameter_errors, quiet=True, verbose=False): ''' Create a Minuit minimizer for a function `function_to_minimize`. Necessary arguments are the number of parameters and the function to be minimized `function_to_minimize`. The function `function_to_minimize`'s arguments must be numerical values. The same goes for its output. Another requirement is for every parameter of `function_to_minimize` to have a default value. These are then used to initialize Minuit. **number_of_parameters** : int The number of parameters of the function to minimize. **function_to_minimize** : function The function which `Minuit` should minimize. This must be a Python function with <``number_of_parameters``> arguments. **parameter_names** : tuple/list of strings The parameter names. These are used to keep track of the parameters in `Minuit`'s output. **start_parameters** : tuple/list of floats The start values of the parameters. It is important to have a good, if rough, estimate of the parameters at the minimum before starting the minimization. Wrong initial parameters can yield a local minimum instead of a global one. **parameter_errors** : tuple/list of floats An initial guess of the parameter errors. These errors are used to define the initial step size. *quiet* : boolean (optional, default: ``True``) If ``True``, suppresses all output from ``TMinuit``. *verbose* : boolean (optional, default: ``False``) If ``True``, sets ``TMinuit``'s print level to a high value, so that all output is logged. ''' #: the name of this minimizer type self.name = "ROOT::TMinuit" #: the actual `FCN` called in ``FCN_wrapper`` self.function_to_minimize = function_to_minimize #: number of parameters to minimize for self.number_of_parameters = number_of_parameters if not quiet: self.out_file = open(log_file("minuit.log"), 'a') else: self.out_file = null_file() # create a TMinuit instance for that number of parameters self.__gMinuit = TMinuit(self.number_of_parameters) # instruct Minuit to use this class's FCN_wrapper method as a FCN self.__gMinuit.SetFCN(self.FCN_wrapper) # set print level according to flag if quiet: self.set_print_level(-1000) # suppress output elif verbose: self.set_print_level(10) # detailed output else: self.set_print_level(0) # frugal output # initialize minimizer self.set_err() self.set_strategy() self.set_parameter_values(start_parameters) self.set_parameter_errors(parameter_errors) self.set_parameter_names(parameter_names) #: maximum number of iterations until ``TMinuit`` gives up self.max_iterations = M_MAX_ITERATIONS #: ``TMinuit`` tolerance self.tolerance = M_TOLERANCE def update_parameter_data(self, show_warnings=False): """ (Re-)Sets the parameter names, values and step size on the C++ side of Minuit. """ error_code = Long(0) try: # Set up the starting fit parameters in TMinuit for i in range(0, self.number_of_parameters): self.__gMinuit.mnparm(i, self.parameter_names[i], self.current_parameters[i], 0.1 * self.parameter_errors[i], 0, 0, error_code) # use 10% of the par. 1-sigma errors as the initial step size except AttributeError as e: if show_warnings: logger.warn("Cannot update Minuit data on the C++ side. " "AttributeError: %s" % (e, )) return error_code # Set methods ############## def set_print_level(self, print_level=P_DETAIL_LEVEL): '''Sets the print level for Minuit. *print_level* : int (optional, default: 1 (frugal output)) Tells ``TMinuit`` how much output to generate. The higher this value, the more output it generates. ''' self.__gMinuit.SetPrintLevel(print_level) # set Minuit print level self.print_level = print_level def set_strategy(self, strategy_id=1): '''Sets the strategy Minuit. *strategy_id* : int (optional, default: 1 (optimized)) Tells ``TMinuit`` to use a certain strategy. Refer to ``TMinuit``'s documentation for available strategies. ''' error_code = Long(0) # execute SET STRATEGY command self.__gMinuit.mnexcm("SET STRATEGY", arr('d', [strategy_id]), 1, error_code) def set_err(self, up_value=1.0): '''Sets the ``UP`` value for Minuit. *up_value* : float (optional, default: 1.0) This is the value by which `FCN` is expected to change. ''' # Tell TMinuit to use an up-value of 1.0 error_code = Long(0) # execute SET ERR command self.__gMinuit.mnexcm("SET ERR", arr('d', [up_value]), 1, error_code) def set_parameter_values(self, parameter_values): ''' Sets the fit parameters. If parameter_values=`None`, tries to infer defaults from the function_to_minimize. ''' if len(parameter_values) == self.number_of_parameters: self.current_parameters = parameter_values else: raise Exception("Cannot get default parameter values from the \ FCN. Not all parameters have default values given.") self.update_parameter_data() def set_parameter_names(self, parameter_names): '''Sets the fit parameters. If parameter_values=`None`, tries to infer defaults from the function_to_minimize.''' if len(parameter_names) == self.number_of_parameters: self.parameter_names = parameter_names else: raise Exception("Cannot set param names. Tuple length mismatch.") self.update_parameter_data() def set_parameter_errors(self, parameter_errors=None): '''Sets the fit parameter errors. If parameter_values=`None`, sets the error to 10% of the parameter value.''' if parameter_errors is None: # set to 0.1% of the parameter value if not self.current_parameters is None: self.parameter_errors = [max(0.1, 0.1 * par) for par in self.current_parameters] else: raise Exception("Cannot set parameter errors. No errors \ provided and no parameters initialized.") elif len(parameter_errors) != len(self.current_parameters): raise Exception("Cannot set parameter errors. \ Tuple length mismatch.") else: self.parameter_errors = parameter_errors self.update_parameter_data() # Get methods ############## def get_error_matrix(self): '''Retrieves the parameter error matrix from TMinuit. return : `numpy.matrix` ''' # set up an array of type `double' to pass to TMinuit tmp_array = arr('d', [0.0]*(self.number_of_parameters**2)) # get parameter covariance matrix from TMinuit self.__gMinuit.mnemat(tmp_array, self.number_of_parameters) # reshape into 2D array return np.asmatrix( np.reshape( tmp_array, (self.number_of_parameters, self.number_of_parameters) ) ) def get_parameter_values(self): '''Retrieves the parameter values from TMinuit. return : tuple Current `Minuit` parameter values ''' result = [] # retrieve fit parameters p, pe = Double(0), Double(0) for i in range(0, self.number_of_parameters): self.__gMinuit.GetParameter(i, p, pe) # retrieve fitresult result.append(float(p)) return tuple(result) def get_parameter_errors(self): '''Retrieves the parameter errors from TMinuit. return : tuple Current `Minuit` parameter errors ''' result = [] # retrieve fit parameters p, pe = Double(0), Double(0) for i in range(0, self.number_of_parameters): self.__gMinuit.GetParameter(i, p, pe) # retrieve fitresult result.append(float(pe)) return tuple(result) def get_parameter_info(self): '''Retrieves parameter information from TMinuit. return : list of tuples ``(parameter_name, parameter_val, parameter_error)`` ''' result = [] # retrieve fit parameters p, pe = Double(0), Double(0) for i in range(0, self.number_of_parameters): self.__gMinuit.GetParameter(i, p, pe) # retrieve fitresult result.append((self.get_parameter_name(i), float(p), float(pe))) return result def get_parameter_name(self, parameter_nr): '''Gets the name of parameter number ``parameter_nr`` **parameter_nr** : int Number of the parameter whose name to get. ''' return self.parameter_names[parameter_nr] 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 def get_chi2_probability(self, n_deg_of_freedom): ''' Returns the probability that an observed :math:`\chi^2` exceeds the calculated value of :math:`\chi^2` for this fit by chance, even for a correct model. In other words, returns the probability that a worse fit of the model to the data exists. If this is a small value (typically <5%), this means the fit is pretty bad. For values below this threshold, the model very probably does not fit the data. n_def_of_freedom : int The number of degrees of freedom. This is typically :math:`n_\text{datapoints} - n_\text{parameters}`. ''' chi2 = Double(self.get_fit_info('fcn')) ndf = Long(n_deg_of_freedom) return TMath.Prob(chi2, ndf) def get_contour(self, parameter1, parameter2, n_points=21): ''' Returns a list of points (2-tuples) representing a sampling of the :math:`1\\sigma` contour of the TMinuit fit. The ``FCN`` has to be minimized before calling this. **parameter1** : int ID of the parameter to be displayed on the `x`-axis. **parameter2** : int ID of the parameter to be displayed on the `y`-axis. *n_points* : int (optional) number of points used to draw the contour. Default is 21. *returns* : 2-tuple of tuples a 2-tuple (x, y) containing ``n_points+1`` points sampled along the contour. The first point is repeated at the end of the list to generate a closed contour. ''' self.out_file.write('\n') # entry in log-file self.out_file.write('\n') self.out_file.write('#'*(5+28)) self.out_file.write('\n') self.out_file.write('# Contour for parameters %2d, %2d #\n'\ %(parameter1, parameter2) ) self.out_file.write('#'*(5+28)) self.out_file.write('\n\n') self.out_file.flush() # # first, make sure we are at minimum self.minimize(final_fit=True, log_print_level=0) # get the TGraph object from ROOT g = self.__gMinuit.Contour(n_points, parameter1, parameter2) # extract point data into buffers xbuf, ybuf = g.GetX(), g.GetY() N = g.GetN() # generate tuples from buffers x = np.frombuffer(xbuf, dtype=float, count=N) y = np.frombuffer(ybuf, dtype=float, count=N) # return (x, y) def get_profile(self, parid, n_points=21): ''' Returns a list of points (2-tuples) the profile the :math:`\\chi^2` of the TMinuit fit. **parid** : int ID of the parameter to be displayed on the `x`-axis. *n_points* : int (optional) number of points used for profile. Default is 21. *returns* : two arrays, par. values and corresp. :math:`\\chi^2` containing ``n_points`` sampled profile points. ''' self.out_file.write('\n') # entry in log-file self.out_file.write('\n') self.out_file.write('#'*(2+26)) self.out_file.write('\n') self.out_file.write("# Profile for parameter %2d #\n" % (parid)) self.out_file.write('#'*(2+26)) self.out_file.write('\n\n') self.out_file.flush() # redirect stdout stream _redirection_target = None ## -- disable redirection completely, for now ##if log_print_level >= 0: ## _redirection_target = self.out_file with redirect_stdout_to(_redirection_target): pv = [] chi2 = [] error_code = Long(0) self.__gMinuit.mnexcm("SET PRINT", arr('d', [0.0]), 1, error_code) # no printout # first, make sure we are at minimum, i.e. re-minimize self.minimize(final_fit=True, log_print_level=0) minuit_id = Double(parid + 1) # Minuit parameter numbers start with 1 # retrieve information about parameter with id=parid pmin = Double(0) perr = Double(0) self.__gMinuit.GetParameter(parid, pmin, perr) # retrieve fitresult # fix parameter parid ... self.__gMinuit.mnexcm("FIX", arr('d', [minuit_id]), 1, error_code) # ... and scan parameter values, minimizing at each point for v in np.linspace(pmin - 3.*perr, pmin + 3.*perr, n_points): pv.append(v) self.__gMinuit.mnexcm("SET PAR", arr('d', [minuit_id, Double(v)]), 2, error_code) self.__gMinuit.mnexcm("MIGRAD", arr('d', [self.max_iterations, self.tolerance]), 2, error_code) chi2.append(self.get_fit_info('fcn')) # release parameter to back to initial value and release self.__gMinuit.mnexcm("SET PAR", arr('d', [minuit_id, Double(pmin)]), 2, error_code) self.__gMinuit.mnexcm("RELEASE", arr('d', [minuit_id]), 1, error_code) return pv, chi2 # Other methods ################ def fix_parameter(self, parameter_number): ''' Fix parameter number <`parameter_number`>. **parameter_number** : int Number of the parameter to fix. ''' error_code = Long(0) logger.info("Fixing parameter %d in Minuit" % (parameter_number,)) # execute FIX command self.__gMinuit.mnexcm("FIX", arr('d', [parameter_number+1]), 1, error_code) def release_parameter(self, parameter_number): ''' Release parameter number <`parameter_number`>. **parameter_number** : int Number of the parameter to release. ''' error_code = Long(0) logger.info("Releasing parameter %d in Minuit" % (parameter_number,)) # execute RELEASE command self.__gMinuit.mnexcm("RELEASE", arr('d', [parameter_number+1]), 1, error_code) def reset(self): '''Execute TMinuit's `mnrset` method.''' self.__gMinuit.mnrset(0) # reset TMinuit def FCN_wrapper(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 fit. 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 fit **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.number_of_parameters) # call the Python implementation of FCN. f[0] = self.function_to_minimize(*parameter_list) def minimize(self, final_fit=True, log_print_level=2): '''Do the minimization. This calls `Minuit`'s algorithms ``MIGRAD`` for minimization and, if `final_fit` is `True`, also ``HESSE`` for computing/checking the parameter error matrix.''' # Set the FCN again. This HAS to be done EVERY # time the minimize method is called because of # the implementation of SetFCN, which is not # object-oriented but sets a global pointer!!! logger.debug("Updating current FCN") self.__gMinuit.SetFCN(self.FCN_wrapper) # Run minimization algorithm (MIGRAD + HESSE) error_code = Long(0) prefix = "Minuit run on" # set the timestamp prefix # insert timestamp self.out_file.write('\n') self.out_file.write('#'*(len(prefix)+4+20)) self.out_file.write('\n') self.out_file.write("# %s " % (prefix,) + strftime("%Y-%m-%d %H:%M:%S #\n", gmtime())) self.out_file.write('#'*(len(prefix)+4+20)) self.out_file.write('\n\n') self.out_file.flush() # redirect stdout stream _redirection_target = None if log_print_level >= 0: _redirection_target = self.out_file with redirect_stdout_to(_redirection_target): self.__gMinuit.SetPrintLevel(log_print_level) # set Minuit print level logger.debug("Running MIGRAD") self.__gMinuit.mnexcm("MIGRAD", arr('d', [self.max_iterations, self.tolerance]), 2, error_code) if(final_fit): logger.debug("Running HESSE") self.__gMinuit.mnexcm("HESSE", arr('d', [self.max_iterations]), 1, error_code) # return to normal print level self.__gMinuit.SetPrintLevel(self.print_level) def minos_errors(self, log_print_level=1): ''' Get (asymmetric) parameter uncertainties from MINOS algorithm. This calls `Minuit`'s algorithms ``MINOS``, which determines parameter uncertainties using profiling of the chi2 function. returns : tuple A tuple of [err+, err-, parabolic error, global correlation] ''' # Set the FCN again. This HAS to be done EVERY # time the minimize method is called because of # the implementation of SetFCN, which is not # object-oriented but sets a global pointer!!! logger.debug("Updating current FCN") self.__gMinuit.SetFCN(self.FCN_wrapper) # redirect stdout stream _redirection_target = None if log_print_level >= 0: _redirection_target = self.out_file with redirect_stdout_to(_redirection_target): self.__gMinuit.SetPrintLevel(log_print_level) logger.debug("Running MINOS") error_code = Long(0) self.__gMinuit.mnexcm("MINOS", arr('d', [self.max_iterations]), 1, error_code) # return to normal print level self.__gMinuit.SetPrintLevel(self.print_level) output = [] errpos=Double(0) # positive parameter error errneg=Double(0) # negative parameter error err=Double(0) # parabolic error gcor=Double(0) # global correlation coefficient for i in range(0, self.number_of_parameters): self.__gMinuit.mnerrs(i, errpos, errneg, err, gcor) output.append([float(errpos),float(errneg),float(err),float(gcor)]) return output
class Minuit: ''' A class for communicating with ROOT's function minimizer tool Minuit. ''' def __init__(self, number_of_parameters, function_to_minimize, parameter_names, start_parameters, parameter_errors, quiet=True, verbose=False): ''' Create a Minuit minimizer for a function `function_to_minimize`. Necessary arguments are the number of parameters and the function to be minimized `function_to_minimize`. The function `function_to_minimize`'s arguments must be numerical values. The same goes for its output. Another requirement is for every parameter of `function_to_minimize` to have a default value. These are then used to initialize Minuit. **number_of_parameters** : int The number of parameters of the function to minimize. **function_to_minimize** : function The function which `Minuit` should minimize. This must be a Python function with <``number_of_parameters``> arguments. **parameter_names** : tuple/list of strings The parameter names. These are used to keep track of the parameters in `Minuit`'s output. **start_parameters** : tuple/list of floats The start values of the parameters. It is important to have a good, if rough, estimate of the parameters at the minimum before starting the minimization. Wrong initial parameters can yield a local minimum instead of a global one. **parameter_errors** : tuple/list of floats An initial guess of the parameter errors. These errors are used to define the initial step size. *quiet* : boolean (optional, default: ``True``) If ``True``, suppresses all output from ``TMinuit``. *verbose* : boolean (optional, default: ``False``) If ``True``, sets ``TMinuit``'s print level to a high value, so that all output is logged. ''' #: the name of this minimizer type self.name = "ROOT::TMinuit" #: the actual `FCN` called in ``FCN_wrapper`` self.function_to_minimize = function_to_minimize #: number of parameters to minimize for self.number_of_parameters = number_of_parameters if not quiet: self.out_file = open(log_file("minuit.log"), 'a') else: self.out_file = null_file() # create a TMinuit instance for that number of parameters self.__gMinuit = TMinuit(self.number_of_parameters) # instruct Minuit to use this class's FCN_wrapper method as a FCN self.__gMinuit.SetFCN(self.FCN_wrapper) # set print level according to flag if quiet: self.set_print_level(-1000) # suppress output elif verbose: self.set_print_level(10) # detailed output else: self.set_print_level(0) # frugal output # initialize minimizer self.set_err() self.set_strategy() self.set_parameter_values(start_parameters) self.set_parameter_errors(parameter_errors) self.set_parameter_names(parameter_names) #: maximum number of iterations until ``TMinuit`` gives up self.max_iterations = M_MAX_ITERATIONS #: ``TMinuit`` tolerance self.tolerance = M_TOLERANCE def update_parameter_data(self, show_warnings=False): """ (Re-)Sets the parameter names, values and step size on the C++ side of Minuit. """ error_code = Long(0) try: # Set up the starting fit parameters in TMinuit for i in xrange(0, self.number_of_parameters): self.__gMinuit.mnparm(i, self.parameter_names[i], self.current_parameters[i], 0.1 * self.parameter_errors[i], 0, 0, error_code) # use 10% of the par. 1-sigma errors as the initial step size except AttributeError, e: if show_warnings: logger.warn("Cannot update Minuit data on the C++ side. " "AttributeError: %s" % (e, )) return error_code
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.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: ''' A class for communicating with ROOT's function minimizer tool Minuit. ''' def __init__(self, number_of_parameters, function_to_minimize, parameter_names, start_parameters, parameter_errors, quiet=True, verbose=False): ''' Create a Minuit minimizer for a function `function_to_minimize`. Necessary arguments are the number of parameters and the function to be minimized `function_to_minimize`. The function `function_to_minimize`'s arguments must be numerical values. The same goes for its output. Another requirement is for every parameter of `function_to_minimize` to have a default value. These are then used to initialize Minuit. **number_of_parameters** : int The number of parameters of the function to minimize. **function_to_minimize** : function The function which `Minuit` should minimize. This must be a Python function with <``number_of_parameters``> arguments. **parameter_names** : tuple/list of strings The parameter names. These are used to keep track of the parameters in `Minuit`'s output. **start_parameters** : tuple/list of floats The start values of the parameters. It is important to have a good, if rough, estimate of the parameters at the minimum before starting the minimization. Wrong initial parameters can yield a local minimum instead of a global one. **parameter_errors** : tuple/list of floats An initial guess of the parameter errors. These errors are used to define the initial step size. *quiet* : boolean (optional, default: ``True``) If ``True``, suppresses all output from ``TMinuit``. *verbose* : boolean (optional, default: ``False``) If ``True``, sets ``TMinuit``'s print level to a high value, so that all output is logged. ''' #: the name of this minimizer type self.name = "ROOT::TMinuit" #: the actual `FCN` called in ``FCN_wrapper`` self.function_to_minimize = function_to_minimize #: number of parameters to minimize for self.number_of_parameters = number_of_parameters self.out_file = open(log_file("minuit.log"), 'a') # create a TMinuit instance for that number of parameters self.__gMinuit = TMinuit(self.number_of_parameters) # instruct Minuit to use this class's FCN_wrapper method as a FCN self.__gMinuit.SetFCN(self.FCN_wrapper) # set print level according to flag if quiet: self.set_print_level(-1000) # suppress output elif verbose: self.set_print_level(10) # detailed output else: self.set_print_level(0) # frugal output # initialize minimizer self.set_err() self.set_strategy() self.set_parameter_values(start_parameters) self.set_parameter_errors(parameter_errors) self.set_parameter_names(parameter_names) #: maximum number of iterations until ``TMinuit`` gives up self.max_iterations = M_MAX_ITERATIONS #: ``TMinuit`` tolerance self.tolerance = M_TOLERANCE def update_parameter_data(self, show_warnings=False): """ (Re-)Sets the parameter names, values and step size on the C++ side of Minuit. """ error_code = Long(0) try: # Set up the starting fit parameters in TMinuit for i in xrange(0, self.number_of_parameters): self.__gMinuit.mnparm(i, self.parameter_names[i], self.current_parameters[i], 0.1 * self.parameter_errors[i], 0, 0, error_code) # use 10% of the par. 1-sigma errors as the initial step size except AttributeError, e: if show_warnings: logger.warn("Cannot update Minuit data on the C++ side. " "AttributeError: %s" % (e, )) return error_code
def test1MinuitFit(self): """Test minuit callback and fit""" # setup minuit and callback gMinuit = TMinuit(5) gMinuit.SetPrintLevel(-1) # quiet gMinuit.SetGraphicsMode(ROOT.kFALSE) gMinuit.SetFCN(fcn) arglist = array('d', 10 * [0.]) if not exp_pyroot and sys.hexversion < 0x3000000: ierflg = ROOT.Long() else: ierflg = ctypes.c_int() arglist[0] = 1 gMinuit.mnexcm("SET ERR", arglist, 1, ierflg) # set starting values and step sizes for parameters vstart = array('d', [3, 1, 0.1, 0.01]) step = array('d', [0.1, 0.1, 0.01, 0.001]) gMinuit.mnparm(0, "a1", vstart[0], step[0], 0, 0, ierflg) gMinuit.mnparm(1, "a2", vstart[1], step[1], 0, 0, ierflg) gMinuit.mnparm(2, "a3", vstart[2], step[2], 0, 0, ierflg) gMinuit.mnparm(3, "a4", vstart[3], step[3], 0, 0, ierflg) # now ready for minimization step arglist[0] = 500 arglist[1] = 1. gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) # verify results if exp_pyroot: Double = ctypes.c_double else: Double = ROOT.Double amin, edm, errdef = Double(), Double(), Double() if not exp_pyroot and sys.hexversion < 0x3000000: Long = ROOT.Long nvpar, nparx, icstat = Long(), Long(), Long() else: nvpar, nparx, icstat = ctypes.c_int(), ctypes.c_int( ), ctypes.c_int() gMinuit.mnstat(amin, edm, errdef, nvpar, nparx, icstat) # gMinuit.mnprin( 3, amin ) if exp_pyroot or sys.hexversion >= 0x3000000: nvpar, nparx, icstat = map(lambda x: x.value, [nvpar, nparx, icstat]) self.assertEqual(nvpar, 4) self.assertEqual(nparx, 4) # success means that full covariance matrix is available (icstat==3) self.assertEqual(icstat, 3) # check results (somewhat debatable ... ) par, err = Double(), Double() if exp_pyroot: # ctypes.c_double requires the explicit retrieval of the inner value gMinuit.GetParameter(0, par, err) self.assertEqual(round(par.value - 2.15, 2), 0.) self.assertEqual(round(err.value - 0.10, 2), 0.) gMinuit.GetParameter(1, par, err) self.assertEqual(round(par.value - 0.81, 2), 0.) self.assertEqual(round(err.value - 0.25, 2), 0.) gMinuit.GetParameter(2, par, err) self.assertEqual(round(par.value - 0.17, 2), 0.) self.assertEqual(round(err.value - 0.40, 2), 0.) gMinuit.GetParameter(3, par, err) self.assertEqual(round(par.value - 0.10, 2), 0.) self.assertEqual(round(err.value - 0.16, 2), 0.) else: gMinuit.GetParameter(0, par, err) self.assertEqual(round(par - 2.15, 2), 0.) self.assertEqual(round(err - 0.10, 2), 0.) gMinuit.GetParameter(1, par, err) self.assertEqual(round(par - 0.81, 2), 0.) self.assertEqual(round(err - 0.25, 2), 0.) gMinuit.GetParameter(2, par, err) self.assertEqual(round(par - 0.17, 2), 0.) self.assertEqual(round(err - 0.40, 2), 0.) gMinuit.GetParameter(3, par, err) self.assertEqual(round(par - 0.10, 2), 0.) self.assertEqual(round(err - 0.16, 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
def prepareAndRunOptimizer(self, xValues, yValues, erryValues, fixParams, optFunction, parValues, parNames, printOutLevel=0): self.xValues = xValues self.yValues = yValues self.erryValues = erryValues self.fixParams = fixParams initialError = 0.1 nBins = len(self.xValues) gMinuit = TMinuit(nBins) gMinuit.SetFCN(optFunction) arglist = array('d', nBins * [0]) ierflg = c_int(0) arglist[0] = 1 # 1 for chi2, 0.5 for likelihood gMinuit.mnexcm('SET ERR', arglist, 1, ierflg) # Set starting values and step sizes for parameters vStart = parValues vStep = [initialError for i in range(len(parValues))] for i, name in enumerate(parNames): gMinuit.mnparm(i, name, vStart[i], vStep[i], 0, 0, ierflg) # Fix parameters (counting from 1) for i, fix in enumerate(self.fixParams): arglist[i] = fix + 1 gMinuit.mnexcm('FIX', arglist, len(self.fixParams), ierflg) # Define printout level arglist[0] = printOutLevel # -1 = no output except from SHOW # 0 = minimum output (no starting values or intermediate results) default value, normal output # 1 = additional output giving intermediate results. # 2 = maximum output, showing progress of minimizations. gMinuit.mnexcm('SET PRI', arglist, 1, ierflg) # Now ready for minimization step arglist[0] = 5000 # Max calls arglist[1] = 0.1 # Tolerance gMinuit.mnexcm('MIGRAD', arglist, 2, ierflg) # Print results self.dof = self.nMeas - len(parValues) + len(self.fixParams) fmin, fedm, errdef = c_double(0.), c_double(0.), c_double(0.) npari, nparx, istat = c_int(0), c_int(0), c_int(0) gMinuit.mnstat(fmin, fedm, errdef, npari, nparx, istat) print('\nFMIN:', round(fmin.value, 2), '\tFEDM:', '{:.1e}'.format(fedm.value), '\tERRDEF:', errdef.value, '\tNPARI:', npari.value, '\tNPARX:', nparx.value, '\tISTAT:', istat.value) print('chi-2:', round(self.chi2, 2), '\td.o.f.:', self.dof, '\tchi-2/d.o.f.:', round(self.chi2 / self.dof, 2), '\n') return gMinuit, istat
def test1MinuitFit(self): """Test minuit callback and fit""" # setup minuit and callback gMinuit = TMinuit(5) gMinuit.SetPrintLevel(-1) # quiet gMinuit.SetGraphicsMode(ROOT.kFALSE) gMinuit.SetFCN(fcn) arglist = array('d', 10 * [0.]) ierflg = Long() arglist[0] = 1 gMinuit.mnexcm("SET ERR", arglist, 1, ierflg) # set starting values and step sizes for parameters vstart = array('d', [3, 1, 0.1, 0.01]) step = array('d', [0.1, 0.1, 0.01, 0.001]) gMinuit.mnparm(0, "a1", vstart[0], step[0], 0, 0, ierflg) gMinuit.mnparm(1, "a2", vstart[1], step[1], 0, 0, ierflg) gMinuit.mnparm(2, "a3", vstart[2], step[2], 0, 0, ierflg) gMinuit.mnparm(3, "a4", vstart[3], step[3], 0, 0, ierflg) # now ready for minimization step arglist[0] = 500 arglist[1] = 1. gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) # verify results amin, edm, errdef = Double(), Double(), Double() nvpar, nparx, icstat = Long(), Long(), Long() gMinuit.mnstat(amin, edm, errdef, nvpar, nparx, icstat) # gMinuit.mnprin( 3, amin ) self.assertEqual(nvpar, 4) self.assertEqual(nparx, 4) # success means that full covariance matrix is available (icstat==3) self.assertEqual(icstat, 3) # check results (somewhat debatable ... ) par, err = Double(), Double() gMinuit.GetParameter(0, par, err) self.assertEqual(round(par - 2.15, 2), 0.) self.assertEqual(round(err - 0.10, 2), 0.) gMinuit.GetParameter(1, par, err) self.assertEqual(round(par - 0.81, 2), 0.) self.assertEqual(round(err - 0.25, 2), 0.) gMinuit.GetParameter(2, par, err) self.assertEqual(round(par - 0.17, 2), 0.) self.assertEqual(round(err - 0.40, 2), 0.) gMinuit.GetParameter(3, par, err) self.assertEqual(round(par - 0.10, 2), 0.) self.assertEqual(round(err - 0.16, 2), 0.)
def dofit(self) : #print 'starting fit for hypothesis index %d'%(self.fitindex)#DEBUG #set up the minuit object, etc. parNames = ['fitindex','pZv','scalelep','scaleblep','scalehad1','scalehad2','scalehad3'] parinivals = [self.fitindex,0.,1.,1.,1.,1.,1.] parerrs = [0.0,0.0,0.0,0.0,0.0,0.0,0.0] bestParValues = [] nPars = 6 if self.topology==1 else 7 for i in range(1,nPars) : bestParValues.append(parinivals[i]) ierflag = Long(1) arglist = array( 'd', [-1.0] ) minuit = TMinuit(nPars) minuit.mnexcm('SET PRINT', arglist, 1,ierflag) minuit.mnexcm('SET NOWARNINGS',arglist,1,ierflag) arglist[0] = 100000. #add parameters to the fitter for i in range(nPars) : minuit.mnparm(i,parNames[i],parinivals[i],1.0,0.,0.,ierflag) #fix the first 'fit index' parameter minuit.mnfixp(0,ierflag) #set the minimization function to use if self.topology==1 : minuit.SetFCN(fcnt1) elif self.topology==2 : minuit.SetFCN(fcnt2) elif self.topology==3 : minuit.SetFCN(fcnt3) #minimize and get the error flag minuit.mnexcm('MIGRAD', arglist, 1, ierflag) #print 'index = %d, errflag = %s'%(self.fitindex,ierflag) #DEBUG errflag = int(ierflag) #Check fit Chi2 tmp1 = Double(1.0); tmp2 = Double(1.0); tmp3 = Double(1.0) minuit.mnstat(tmp1,tmp2,tmp3,Long(1),Long(1),Long(1)) #print 'chi2 = %.4f'%(tmp1) #DEBUG chi2value = float(tmp1) #Get the bestfit parameters back from minuit for j in range(1,nPars) : tmp = Double(1.0) minuit.GetParameter(j,tmp,Double(parerrs[j])) bestParValues[j-1] = float(tmp) return errflag, chi2value, bestParValues
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 test1MinuitFit( self ): """Test minuit callback and fit""" # setup minuit and callback gMinuit = TMinuit(5) gMinuit.SetPrintLevel( -1 ) # quiet gMinuit.SetGraphicsMode( ROOT.kFALSE ) gMinuit.SetFCN( fcn ) arglist = array( 'd', 10*[0.] ) ierflg = Long() arglist[0] = 1 gMinuit.mnexcm( "SET ERR", arglist, 1, ierflg ) # set starting values and step sizes for parameters vstart = array( 'd', [ 3, 1, 0.1, 0.01 ] ) step = array( 'd', [ 0.1, 0.1, 0.01, 0.001 ] ) gMinuit.mnparm( 0, "a1", vstart[0], step[0], 0, 0, ierflg ) gMinuit.mnparm( 1, "a2", vstart[1], step[1], 0, 0, ierflg ) gMinuit.mnparm( 2, "a3", vstart[2], step[2], 0, 0, ierflg ) gMinuit.mnparm( 3, "a4", vstart[3], step[3], 0, 0, ierflg ) # now ready for minimization step arglist[0] = 500 arglist[1] = 1. gMinuit.mnexcm( "MIGRAD", arglist, 2, ierflg ) # verify results amin, edm, errdef = Double(), Double(), Double() nvpar, nparx, icstat = Long(), Long(), Long() gMinuit.mnstat( amin, edm, errdef, nvpar, nparx, icstat ) # gMinuit.mnprin( 3, amin ) self.assertEqual( nvpar, 4 ) self.assertEqual( nparx, 4 ) # success means that full covariance matrix is available (icstat==3) self.assertEqual( icstat, 3 ) # check results (somewhat debatable ... ) par, err = Double(), Double() gMinuit.GetParameter( 0, par, err ) self.assertEqual( round( par - 2.15, 2 ), 0. ) self.assertEqual( round( err - 0.10, 2 ), 0. ) gMinuit.GetParameter( 1, par, err ) self.assertEqual( round( par - 0.81, 2 ), 0. ) self.assertEqual( round( err - 0.25, 2 ), 0. ) gMinuit.GetParameter( 2, par, err ) self.assertEqual( round( par - 0.17, 2 ), 0. ) self.assertEqual( round( err - 0.40, 2 ), 0. ) gMinuit.GetParameter( 3, par, err ) self.assertEqual( round( par - 0.10, 2 ), 0. ) self.assertEqual( round( err - 0.16, 2 ), 0. )
g1 = 0. g2 = 0. for i in range(len(error_w1)): g1 += error_w1[i]**2 g2 += error_w2[i]**2 s = error_w1[3]*error_w2[3] t = error_w1[4]*error_w2[4] global kor_matirx kor_matirx = np.array([[g1, s+t], [ s+t, g2]]) kor_matirx_inv = inv(kor_matirx) print kor_matirx # --> set up MINUIT myMinuit = TMinuit(npar) # initialize TMinuit with maximum of npar parameters myMinuit.SetFCN(fcn) # set function to minimize arglist = arr('d', 2*[0.01]) # set error definition ierflg = Long(0) arglist[0] = 1. # 1 sigma is Delta chi^2 = 1 myMinuit.mnexcm("SET ERR", arglist ,1,ierflg) # --> Set starting values and step sizes for parameters # Define the parameters for the fit myMinuit.mnparm(1, "mean", 400, 0.001, 0,0,ierflg) arglist[0] = 6000 # Number of calls to FCN before giving up. arglist[1] = 0.3 # Tolerance myMinuit.mnexcm("MIGRAD", arglist ,2,ierflg) # execute the minimisation
outTag =outTag_name+str(q) loaded_model = joblib.load(os.path.expanduser('~/HHbbgg_ETH_devel/bregression/output_files/regression_heppy_'+outTag+'.pkl')) X_pred_data = loaded_model.predict(X_test_features).astype(np.float64) X_predicted_all.append(X_pred_data) n_evt = len(X_predicted_all[0]) X_predictions_for_fit = np.column_stack([x for x in X_predicted_all]) mpvs=[] print 'here' fit_quantile=[] gMinuit = TMinuit(4) gMinuit.SetPrintLevel(-1) gMinuit.SetFCN( fcn ) arglist = np.array( 10*[0.] ) ierflg = 0 arglist[0] = 1 gMinuit.mnexcm( "SET ERR", arglist, 1, Long(ierflg) ) arglist[0] = 0 gMinuit.mnexcm("SET PRINT", arglist ,0,Long(ierflg)); vstart = np.array( [ -2 , 4,-1, 0.5] ) step = np.array( [ 0.001, 0.001, 0.01, 0.01 ] ) gMinuit.mnparm( 0, "a1", vstart[0], step[0], 0, 0, Long(ierflg) ) gMinuit.mnparm( 1, "a2", vstart[1], step[1], 0, 0, Long(ierflg) ) gMinuit.mnparm( 2, "a3", vstart[2], step[2], 0, 0, Long(ierflg) ) gMinuit.mnparm( 3, "a4", vstart[3], step[3], 0, 0, Long(ierflg) ) arglist[0] = 500
g1 = 0. g2 = 0. for i in range(len(error_w1)): g1 += error_w1[i]**2 g2 += error_w2[i]**2 s = error_w1[3] * error_w2[3] t = error_w1[4] * error_w2[4] global kor_matirx kor_matirx = np.array([[g1, s + t], [s + t, g2]]) kor_matirx_inv = inv(kor_matirx) print kor_matirx # --> set up MINUIT myMinuit = TMinuit(npar) # initialize TMinuit with maximum of npar parameters myMinuit.SetFCN(fcn) # set function to minimize arglist = arr('d', 2 * [0.01]) # set error definition ierflg = Long(0) arglist[0] = 1. # 1 sigma is Delta chi^2 = 1 myMinuit.mnexcm("SET ERR", arglist, 1, ierflg) # --> Set starting values and step sizes for parameters # Define the parameters for the fit myMinuit.mnparm(1, "mean", 400, 0.001, 0, 0, ierflg) arglist[0] = 6000 # Number of calls to FCN before giving up. arglist[1] = 0.3 # Tolerance myMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) # execute the minimisation # --> check TMinuit status
class Fits: def __init__(self): self.p_n = [ 0, ] * 100 self.e_n = [ 0, ] * 100 self.stored_parameters = [ 0, ] * 100 self.num_bins = 0 self.xmins = [] self.xmaxes = [] self.data = [] self.errors = [] self.data_fits = [] self.model_scale_values = [] self.final = False self.exclude_regions = ((0, 0), ) self.col1 = 1 self.col2 = TColor.GetColor(27, 158, 119) self.col3 = TColor.GetColor(217, 95, 2) self.col4 = TColor.GetColor(117, 112, 179) def run_mass_fit(self, peak_scale_initial, mass=0): self.gMinuit = TMinuit(30) self.gMinuit.SetPrintLevel(-1) self.gMinuit.SetFCN(self.Fitfcn_max_likelihood) arglist = array("d", [ 0, ] * 10) ierflg = ROOT.Long(0) arglist[0] = ROOT.Double(1) # peak_scale_initial = ROOT.Double(peak_scale_initial) tmp = array("d", [ 0, ]) self.gMinuit.mnexcm("SET NOWarnings", tmp, 0, ierflg) self.gMinuit.mnexcm("SET ERR", arglist, 1, ierflg) self.gMinuit.mnparm(0, "p1", 5e-6, 1e-7, 0, 0, ierflg) self.gMinuit.mnparm(1, "p2", 10, 10, 0, 0, ierflg) self.gMinuit.mnparm(2, "p3", -5.3, 1, 0, 0, ierflg) self.gMinuit.mnparm(3, "p4", -4e-2, 1e-2, 0, 0, ierflg) self.gMinuit.mnparm(4, "p5", peak_scale_initial, peak_scale_initial / 50, 0, 0, ierflg) self.background_fit_only = [ 0, ] * len(self.data) arglist[0] = ROOT.Double(0) arglist[1] = ROOT.Double(0) #self.exclude_regions = ((2.2, 3.3),) self.gMinuit.FixParameter(2) self.gMinuit.FixParameter(3) self.gMinuit.FixParameter(4) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) self.gMinuit.Release(2) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) self.gMinuit.Release(3) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) # Find an actual best fit #self.exclude_regions = ((0, 2), (3.3, 100),) self.gMinuit.FixParameter(0) self.gMinuit.FixParameter(1) self.gMinuit.FixParameter(2) self.gMinuit.FixParameter(3) self.gMinuit.Release(4) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) self.exclude_regions = () self.gMinuit.Release(0) self.gMinuit.Release(1) self.gMinuit.Release(2) self.gMinuit.Release(3) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) best_fit_value = ROOT.Double(0) self.gMinuit.mnstat(best_fit_value, ROOT.Double(0), ROOT.Double(0), ROOT.Long(0), ROOT.Long(0), ROOT.Long(0)) #print("Best fit value", best_fit_value) # And prepare for iterating over fit values for N injected events self.gMinuit.Release(0) self.gMinuit.Release(1) self.gMinuit.Release(2) self.gMinuit.Release(3) self.exclude_regions = () #self.data_fits = no_peak_data_fits x_values = [] y_values = [] fitted_N = ROOT.Double(0) self.gMinuit.GetParameter(4, fitted_N, ROOT.Double(0)) best_fit_likelihood = self.calc_likelihood(fitted_N) step = 5 if int(mass) >= 4000: step = 1 if int(mass) >= 5000: step = 0.2 if int(mass) >= 6000: step = 0.1 if int(mass) >= 6500: step = 0.05 N = 0 while N < 5000: start_sum = sum([math.exp(-a) for a in y_values]) fit_likelihood = self.calc_likelihood(N) x_values.append(N) y_values.append(fit_likelihood - best_fit_likelihood) probabilities = [math.exp(-a) for a in y_values] end_sum = sum(probabilities) max_prob = max(probabilities) normalised_probabilities = [a / max_prob for a in probabilities] if N / step > 50 and all( [v > 0.99 for v in normalised_probabilities]): print( "Probability=1 everywhere, probably something wrong with fit" ) print(normalised_probabilities) return None, None # if new value changes total by less than 0.1%, end loop if N > 0 and (end_sum - start_sum) / start_sum < 0.0001: print("Iterated up to {0}".format(N)) break N += step self.iflag = int(ierflg) return x_values, y_values def Fitfcn_max_likelihood(self, npar, gin, fcnVal, par, iflag): likelihood = 0 mf = ROOT.MyMassSpectrum() mf.SetParameters(par) ig = GaussIntegrator() ig.SetFunction(mf) ig.SetRelTolerance(0.00001) for i in range(0, self.num_bins): for lower, higher in self.exclude_regions: if lower < self.xmins[i] < higher: continue model_val = ig.Integral(self.xmins[i], self.xmaxes[i]) / ( self.xmaxes[i] - self.xmins[i]) self.background_fit_only[i] = model_val model_val += self.model_scale_values[i] * par[4] self.data_fits[i] = model_val likelihood += model_val - self.data[i] if self.data[i] > 0 and model_val > 0: likelihood += self.data[i] * (math.log(self.data[i]) - math.log(model_val)) fcnVal[0] = likelihood def calc_likelihood(self, peak_scale): like = 0 for i in range(0, self.num_bins): if self.data_fits[i] <= 0: continue p = peak_scale * self.model_scale_values[i] tmp = ROOT.TMath.PoissonI(self.data[i], self.background_fit_only[i] + p) #if peak_scale == 40000: # print(i, "\txmin", self.xmins[i], "\tdata", self.data[i], "\tdata_fit", self.data_fits[i], "\tp", p, "\tdata_fit+p", self.data_fits[i]+p) if tmp == 0: print("tmp == 0 here") logtmp = math.log(sys.float_info.min) else: logtmp = math.log(tmp) like += logtmp return -like
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" ) 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 ), 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( "" ), for i in range(mshape[0]): print "{0:>10s}".format( self.__parnames[i] ), print for i in range(mshape[0]): print "{0:>10s}".format( self.__parnames[i] ), for j in range(mshape[1]): fmtstr= "{0:10"+ffmt+"}" print fmtstr.format( m[i,j] ), 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= Double() pare= Double() ivarbl= self.__minuit.GetParameter( ipar, par, pare ) if ivarbl < 0: message= "Parameter " + str(ipar) + " not defined" raise MinuitError( message ) pars.append( par ) parerrors.append( pare ) 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= Double() fedm= Double() errdef= Double() npari= Long() nparx= Long() istat= Long() self.__minuit.mnstat( fmin, fedm, errdef, npari, nparx, istat ) hstat= { "min": fmin, "edm": fedm, "errdef": errdef, "npari": npari, "nparx": nparx, "status": istat } return hstat
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 Fits: def __init__(self): self.p_n = [ 0, ] * 100 self.e_n = [ 0, ] * 100 self.stored_parameters = [ 0, ] * 100 self.num_bins = 0 self.xmins = [] self.xmaxes = [] self.data = [] self.errors = [] self.data_fits = [] self.model_scale_values = [] self.final = False self.exclude_regions = ((0, 0), ) self.col1 = 1 self.col2 = TColor.GetColor(27, 158, 119) self.col3 = TColor.GetColor(217, 95, 2) self.col4 = TColor.GetColor(117, 112, 179) def run_mass_fit(self, peak_scale_initial): self.gMinuit = TMinuit(30) self.gMinuit.SetPrintLevel(-1) self.gMinuit.SetFCN(self.Fitfcn_max_likelihood) self.fit_failed = False arglist = array("d", [ 0, ] * 10) ierflg = ROOT.Long(0) arglist[0] = ROOT.Double(1) # peak_scale_initial = ROOT.Double(peak_scale_initial) tmp = array("d", [ 0, ]) self.gMinuit.mnexcm("SET NOWarnings", tmp, 0, ierflg) self.gMinuit.mnexcm("SET ERR", arglist, 1, ierflg) self.gMinuit.mnparm(0, "p1", 30, 20, 0, 100, ierflg) self.gMinuit.mnparm(1, "p2", 10, 1, 0, 0, ierflg) self.gMinuit.mnparm(2, "p3", -5.3, 1, 0, 0, ierflg) self.gMinuit.mnparm(3, "p4", -4e-2, 1e-2, 0, 0, ierflg) self.gMinuit.mnparm(4, "p5", peak_scale_initial, 1, 0, 10000, ierflg) self.background_fit_only = [ 0, ] * len(self.data) arglist[0] = ROOT.Double(0) arglist[1] = ROOT.Double(0) #self.exclude_regions = ((2.2, 3.3),) self.gMinuit.FixParameter(1) self.gMinuit.FixParameter(2) self.gMinuit.FixParameter(3) self.gMinuit.FixParameter(4) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) print("Run fit 0") if self.fit_failed: print("Fit failed, returning") return None, None self.gMinuit.Release(1) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) print("Run fit 1") if self.fit_failed: print("Fit failed, returning") return None, None self.gMinuit.Release(2) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) print("Run fit 2") if self.fit_failed: print("Fit failed, returning") return None, None self.gMinuit.Release(3) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) print("Run fit 3") if self.fit_failed: print("Fit failed, returning") return None, None # Find an actual best fit #self.exclude_regions = ((0, 2), (3.3, 100),) self.gMinuit.FixParameter(0) self.gMinuit.FixParameter(1) self.gMinuit.FixParameter(2) self.gMinuit.FixParameter(3) self.gMinuit.Release(4) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) print("Run fit 4") if self.fit_failed: print("Fit failed, returning") return None, None self.exclude_regions = () self.gMinuit.Release(0) self.gMinuit.Release(1) self.gMinuit.Release(2) self.gMinuit.Release(3) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) print("Run last fitting stage") if self.fit_failed: print("Fit failed, returning") return None, None best_fit_value = ROOT.Double(0) self.gMinuit.mnstat(best_fit_value, ROOT.Double(0), ROOT.Double(0), ROOT.Long(0), ROOT.Long(0), ROOT.Long(0)) #print("Best fit value", best_fit_value) # And prepare for iterating over fit values for N injected events self.gMinuit.Release(0) self.gMinuit.Release(1) self.gMinuit.Release(2) self.gMinuit.Release(3) self.exclude_regions = () #self.data_fits = no_peak_data_fits x_values = [] y_values = [] for i in range(0, 5): p = ROOT.Double(0) self.gMinuit.GetParameter(i, p, ROOT.Double(0)) print("Parameter", i, p) fitted_N = ROOT.Double(0) self.gMinuit.GetParameter(4, fitted_N, ROOT.Double(0)) best_fit_likelihood = self.calc_likelihood(fitted_N) self.iflag = int(ierflg) print("iflag:", self.iflag) step = 5 if int(MASS_FILE) >= 4000: step = 1 if int(MASS_FILE) >= 5000: step = 0.2 if int(MASS_FILE) >= 6000: step = 0.1 if int(MASS_FILE) >= 6500: step = 0.1 N = 0 print("About to start likelihood testing loop") while N < 10000: start_sum = sum([math.exp(-a) for a in y_values]) fit_likelihood = self.calc_likelihood(N) if fit_likelihood is None: print("Bad fit_likelihood") return None, None x_values.append(N) y_values.append(fit_likelihood - best_fit_likelihood) probabilities = [math.exp(-a) for a in y_values] end_sum = sum(probabilities) max_prob = max(probabilities) if max_prob == 0: print("max_prob == 0") return None, None normalised_probabilities = [a / max_prob for a in probabilities] if N / step > 50 and all( [v > 0.99 for v in normalised_probabilities]): print( "Probability=1 everywhere, probably something wrong with fit" ) print(normalised_probabilities) return None, None # if new value changes total by less than 0.1%, end loop if N > 0 and (end_sum - start_sum) / start_sum < 0.0001: print("Iterated up to {0}".format(N)) break N += step self.iflag = int(ierflg) return x_values, y_values def Fitfcn_max_likelihood(self, npar, gin, fcnVal, par, iflag): likelihood = 0 mf = ROOT.MyMassSpectrum() mf.SetParameters(par) ig = GaussIntegrator() ig.SetFunction(mf) ig.SetRelTolerance(0.00001) for i in range(0, self.num_bins): for lower, higher in self.exclude_regions: if lower < self.xmins[i] < higher: continue model_val = ig.Integral(self.xmins[i], self.xmaxes[i]) / ( self.xmaxes[i] - self.xmins[i]) self.background_fit_only[i] = model_val model_val += self.model_scale_values[i] * par[4] self.data_fits[i] = model_val mv = model_val di = self.data[i] #print("mv", mv, "di", di) #sys.stdout.flush() if di > 1e10 or math.isinf(mv) or math.isnan(mv): self.fit_failed = True return likelihood += mv - di if di > 0 and mv > 0: likelihood += di * (TMath.Log(di) - TMath.Log(mv)) #sys.stderr.flush() fcnVal[0] = likelihood def calc_likelihood(self, peak_scale): like = 0 for i in range(0, self.num_bins): if self.data_fits[i] <= 0: continue p = peak_scale * self.model_scale_values[i] tmp = ROOT.TMath.PoissonI(self.data[i], self.background_fit_only[i] + p) if tmp == 0: return None print("tmp == 0 here") logtmp = math.log(sys.float_info.min) else: logtmp = math.log(tmp) like += logtmp return -like
def ifit(): gMinuit = TMinuit(npars) gMinuit.SetFCN(fcn) arglist = array('d', 10*[0.]) ierflg = Long(1982) arglist[0] = 1 # 1 for chisquared fits, 0.5 for negative log likelihood gMinuit.mnexcm("SET ERR", arglist, 1, ierflg) arglist[0] = 0.00001 # floating point precision gMinuit.mnexcm("SET EPS", arglist, 1, ierflg) arglist[0] = 2 # 1 mean fewer function calls, 2 mean more reliable minimization gMinuit.mnexcm("SET STR", arglist, 1, ierflg) # Set starting values and step sizes for parameters vstart = array('d', npars*[1.0]) #vstart = array('d', [0.909, 1.341, 0.942, 1.300, 1.003]) step = array('d', npars*[0.001]) gMinuit.mnparm(0, "ZjLF", vstart[0], step[0], 0, 0, ierflg) gMinuit.mnparm(1, "ZjHF", vstart[1], step[1], 0, 0, ierflg) gMinuit.mnparm(2, "WjLF", vstart[2], step[2], 0, 0, ierflg) gMinuit.mnparm(3, "WjHF", vstart[3], step[3], 1.3, 2.0, ierflg) gMinuit.mnparm(4, "TT" , vstart[4], step[4], 0, 0, ierflg) # Fix parameter #arglist[0] = 1 #gMinuit.mnexcm("FIX", arglist, 1, ierflg) #arglist[0] = 2 #gMinuit.mnexcm("FIX", arglist, 1, ierflg) #arglist[0] = 3 #gMinuit.mnexcm("FIX", arglist, 1, ierflg) #arglist[0] = 4 #gMinuit.mnexcm("FIX", arglist, 1, ierflg) #arglist[0] = 5 #gMinuit.mnexcm("FIX", arglist, 1, ierflg) # Scan for best parameter values #arglist[0] = 1 #gMinuit.mnexcm("SCAN", arglist, 0, ierflg) # Now ready for minimization step arglist[0] = 500 arglist[1] = 1. # default: 0.1 gMinuit.mnexcm("SIMPLEX", arglist, 2, ierflg) #gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) # Search for additional distinct local minima #arglist[0] = 10000 #gMinuit.mnexcm("IMP", arglist, 1, ierflg) # Do error analysis gMinuit.mnexcm("HESSE", arglist, 0, ierflg) #arglist[0] = 10000 #gMinuit.mnexcm("MINOS", arglist, 1, ierflg) # Print results amin, edm, errdef = Double(0.18), Double(0.19), Double(0.20) # passing doubles by reference is allowed, as long as on the python side doubles are unique (init them with different values, for example). nvpar, nparx, icstat = Long(1986), Long(1987), Long(1988) gMinuit.mnstat(amin, edm, errdef, nvpar, nparx, icstat) gMinuit.mnprin(5, amin) ndf = 0 for i in xrange(len(data[0])): # loop over all bins mc_ = [m[i] for m in mc] if sum(mc_) < tolerance: continue if data[0][i] < tolerance: continue ndf += 1 print "chi^2 = ", amin, ", NDF = ", ndf, ", chi^2/NDF = ", amin/ndf, ", prob = ", TMath.Prob(amin,ndf) print "amin: ", amin, ", edm: ", edm, ", errdef: ", errdef, ", nvpar: ", nvpar, ", nparx: ", nparx, ", icstat: ", icstat print "c0: ", vstart[0], ", c1: ", vstart[1], ", c2: ", vstart[2], ", c3: ", vstart[3], ", c4: ", vstart[4]
def fit_model(self, country, time_range): s_data = self.data_sir["susceptible"] i_data = self.data_sir["infected"] r_data = self.data_sir["recovered"] def fcn(nPar, gin, f, par, flag): tr = par[0] rec = par[1] chi2 = 0. n_bins = s_data.GetNbinsX() s_model, i_model, r_model = solve_SIR(tr, rec, time_range) for i in range(1, n_bins + 1): dif = (i_data.GetBinContent(i) - i_model.GetBinContent(i)) / i_data.GetBinError(i) chi2 += dif**2 dif = (r_data.GetBinContent(i) - r_model.GetBinContent(i)) / r_data.GetBinError(i) chi2 += dif**2 f[0] = chi2 return minuit = TMinuit(3) minuit.SetFCN(fcn) vstep = [1e-10, 1e-10] vinit = [1e-10, 1e-10] # vinit = [1e-3, 1e-3] vmin = [1e-15, 1e-15] vmax = [1e0, 1e0] from ctypes import c_int, c_double import array as c_arr arglist = c_arr.array('d', 10 * [0.]) ierflag = 0 arglist[0] = 1 minuit.mnexcm("SET ERR", arglist, 1, c_int(ierflag)) minuit.mnparm(0, "transition rate", vinit[0], vstep[0], vmin[0], vmax[0], c_int(ierflag)) minuit.mnparm(1, "recovery rate", vinit[1], vstep[1], vmin[1], vmax[1], c_int(ierflag)) # minuit.SetErrorDef(1.) # minuit.SetMaxIteration(500) # minuit.Migrad() arglist[0] = 500 arglist[1] = 1. # minuit.mnexcm( "MIGRAD", arglist, 2, c_int(ierflag) ) res_trans = c_double(0.) res_rec = c_double(0.) err_trans = c_double(0.) err_rec = c_double(0.) minuit.GetParameter(0, res_trans, err_trans) minuit.GetParameter(1, res_rec, err_rec) # return res_trans.value, res_rec.value hists = self.solve_SIR(res_trans.value, res_rec.value, time_range) self.hists = { "susceptible": hists[0], "infected": hists[1], "recovered": hists[2] } self.draw()
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 make_renormalization_dict(name,alpha,epsilon,muRup,muRdown,muFup,muFdown,scup,scdown,pdfas,pdfasup,pdfasdown,topptrwhist) : global qq_global_hist_projection global gg_global_hist_projection returndict = {} #start with the simple values returndict['muRup']=muRup.GetMean() returndict['muRdown']=muRdown.GetMean() returndict['muFup']=muFup.GetMean() returndict['muFdown']=muFdown.GetMean() returndict['scup']=scup.GetMean() returndict['scdown']=scdown.GetMean() returndict['pdfas']=pdfas.GetMean() returndict['pdfasup']=pdfasup.GetMean() returndict['pdfasdown']=pdfasdown.GetMean() returndict['topptrwmean']=topptrwhist.GetMean() printlines = [] if alpha==1.0 and epsilon==1.0 and (name.find('_TT')!=-1 or name.find('_TTJets')!=-1) : #now we've got to do the fits for the "deweighting" alpha and epsilon values print 'fitting for alpha/epsilon values... ' #Define the Minuit instance we'll use alpha_minuit = TMinuit(1); alpha_minuit.SetFCN(alpha_fcn) epsilon_minuit = TMinuit(1); epsilon_minuit.SetFCN(epsilon_fcn) #miscellaneous minuit stuff ierflag = Long(1); arglist = array('d',[-1]) alpha_minuit.mnexcm('SET PRINT', arglist, 1,ierflag); alpha_minuit.mnexcm('SET NOWARNINGS',arglist,1,ierflag) epsilon_minuit.mnexcm('SET PRINT', arglist, 1,ierflag); epsilon_minuit.mnexcm('SET NOWARNINGS',arglist,1,ierflag) arglist[0]=100000. #for each bin in beta for i in range(1,csvb_qq_global_hist.GetXaxis().GetNbins()+1) : BETA[0] = csvb_qq_global_hist.GetXaxis().GetBinCenter(i) #qq_global_hist_projection = symmetrize(csvb_qq_global_hist.ProjectionY('qq_proj_'+str(i),i,i)) qq_global_hist_projection = csvb_qq_global_hist.ProjectionY('qq_proj_'+str(i),i,i) #add parameter alpha_minuit.mnparm(0,'alpha',0.1,0.5,0.,0.,ierflag) #minimize alpha_minuit.mnexcm('MIGRAD',arglist,1,ierflag) #get back the fitted alpha value fitted_alpha=Double(0.0); fitted_alpha_err=Double(0.0) alpha_minuit.GetParameter(0,fitted_alpha,fitted_alpha_err) #print 'NEW NAME = alpha_'+str(csvb_qq_global_hist.GetXaxis().GetBinLowEdge(i)) returndict['alpha_'+str(csvb_qq_global_hist.GetXaxis().GetBinLowEdge(i))]=fitted_alpha printlines.append('fitted alpha value in bin %d = %.4f +/- %.4f'%(i,fitted_alpha,fitted_alpha_err)) #epsilon stuff for i in range(1,csvb_gg_global_hist.GetXaxis().GetNbins()+1) : BETA[0] = csvb_gg_global_hist.GetXaxis().GetBinCenter(i) #gg_global_hist_projection = symmetrize(csvb_gg_global_hist.ProjectionY('gg_proj_'+str(i),i,i)) gg_global_hist_projection = csvb_gg_global_hist.ProjectionY('gg_proj_'+str(i),i,i) epsilon_minuit.mnparm(0,'epsilon',0.1,0.5,0.,0.,ierflag) epsilon_minuit.mnexcm('MIGRAD',arglist,1,ierflag) fitted_epsilon=Double(0.0); fitted_epsilon_err=Double(0.0) epsilon_minuit.GetParameter(0,fitted_epsilon,fitted_epsilon_err) returndict['epsilon_'+str(csvb_gg_global_hist.GetXaxis().GetBinLowEdge(i))]=fitted_epsilon printlines.append('fitted epsilon value in bin %d = %.4f +/- %.4f'%(i,fitted_epsilon,fitted_epsilon_err)) else : returndict['alpha']=alpha; returndict['epsilon']=epsilon fitted_alpha_err = 1.0; fitted_epsilon_err = 1.0 #print the values print 'muRup value = %.4f'%(returndict['muRup']) print 'muRdown value = %.4f'%(returndict['muRdown']) print 'muFup value = %.4f'%(returndict['muFup']) print 'muFdown value = %.4f'%(returndict['muFdown']) print 'scup value = %.4f'%(returndict['scup']) print 'scdown value = %.4f'%(returndict['scdown']) print 'pdfas value = %.4f'%(returndict['pdfas']) print 'pdfasup value = %.4f'%(returndict['pdfasup']) print 'pdfasdown value = %.4f'%(returndict['pdfasdown']) print 'top pT reweight (v1) mean value = %.4f'%(returndict['topptrwmean']) for line in printlines : print line #return the dictionary return returndict
def Minuit(fun, x0, args=(), bounds=None, verbose=0, step_size=0.01, n_iterations=500, delta_one_sigma=0.5, **options): """ Interface to ROOT Minuit @param bounds list of tuple with limits for each variable @param verbose -1 (silent) 0 (normal) 1 (verbose) @param step_size float or Sequence @param n_iterations maximum number of iterations @param delta_one_sigma delta in loss function which corresponds to one sigma errors """ from ROOT import TMinuit, Double, Long n_parameters = len(x0) def to_minimize(npar, deriv, f, apar, iflag): f[0] = fun(np.array([apar[i] for i in range(n_parameters)]), *args) minuit = TMinuit(n_parameters) minuit.SetFCN(to_minimize) minuit.SetPrintLevel(verbose) ierflg = np.array(0, dtype=np.int32) minuit.mnexcm("SET ERR", np.array([delta_one_sigma]), 1, ierflg) if ierflg > 0: raise RuntimeError("Error during \"SET ERR\" call") for i in range(n_parameters): low, high = 0.0, 0.0 # Zero seems to mean no limit if bounds is not None: low, high = bounds[i] minuit.mnparm( i, 'Parameter_{}'.format(i), x0[i], step_size[i] if isinstance( step_size, collections.Sequence) else step_size, low, high, ierflg) if ierflg > 0: raise RuntimeError( "Error during \"nmparm\" call for parmameter {}".format(i)) minuit.mnexcm("MIGRAD", np.array([n_iterations, 0.01], dtype=np.float64), 2, ierflg) if ierflg > 0: raise RuntimeError("Error during \"MIGRAD\" call") xbest = np.zeros(n_parameters) xbest_error = np.zeros(n_parameters) p, pe = Double(0), Double(0) for i in range(n_parameters): minuit.GetParameter(i, p, pe) xbest[i] = p xbest_error[i] = pe buf = array.array('d', [0.] * (n_parameters**2)) minuit.mnemat(buf, n_parameters) # retrieve error matrix hessian = np.array(buf).reshape(n_parameters, n_parameters) amin = Double(0.) # value of fcn at minimum edm = Double(0.) # estimated distance to mimimum errdef = Double(0.) # delta_fcn used to define 1 sigma errors nvpar = np.array(0, dtype=np.int32) # number of variable parameters nparx = np.array(0, dtype=np.int32) # total number of parameters icstat = np.array( 0, dtype=np.int32 ) # status of error matrix: 3=accurate 2=forced pos. def 1= approximative 0=not calculated minuit.mnstat(amin, edm, errdef, nvpar, nparx, icstat) return scipy.optimize.OptimizeResult(x=xbest, fun=amin, hessian=hessian, success=(ierflg == 0), status=ierflg)
def fit( p , perr ): global val, err, nBins val = p err = perr nBins=len(val) #name=['q0','q1','q2','q3','q4'] #name=['q0','q1','q2','q3'] #name=['q0','q1','q2'] name = [ 'q0' , 'q1' ] npar=len(name) # the initial values vstart = arr( 'd' , npar*[0.1] ) # the initial step size step = arr( 'd' , npar*[0.000001] ) # --> set up MINUIT gMinuit = TMinuit ( npar ) # initialize TMinuit with maximum of npar parameters gMinuit.SetFCN( fcn ) # set function to minimize arglist = arr( 'd' , npar*[0.01] ) # set error definition ierflg = c_int(0) arglist[0] = 1. # 1 sigma is Delta chi2 = 1 gMinuit.mnexcm("SET ERR", arglist, 1, ierflg ) # --> set starting values and step size for parameters # Define the parameters for the fit for i in range(0,npar): gMinuit.mnparm( i, name[i] , vstart[i] , step[i] , 0, 0, ierflg ) # now ready for minimization step arglist [0] = 500 # Number of calls for FCN before giving up arglist [1] = 1. # Tolerance gMinuit.mnexcm("MIGRAD" , arglist , 2 , ierflg) # execute the minimisation # --> check TMinuit status amin , edm , errdef = c_double(0.) , c_double(0.) , c_double(0.) nvpar , nparx , icstat = c_int(0) , c_int(0) , c_int(0) gMinuit.mnstat (amin , edm , errdef , nvpar , nparx , icstat ) gMinuit.mnprin(3,amin) # print-out by Minuit # meaning of parameters: # amin: value of fcn distance at minimum (=chi^2) # edm: estimated distance to minimum # errdef: delta_fcn used to define 1 sigam errors # nvpar: total number of parameters # icstat: status of error matrix: # 3 = accurate # 2 = forced pos. def # 1 = approximative # 0 = not calculated # # --> get results from MINUIT finalPar = [] finalParErr = [] p, pe = c_double(0.) , c_double(0.) for i in range(0,npar): gMinuit.GetParameter(i, p, pe) # retrieve parameters and errors finalPar.append( float(p.value) ) finalParErr.append( float(pe.value) ) # get covariance matrix buf = arr('d' , npar*npar*[0.]) gMinuit.mnemat( buf , npar ) # retrieve error matrix emat = np.array( buf ).reshape( npar , npar ) # --> provide formatted output of results print "\n" print "*==* MINUIT fit completed:" print'fcn@minimum = %.3g'%( amin.value ) , " error code =" , ierflg.value , " status =" , icstat.value , " (if its 3, mean accurate)" print " Results: \t value error corr. mat." for i in range(0,npar): print'%s: \t%10.3e +/- %.1e'%( name[i] , finalPar[i] , finalParErr[i] ) , for j in range (0,i): print'%+.3g'%( emat[i][j]/np.sqrt(emat[i][i])/np.sqrt(emat[j][j]) ), print "\n" return [ [i,j] for i,j in zip(finalPar , finalParErr) ]
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))