class Fits: def __init__(self): self.p_n = [ 0, ] * 100 self.e_n = [ 0, ] * 100 self.stored_parameters = [ 0, ] * 100 self.num_bins = 0 self.xmins = [] self.xmaxes = [] self.data = [] self.errors = [] self.data_fits = [] self.model_scale_values = [] self.final = False self.exclude_regions = ((0, 0), ) self.col1 = 1 self.col2 = TColor.GetColor(27, 158, 119) self.col3 = TColor.GetColor(217, 95, 2) self.col4 = TColor.GetColor(117, 112, 179) def run_mass_fit(self, peak_scale_initial, mass=0): self.gMinuit = TMinuit(30) self.gMinuit.SetPrintLevel(-1) self.gMinuit.SetFCN(self.Fitfcn_max_likelihood) arglist = array("d", [ 0, ] * 10) ierflg = ROOT.Long(0) arglist[0] = ROOT.Double(1) # peak_scale_initial = ROOT.Double(peak_scale_initial) tmp = array("d", [ 0, ]) self.gMinuit.mnexcm("SET NOWarnings", tmp, 0, ierflg) self.gMinuit.mnexcm("SET ERR", arglist, 1, ierflg) self.gMinuit.mnparm(0, "p1", 5e-6, 1e-7, 0, 0, ierflg) self.gMinuit.mnparm(1, "p2", 10, 10, 0, 0, ierflg) self.gMinuit.mnparm(2, "p3", -5.3, 1, 0, 0, ierflg) self.gMinuit.mnparm(3, "p4", -4e-2, 1e-2, 0, 0, ierflg) self.gMinuit.mnparm(4, "p5", peak_scale_initial, peak_scale_initial / 50, 0, 0, ierflg) self.background_fit_only = [ 0, ] * len(self.data) arglist[0] = ROOT.Double(0) arglist[1] = ROOT.Double(0) #self.exclude_regions = ((2.2, 3.3),) self.gMinuit.FixParameter(2) self.gMinuit.FixParameter(3) self.gMinuit.FixParameter(4) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) self.gMinuit.Release(2) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) self.gMinuit.Release(3) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) # Find an actual best fit #self.exclude_regions = ((0, 2), (3.3, 100),) self.gMinuit.FixParameter(0) self.gMinuit.FixParameter(1) self.gMinuit.FixParameter(2) self.gMinuit.FixParameter(3) self.gMinuit.Release(4) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) self.exclude_regions = () self.gMinuit.Release(0) self.gMinuit.Release(1) self.gMinuit.Release(2) self.gMinuit.Release(3) self.gMinuit.mnexcm("simplex", arglist, 2, ierflg) self.gMinuit.mnexcm("MIGRAD", arglist, 2, ierflg) best_fit_value = ROOT.Double(0) self.gMinuit.mnstat(best_fit_value, ROOT.Double(0), ROOT.Double(0), ROOT.Long(0), ROOT.Long(0), ROOT.Long(0)) #print("Best fit value", best_fit_value) # And prepare for iterating over fit values for N injected events self.gMinuit.Release(0) self.gMinuit.Release(1) self.gMinuit.Release(2) self.gMinuit.Release(3) self.exclude_regions = () #self.data_fits = no_peak_data_fits x_values = [] y_values = [] fitted_N = ROOT.Double(0) self.gMinuit.GetParameter(4, fitted_N, ROOT.Double(0)) best_fit_likelihood = self.calc_likelihood(fitted_N) step = 5 if int(mass) >= 4000: step = 1 if int(mass) >= 5000: step = 0.2 if int(mass) >= 6000: step = 0.1 if int(mass) >= 6500: step = 0.05 N = 0 while N < 5000: start_sum = sum([math.exp(-a) for a in y_values]) fit_likelihood = self.calc_likelihood(N) x_values.append(N) y_values.append(fit_likelihood - best_fit_likelihood) probabilities = [math.exp(-a) for a in y_values] end_sum = sum(probabilities) max_prob = max(probabilities) normalised_probabilities = [a / max_prob for a in probabilities] if N / step > 50 and all( [v > 0.99 for v in normalised_probabilities]): print( "Probability=1 everywhere, probably something wrong with fit" ) print(normalised_probabilities) return None, None # if new value changes total by less than 0.1%, end loop if N > 0 and (end_sum - start_sum) / start_sum < 0.0001: print("Iterated up to {0}".format(N)) break N += step self.iflag = int(ierflg) return x_values, y_values def Fitfcn_max_likelihood(self, npar, gin, fcnVal, par, iflag): likelihood = 0 mf = ROOT.MyMassSpectrum() mf.SetParameters(par) ig = GaussIntegrator() ig.SetFunction(mf) ig.SetRelTolerance(0.00001) for i in range(0, self.num_bins): for lower, higher in self.exclude_regions: if lower < self.xmins[i] < higher: continue model_val = ig.Integral(self.xmins[i], self.xmaxes[i]) / ( self.xmaxes[i] - self.xmins[i]) self.background_fit_only[i] = model_val model_val += self.model_scale_values[i] * par[4] self.data_fits[i] = model_val likelihood += model_val - self.data[i] if self.data[i] > 0 and model_val > 0: likelihood += self.data[i] * (math.log(self.data[i]) - math.log(model_val)) fcnVal[0] = likelihood def calc_likelihood(self, peak_scale): like = 0 for i in range(0, self.num_bins): if self.data_fits[i] <= 0: continue p = peak_scale * self.model_scale_values[i] tmp = ROOT.TMath.PoissonI(self.data[i], self.background_fit_only[i] + p) #if peak_scale == 40000: # print(i, "\txmin", self.xmins[i], "\tdata", self.data[i], "\tdata_fit", self.data_fits[i], "\tp", p, "\tdata_fit+p", self.data_fits[i]+p) if tmp == 0: print("tmp == 0 here") logtmp = math.log(sys.float_info.min) else: logtmp = math.log(tmp) like += logtmp return -like
class Minuit(object): """A wrapper class to initialize a minuit object with a numpy array. Positional args: myFCN : A python callable params : An array (or other python sequence) of parameters Keyword args: limits [None] : a nested sequence of (lower_limit,upper_limit) for each parameter. steps [[.1]*npars] : Estimated errors for the parameters, used as an initial step size. tolerance [.001] : Tolerance to test for convergence. Minuit considers convergence to be when the estimated distance to the minimum (edm) is <= .001*up*tolerance, or 5e-7 by default. up [.5] : Change in the objective function that determines 1-sigma error. .5 if the objective function is -log(Likelihood), 1 for chi-squared. max_calls [10000] : Maximum number of calls to the objective function. param_names ['p0','p1',...] : a list of names for the parameters args [()] : a tuple of extra arguments to pass to myFCN and gradient. gradient [None] : a function that takes a list of parameters and returns a list of first derivatives of myFCN. Assumed to take the same args as myFCN. force_gradient [0] : Set to 1 to force Minuit to use the user-provided gradient function. strategy[1] : Strategy for minuit to use, from 0 (fast) to 2 (safe) fixed [False, False, ...] : If passed, an array of all the parameters to fix """ def __init__(self, myFCN, params, **kwargs): from ROOT import TMinuit, Long, Double self.limits = np.zeros((len(params), 2)) self.steps = .04 * np.ones( len(params)) # about 10 percent in log10 space self.tolerance = .001 self.maxcalls = 10000 self.printMode = 0 self.up = 0.5 self.param_names = ['p%i' % i for i in xrange(len(params))] self.erflag = Long() self.npars = len(params) self.args = () self.gradient = None self.force_gradient = 0 self.strategy = 1 self.fixed = np.zeros_like(params) self.__dict__.update(kwargs) self.params = np.asarray(params, dtype='float') self.fixed = np.asarray(self.fixed, dtype=bool) self.fcn = FCN(myFCN, self.params, args=self.args, gradient=self.gradient) self.fval = self.fcn.fval self.minuit = TMinuit(self.npars) self.minuit.SetPrintLevel(self.printMode) self.minuit.SetFCN(self.fcn) if self.gradient: self.minuit.mncomd('SET GRA %i' % (self.force_gradient), self.erflag) self.minuit.mncomd('SET STR %i' % self.strategy, Long()) for i in xrange(self.npars): if self.limits[i][0] is None: self.limits[i][0] = 0.0 if self.limits[i][1] is None: self.limits[i][1] = 0.0 self.minuit.DefineParameter(i, self.param_names[i], self.params[i], self.steps[i], self.limits[i][0], self.limits[i][1]) self.minuit.SetErrorDef(self.up) for index in np.where(self.fixed)[0]: self.minuit.FixParameter(int(index)) def minimize(self, method='MIGRAD'): from ROOT import TMinuit, Long, Double self.minuit.mncomd( '%s %i %f' % (method, self.maxcalls, self.tolerance), self.erflag) for i in xrange(self.npars): val, err = Double(), Double() self.minuit.GetParameter(i, val, err) self.params[i] = val self.fval = self.fcn.fcn(self.params) return (self.params, self.fval) def errors(self, method='HESSE'): """method ['HESSE'] : how to calculate the errors; Currently, only 'HESSE' works.""" if not np.any(self.fixed): mat = np.zeros(self.npars**2) if method == 'HESSE': self.minuit.mnhess() else: raise Exception("Method %s not recognized." % method) self.minuit.mnemat(mat, self.npars) return mat.reshape((self.npars, self.npars)) else: # Kind of ugly, but for fixed parameters, you need to expand out the covariance matrix. nf = int(np.sum(~self.fixed)) mat = np.zeros(nf**2) if method == 'HESSE': self.minuit.mnhess() else: raise Exception("Method %s not recognized." % method) self.minuit.mnemat(mat, nf) # Expand out the covariance matrix. cov = np.zeros((self.npars, self.npars)) cov[np.outer(~self.fixed, ~self.fixed)] = np.ravel(mat) return cov def uncorrelated_minos_error(self): """ Kind of a kludge, but a compromise between the speed of HESSE errors and the accuracy of MINOS errors. It accounts for non-linearities in the likelihood by varying each function until the value has fallen by the desired amount using hte MINOS code. But does not account for correlations between the parameters by fixing all other parameters during the minimzation. Again kludgy, but what is returned is an effective covariance matrix. The diagonal errors are calcualted by averaging the upper and lower errors and the off diagonal terms are set to 0. """ cov = np.zeros((self.npars, self.npars)) # fix all parameters for i in range(self.npars): self.minuit.FixParameter(int(i)) # loop over free paramters getting error for i in np.where(self.fixed == False)[0]: self.minuit.Release(int(i)) # compute error self.minuit.mnmnos() low, hi, parab, gcc = ROOT.Double(), ROOT.Double(), ROOT.Double( ), ROOT.Double() # get error self.minuit.mnerrs(int(i), low, hi, parab, gcc) cov[i, i] = ((abs(low) + abs(hi)) / 2.0)**2 self.minuit.FixParameter(int(i)) for i, fixed in enumerate(self.fixed): if fixed: self.minuit.FixParameter(int(i)) else: self.minuit.Release(int(i)) return cov
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