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 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
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
def signalfit(data_hist, signalfunction, signalname, process): binning = HistBinsToList(data_hist) data_x = HistToList(data_hist) data_error = HistErrorList(data_hist) parfunction = signalfunction.GetNumberFreeParameters() partot = signalfunction.GetNumberFreeParameters() print partot ### the fucntion used for TMinuit def fcn(npar, gin, f, par, ifag): L = 0 # calculate likelihood, input par[0] is the N_B, par[1] is N_C, par[2] is N_L for ibin in range(len(binning)): #if (data_x[ibin] ==0): # continue bincen = binning[ibin] mu_x = 0 data = data_x[ibin] if data_error[ibin] == 0: continue #if data<0.1: # continue if signalname == "CrystalBall": if par[3] < 0: mu_x = 0 else: t = (bincen - par[2]) / (par[3]) if (par[0] < 0): t = -t absAlpha = abs(par[0]) if (t >= -absAlpha): mu_x = par[4] * exp(-0.5 * t * t) else: nDivAlpha = par[1] / absAlpha AA = exp(-0.5 * absAlpha * absAlpha) B = nDivAlpha - absAlpha arg = nDivAlpha / (B - t) mu_x = par[4] * (arg**par[1]) if signalname == "CrystalBallGaus": if par[3] < 0: mu_x = 0 else: t = (bincen - par[2]) / (par[3]) if (par[0] < 0): t = -t absAlpha = abs(par[0]) if (t >= -absAlpha): mu_x = par[4] * exp(-0.5 * t * t + exp(-(bincen - par[5])**2 / (2 * par[6]**2))) else: nDivAlpha = par[1] / absAlpha AA = exp(-0.5 * absAlpha * absAlpha) B = nDivAlpha - absAlpha arg = nDivAlpha / (B - t) mu_x = par[4] * (arg**par[1] + exp(-(bincen - par[5])**2 / (2 * par[6]**2))) #print mu_x, data, data_error[ibin] #L = L + mu_x - data*log(mu_x) L = L + ((mu_x - data) / data_error[ibin])**2 f[0] = L # initialize the TMinuit object arglist_p = 10 * [0] arglist = array.array('d') arglist.fromlist(arglist_p) ierflag = Long(0) maxiter = 1000000000 arglist_p = [1] gMinuit = TMinuit(partot) gMinuit.mnexcm('SET PRIntout', arglist, 0, ierflag) gMinuit.SetPrintLevel(1) gMinuit.SetErrorDef(1.0) gMinuit.SetFCN(fcn) arglist_p = [2] arglist = array.array('d') arglist.fromlist(arglist_p) gMinuit.mnexcm('SET STRategy', arglist, 1, ierflag) arglist_p = [maxiter, 0.0000001] arglist = array.array('d') arglist.fromlist(arglist_p) gMinuit.mnexcm('MIGrad', arglist, 2, ierflag) gMinuit.SetMaxIterations(maxiter) # initialize fitting the variables vstart = [125.0] * partot step = [0.1] * partot upper = [1000000] * partot lower = [-100] * partot varname = [] lower[3] = 0 lower[4] = 0 lower[1] = 0 vstart[4] = data_hist.Integral() if process == "signal": vstart[2] = 125 lower[2] = 110 upper[2] = 140 vstart[3] = 10 lower[3] = 2 upper[3] = 25 if len(vstart) > 5: vstart[5] = 125 lower[5] = 110 upper[5] = 140 vstart[6] = 10 lower[6] = 5 upper[6] = 20 if process == "z": vstart[2] = 90 lower[2] = 70 upper[2] = 110 vstart[3] = 10 lower[3] = 2 upper[3] = 30 if len(vstart) > 5: vstart[5] = 90 lower[5] = 70 upper[5] = 110 vstart[6] = 10 lower[6] = 2 upper[6] = 30 for i in range(parfunction): varname.append("p" + str(i)) for i in range(partot): gMinuit.mnparm(i, varname[i], vstart[i], step[i], lower[i], upper[i], ierflag) # fitting procedure migradstat = gMinuit.Command('MIGrad ' + str(maxiter) + ' ' + str(0.001)) #improvestat = gMinuit.Command('IMProve ' + str(maxiter) + ' ' + str(0.01)) for i in range(partot): arglist_p.append(i + 1) arglist = array.array('d') arglist.fromlist(arglist_p) gMinuit.mnmnos() # get fitting parameters fitval_p = [Double(0)] * partot fiterr_p = [Double(0)] * partot errup_p = [Double(0)] * partot errdown_p = [Double(0)] * partot eparab_p = [Double(0)] * partot gcc_p = [Double(0)] * partot fmin_p = [Double(0)] fedm_p = [Double(0)] errdef_p = [Double(0)] npari_p = Long(0) nparx_p = Long(0) istat_p = Long(0) fitval = array.array('d') fiterr = array.array('d') errup = array.array('d') errdown = array.array('d') eparab = array.array('d') gcc = array.array('d') for i in range(partot): gMinuit.GetParameter(i, fitval_p[i], fiterr_p[i]) fitval.append(fitval_p[i]) fiterr.append(fiterr_p[i]) errup.append(errup_p[i]) errdown.append(errdown_p[i]) eparab.append(eparab_p[i]) gcc.append(gcc_p[i]) gMinuit.mnstat(fmin_p[0], fedm_p[0], errdef_p[0], npari_p, nparx_p, istat_p) for p in range(signalfunction.GetNumberFreeParameters()): signalfunction.SetParameter(p, fitval[p]) print "fit uncert", fiterr_p[p] signalfunction.SetChisquare(fmin_p[0]) print fmin_p[0] return fitval[partot - 1], fitval[partot - 2]
def bkgfit(data_hist, bkgfunction, bkgname, doFloatZ=False, signal_hist=None, z_hist=None): isBkgPlusZFit = False isSpuriousFit = False binning = HistBinsToList(data_hist) data_x = HistToList(data_hist) data_error = HistErrorList(data_hist) z_x = [] signal_x = [] if z_hist != None: isBkgPlusZFit = True z_x = HistToList(z_hist) if signal_hist != None: isSpuriousFit = True signal_x = HistToList(signal_hist) parfunction = bkgfunction.GetNumberFreeParameters() partot = bkgfunction.GetNumberFreeParameters() + 2 ### the fucntion used for TMinuit def fcn(npar, gin, f, par, ifag): L = 0 # calculate likelihood, input par[0] is the N_B, par[1] is N_C, par[2] is N_L for ibin in range(len(binning)): if (data_x[ibin] < 0.5): continue bincen = binning[ibin] bkg = 0 data = data_x[ibin] if bkgname == "BernsteinO2": bkg = (par[0] * (1 - (bincen - fit_start) / fit_range)**2 + 2 * par[1] * (1 - (bincen - fit_start) / fit_range) * ((bincen - fit_start) / fit_range) + par[2] * ((bincen - fit_start) / fit_range)**2) if bkgname == "BernsteinO3": bkg = par[0] * (1 - ( (bincen - fit_start) / fit_range))**3 + par[1] * ( 3 * ((bincen - fit_start) / fit_range) * (1 - ((bincen - fit_start) / fit_range))**2) + par[2] * ( 3 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))) + par[3] * ( (bincen - fit_start) / fit_range)**3 if bkgname == "BernsteinO4": bkg = par[0] * (1 - ( (bincen - fit_start) / fit_range))**4 + par[1] * ( 4 * ((bincen - fit_start) / fit_range) * (1 - ((bincen - fit_start) / fit_range))**3) + par[2] * ( 6 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))**2 ) + par[3] * ( 4 * ((bincen - fit_start) / fit_range)**3 * (1 - ((bincen - fit_start) / fit_range))) + par[4] * ( (bincen - fit_start) / fit_range)**4 if bkgname == "BernsteinO5": bkg = par[0] * (1 - ( (bincen - fit_start) / fit_range))**5 + par[1] * (5 * ( (bincen - fit_start) / fit_range) * (1 - ( (bincen - fit_start) / fit_range))**4) + par[2] * ( 10 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))**3 ) + par[3] * (10 * ( (bincen - fit_start) / fit_range)**3 * (1 - ( (bincen - fit_start) / fit_range ))**2) + par[4] * (5 * ( (bincen - fit_start) / fit_range)**4 * (1 - ((bincen - fit_start) / fit_range))) + par[5] * ( (bincen - fit_start) / fit_range)**5 if bkgname == "BernsteinO6": bkg = (par[0] * (1 - ((bincen - fit_start) / fit_range))**6 + par[1] * (6 * ((bincen - fit_start) / fit_range)**1 * (1 - ((bincen - fit_start) / fit_range))**5) + par[2] * (15 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))**4) + par[3] * (20 * ((bincen - fit_start) / fit_range)**3 * (1 - ((bincen - fit_start) / fit_range))**3) + par[4] * (15 * ((bincen - fit_start) / fit_range)**4 * (1 - ((bincen - fit_start) / fit_range))**2) + par[5] * (6 * ((bincen - fit_start) / fit_range)**5 * (1 - ((bincen - fit_start) / fit_range))**1) + par[6] * ((bincen - fit_start) / fit_range)**6) if bkgname == "ExpoBernsteinO2": try: bkg = exp(par[0] * (bincen - fit_start) / fit_range) * ( par[1] * (1 - (bincen - fit_start) / fit_range)**2 + 2 * par[2] * (1 - (bincen - fit_start) / fit_range) * ((bincen - fit_start) / fit_range) + par[3] * ((bincen - fit_start) / fit_range)**2) except OverflowError: bkg = 0 if bkgname == "ExpoBernsteinO3": try: bkg = exp(par[0] * (bincen - fit_start) / fit_range) * ( par[1] * (1 - ((bincen - fit_start) / fit_range))**3 + par[2] * (3 * ((bincen - fit_start) / fit_range) * (1 - ((bincen - fit_start) / fit_range))**2) + par[3] * (3 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))) + par[4] * ((bincen - fit_start) / fit_range)**3) except OverflowError: bkg = 0 if bkgname == "ExpoBernsteinO4": try: bkg = exp(par[0] * (bincen - fit_start) / fit_range) * ( par[1] * (1 - ((bincen - fit_start) / fit_range))**4 + par[2] * (4 * ((bincen - fit_start) / fit_range) * (1 - ((bincen - fit_start) / fit_range))**3) + par[3] * (6 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))**2) + par[4] * (4 * ((bincen - fit_start) / fit_range)**3 * (1 - ((bincen - fit_start) / fit_range))) + par[5] * ((bincen - fit_start) / fit_range)**4) except OverflowError: bkg = 0 if bkgname == "ExpoBernsteinO5": try: bkg = exp(par[0] * (bincen - fit_start) / fit_range) * ( par[1] * (1 - ((bincen - fit_start) / fit_range))**5 + par[2] * (5 * ((bincen - fit_start) / fit_range) * (1 - ((bincen - fit_start) / fit_range))**4) + par[3] * (10 * ((bincen - fit_start) / fit_range)**2 * (1 - ((bincen - fit_start) / fit_range))**3) + par[4] * (10 * ((bincen - fit_start) / fit_range)**3 * (1 - ((bincen - fit_start) / fit_range))**2) + par[5] * (5 * ((bincen - fit_start) / fit_range)**4 * (1 - ((bincen - fit_start) / fit_range))) + par[6] * ((bincen - fit_start) / fit_range)**5) except OverflowError: bkg = 0 if bkgname == "ExpoPolO2": bkg = exp(-(par[0] + par[1] * ((bincen - fit_start) / fit_range) + par[2] * ((bincen - fit_start) / fit_range)**2)) if bkgname == "ExpoPolO3": bkg = exp(-(par[0] + par[1] * ((bincen - fit_start) / fit_range) + par[2] * ((bincen - fit_start) / fit_range)**2 + par[3] * ((bincen - fit_start) / fit_range)**3)) if bkgname == "ExpoPolO4": bkg = exp(-(par[0] + par[1] * ((bincen - fit_start) / fit_range) + par[2] * ((bincen - fit_start) / fit_range)**2 + par[3] * ((bincen - fit_start) / fit_range)**3 + par[4] * ((bincen - fit_start) / fit_range)**4)) mu_x = bkg #if isBkgPlusZFit: # mu_x = mu_x + (par[partot-1] *z_x[ibin]) if isSpuriousFit: mu_x = mu_x + par[partot - 2] * signal_x[ibin] #L = L + mu_x - data*log(mu_x) L = L + ((mu_x - data) / data_error[ibin])**2 f[0] = L # initialize the TMinuit object arglist_p = 10 * [0] arglist = array.array('d') arglist.fromlist(arglist_p) ierflag = Long(0) maxiter = 1000000000 arglist_p = [1] gMinuit = TMinuit(partot) gMinuit.mnexcm('SET PRIntout', arglist, 0, ierflag) gMinuit.SetPrintLevel(1) gMinuit.SetErrorDef(1.0) gMinuit.SetFCN(fcn) arglist_p = [2] arglist = array.array('d') arglist.fromlist(arglist_p) gMinuit.mnexcm('SET STRategy', arglist, 1, ierflag) arglist_p = [maxiter, 0.0000001] arglist = array.array('d') arglist.fromlist(arglist_p) gMinuit.mnexcm('MIGrad', arglist, 2, ierflag) gMinuit.SetMaxIterations(maxiter) # initialize fitting the variables vstart = [100.0] * partot # start alpha_z with 1 vstart[partot - 1] = 1.0 vstart[partot - 2] = 0 step = [0.1] * partot upper = [100000] * partot lower = [0.1] * partot varname = [] if "ExpoPol" in bkgname: upper = [1000] * partot lower = [-1000] * partot if "ExpoBernstein" in bkgname: vstart[0] = -1 upper[0] = 0 lower[0] = -10 for i in range(parfunction): varname.append("p" + str(i)) varname.append("alpha_sig") varname.append("alpha_z") if doFloatZ: vstart[partot - 1] = 1.0 upper[partot - 1] = 2 lower[partot - 1] = 0 step[partot - 1] = 0.01 if isSpuriousFit: upper[partot - 2] = 10.0 lower[partot - 2] = -10.0 step[partot - 2] = 0.1 vstart[partot - 2] = 1 for i in range(partot): gMinuit.mnparm(i, varname[i], vstart[i], step[i], lower[i], upper[i], ierflag) if not isSpuriousFit: vstart[partot - 2] = 0 gMinuit.FixParameter(partot - 2) if not doFloatZ: lower[partot - 1] = 1 upper[partot - 1] = 1 gMinuit.FixParameter(partot - 1) if not isBkgPlusZFit: vstart[partot - 1] = 0.0 gMinuit.FixParameter(partot - 1) # fitting procedure migradstat = gMinuit.Command('MIGrad ' + str(maxiter) + ' ' + str(0.001)) improvestat = gMinuit.Command('IMProve ' + str(maxiter) + ' ' + str(0.01)) for i in range(partot): arglist_p.append(i + 1) arglist = array.array('d') arglist.fromlist(arglist_p) #gMinuit.mnmnos() # get fitting parameters fitval_p = [Double(0)] * partot fiterr_p = [Double(0)] * partot errup_p = [Double(0)] * partot errdown_p = [Double(0)] * partot eparab_p = [Double(0)] * partot gcc_p = [Double(0)] * partot fmin_p = [Double(0)] fedm_p = [Double(0)] errdef_p = [Double(0)] npari_p = Long(0) nparx_p = Long(0) istat_p = Long(0) fitval = array.array('d') fiterr = array.array('d') errup = array.array('d') errdown = array.array('d') eparab = array.array('d') gcc = array.array('d') for i in range(partot): gMinuit.GetParameter(i, fitval_p[i], fiterr_p[i]) fitval.append(fitval_p[i]) fiterr.append(fiterr_p[i]) errup.append(errup_p[i]) errdown.append(errdown_p[i]) eparab.append(eparab_p[i]) gcc.append(gcc_p[i]) gMinuit.mnstat(fmin_p[0], fedm_p[0], errdef_p[0], npari_p, nparx_p, istat_p) for p in range(bkgfunction.GetNumberFreeParameters()): bkgfunction.SetParameter(p, fitval[p]) print "fit uncert", fiterr_p[p] bkgfunction.SetChisquare(fmin_p[0]) return fitval[partot - 1], fitval[partot - 2]
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 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)
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 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) # meaning of parameters: # amin: value of fcn at minimum (=chi^2) # edm: estimated distance to mimimum # errdef: delta_fcn used to define 1 sigma errors # nvpar: number of variable parameters # nparx: total number of parameters # icstat: status of error matrix: # 3=accurate # 2=forced pos. def # 1= approximative # 0=not calculated myMinuit.mnprin(3,amin) # print-out by Minuit
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)
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
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
#−−>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 myMinuit.mnexcm("MIGRAD", arglist, 2, 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) #meaning of parameters: # amin: value of fcn at minimum (=chi^2) # edm: estimated distance to mimimum # errdef: delta_fcn used to define 1 sigma errors # nvpar: number of variable parameters # nparx: total number of parameters # icstat: status of error matrix: # 3 = accurate # 2 = forced pos.def # 1 = approximative # 0 = not calculated myMinuit.mnprin(3,amin) # print−out by Minuit #−−> get results from MINUIT finalPar=[] finalParErr=[]
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 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 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
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.)
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]
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))
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
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))