Beispiel #1
0
class Fit2L:
    def __init__(self, file_names, br_name="mass"):
        self.file_names = file_names
        self.br_name = br_name
        min_x = 8
        max_x = 12
        self.obs = RooRealVar("obs", "m4l", min_x, max_x)
        self.nbins = int((max_x - min_x) * 20)
        self.obs.setBin(self.nbins)
        # self.ws = ROOT.RooWorkspace("combined", "combined")
        self.obs.setRange("fit", 8.5, 11.5)
        self.dummy_hists = []
        self.chi2_cut = 10000

    def build_model(self):
        # mean = RooRealVar("mean", "mass of 1S", 9.46, 9.2, 9.7)
        # sigma = RooRealVar("sigma", "sigma of gaussian", 0.14, 0.09, 0.3)
        mean = RooRealVar("mean", "mass of 1S", 9.48352)
        sigma = RooRealVar("sigma", "sigma of gaussian", 1.38574e-01)
        gaussian = ROOT.RooGaussian("gauss", "gauss", self.obs, mean, sigma)

        ## try Crystal Ball
        # alpha = RooRealVar("alpha", "alpha of CB", 5.9, 0, 100)
        # cb_n = RooRealVar("cb_n", "n of CB",  55.4, 0, 100)
        # gaussian = ROOT.RooCBShape("gauss", "gauss", self.obs, mean, sigma, alpha, cb_n)

        n_sig = RooRealVar("n_sig", "number of signal", 5000, 0, 100000)
        esig = ROOT.RooExtendPdf("esig", "esig", gaussian, n_sig)

        m2_shift = RooRealVar("m2_shift", "m2 shift", 0.56296)
        m2 = ROOT.RooFormulaVar("m2", "mass of 2S", "@0+ @1", RooArgList(mean, m2_shift))
        s2 = ROOT.RooFormulaVar("s2", "sigma*(unit+m2_shift)/mean", "@0*(1+@1/9.46)", RooArgList(sigma, m2_shift))
        g2 = ROOT.RooGaussian("g2", "gauss", self.obs, m2, s2)
        n2 = RooRealVar("n2", "number of 2S", 800, 100, 100000)
        # n2 = ROOT.RooFormulaVar("n2", "number of 2S" ,"n_sig*0.26", RooArgList(n_sig))
        esig2 = ROOT.RooExtendPdf("esig2", "esig2", g2, n2)

        m3_shift = RooRealVar("m3_shift", "m3 shift", 0.8949)
        m3 = ROOT.RooFormulaVar("m3", "mass of 3S", "mean + m3_shift", RooArgList(mean, m3_shift))
        s3 = ROOT.RooFormulaVar("s3", "sigma*(unit+m2_shift)/mean", "@0*(1+@1/9.46)", RooArgList(sigma, m3_shift))
        g3 = ROOT.RooGaussian("g3", "gauss", self.obs, m3, s3)
        # n3 = RooRealVar("n3", "number of 3S" , 50, 0, 1000)
        n3 = ROOT.RooFormulaVar("n3", "number of 3S", "n2*0.45", RooArgList(n2))
        esig3 = ROOT.RooExtendPdf("esig3", "esig3", g3, n3)

        n_bkg = RooRealVar("n_bkg", "number of bkg", 1000, 0, 1e6)
        # p0 = RooRealVar("p0", "p0", 5.8677e-02, -1E6, 1E6)
        # p1 = RooRealVar("p1", "p1", -5.086E-02, -1E6, 1E6)
        # p2 = RooRealVar("p2", "p2", -1.96e-02, -1E6, 1E6)
        # p3 = RooRealVar("p3", "p3", -1.08E-02, -1E6, 1E6)
        # p4 = RooRealVar("p4", "p4", -1.55E-02, -1E6, 1E6)

        # best fitted value using 2015+2016
        p0 = RooRealVar("p0", "p0", 5.86772e-02)
        p1 = RooRealVar("p1", "p1", -5.08695e-02)
        p2 = RooRealVar("p2", "p2", -1.95595e-02)
        p3 = RooRealVar("p3", "p3", -1.08105e-02)
        p4 = RooRealVar("p4", "p4", -1.54855e-02)
        bkg = ROOT.RooChebychev("bkg", "bkg", self.obs, RooArgList(p0, p1, p2, p3, p4))
        # bkg = ROOT.RooPolynomial("bkg", "bkg", self.obs, RooArgList(p0, p1, p2))
        ebkg = ROOT.RooExtendPdf("ebkg", "ebkg", bkg, n_bkg)
        model = ROOT.RooAddPdf("model", "model", RooArgList(esig, esig2, esig3, ebkg))
        getattr(self.ws, "import")(model)

    def get_data(self):
        """
        create RooDataSet from file_names
        """
        tree = ROOT.TChain("upsilon", "upsilon")
        if type(self.file_names) is list:
            for file_name in self.file_names:
                tree.Add(file_name)
        else:
            tree.Add(self.file_names)

        nentries = tree.GetEntries()
        print "total: ", nentries
        obs_set = RooArgSet(self.obs)
        data = ROOT.RooDataSet("data", "data", obs_set)
        for ientry in xrange(nentries):
            tree.GetEntry(ientry)
            for i, m4l in enumerate(getattr(tree, self.br_name)):
                # m4l = m4l/1000
                if m4l > self.obs.getMax() or m4l < self.obs.getMin():
                    continue
                if tree.chi2[i] > self.chi2_cut:
                    continue

                self.obs.setVal(m4l)
                data.add(obs_set)

        getattr(self.ws, "import")(data)

    def fit(self):
        if not hasattr(self, "ws"):
            self.ws = ROOT.RooWorkspace("combined", "combined")

        self.build_model()
        self.get_data()
        data = self.ws.obj("data")
        model = self.ws.obj("model")
        print "total data:", data.sumEntries()
        nll = model.createNLL(data)

        do_bkg_only = False
        n_sig = self.ws.var("n_sig")
        n2 = self.ws.var("n2")
        n3 = self.ws.var("n3")
        if do_bkg_only:
            n_sig.setVal(0.0)
            n2.setVal(0.0)
            n3.setVal(0.0)
            n_sig.setConstant()
            n2.setConstant()
            n3.setConstant()
            model.fitTo(data)
            nll_condition = nll.getVal()
            model.plotOn(frame, ROOT.RooFit.LineColor(4))
        else:
            nll_condition = 0.0

        # n_sig.setVal(50000)
        n_sig.setConstant(False)
        # n2.setVal(50000)
        # n3.setVal(50000)
        # n2.setConstant(False)
        # n3.setConstant(False)
        model.fitTo(data)
        nll_uncondition = nll.getVal()
        self.ws.saveSnapshot("splusb", self.ws.allVars())

        try:
            sig = math.sqrt(2 * (nll_condition - nll_uncondition))
            print "significance: ", sig
        except:
            print nll_condition, nll_uncondition

        self.ws.writeToFile("combined_" + str(self.chi2_cut) + ".root")

    def plot(self):
        ## plot
        if not hasattr(self, "ws"):
            f1 = ROOT.TFile.Open("combined_" + str(self.chi2_cut) + ".root")
            self.ws = f1.Get("combined")

        self.ws.loadSnapshot("splusb")

        ## get number of signal and background in signal region
        obs = self.ws.var("obs")
        obs.setRange("signal", 9.2, 9.7)
        nsig = self.ws.obj("n_sig").getVal()
        nbkg = self.ws.obj("n_bkg").getVal()
        nall = self.ws.obj("model").expectedEvents(RooArgSet(obs))
        print "full range:", nsig, nbkg, nall
        frac_sig_full = self.ws.obj("gauss").createIntegral(RooArgSet(obs)).getVal()
        frac_sig = self.ws.obj("gauss").createIntegral(RooArgSet(obs), ROOT.RooFit.Range("signal")).getVal()
        frac_bkg_full = self.ws.obj("bkg").createIntegral(RooArgSet(obs)).getVal()
        frac_bkg = self.ws.obj("bkg").createIntegral(RooArgSet(obs), ROOT.RooFit.Range("signal")).getVal()
        frac_all = self.ws.obj("model").createIntegral(RooArgSet(obs), ROOT.RooFit.Range("signal")).getVal()
        print "fraction: ", frac_sig, frac_sig_full, frac_bkg, frac_bkg_full, frac_all
        nsig *= frac_sig / frac_sig_full
        nbkg *= frac_bkg / frac_bkg_full
        print "signal region:", nsig, nbkg

        # prepare for the plot
        frame = obs.frame(self.nbins)
        self.ws.obj("data").plotOn(frame)
        self.ws.obj("model").plotOn(frame, ROOT.RooFit.LineColor(2))
        self.ws.obj("bkg").plotOn(
            frame,
            ROOT.RooFit.LineColor(4),
            ROOT.RooFit.Normalization(self.ws.obj("n_bkg").getVal(), ROOT.RooAbsReal.NumEvent),
        )

        self.ws.obj("gauss").plotOn(
            frame,
            ROOT.RooFit.LineColor(3),
            ROOT.RooFit.Normalization(self.ws.obj("n_sig").getVal(), ROOT.RooAbsReal.NumEvent),
        )
        self.ws.obj("g2").plotOn(
            frame,
            ROOT.RooFit.LineColor(6),
            ROOT.RooFit.Normalization(self.ws.obj("n2").getVal(), ROOT.RooAbsReal.NumEvent),
        )
        self.ws.obj("g3").plotOn(
            frame,
            ROOT.RooFit.LineColor(8),
            ROOT.RooFit.Normalization(self.ws.obj("n3").getVal(), ROOT.RooAbsReal.NumEvent),
        )
        canvas = ROOT.TCanvas("canvas", "canvas", 600, 600)
        frame.Draw()
        max_y = frame.GetMaximum()
        # frame.SetMaximum(max_y*1.7)
        ## add legend
        y_start = 0.45
        x_start = 0.60
        ROOT.myText(x_start, y_start, 1, "In [9.2, 9.7] GeV")
        ROOT.myText(x_start, y_start - 0.05, 1, "with #chi^{2} < " + str(self.chi2_cut))
        ROOT.myText(x_start, y_start - 0.05 * 2, 1, "N(1S) = {:.1f}".format(nsig))
        ROOT.myText(x_start, y_start - 0.05 * 3, 1, "N(bkg) = {:.1f}".format(nbkg))
        ROOT.myText(x_start, y_start - 0.05 * 4, 1, "S/sqrt(B):{:.1f}".format(nsig / math.sqrt(nbkg)))
        mean = self.ws.var("mean")
        # ROOT.myText(0.2, y_start, 1, "m = {:.3f} #pm {:.3f} GeV".format(mean.getVal(), mean.getError()))
        ROOT.myText(0.2, y_start, 1, "m = {:.3f} GeV".format(mean.getVal()))
        ROOT.myText(0.2, y_start - 0.05, 1, "#sigma = {:.2f} GeV".format(self.ws.var("sigma").getVal()))

        canvas.SaveAs("fit_" + str(self.chi2_cut) + ".pdf")
        self.ws.obj("s2").Print()
        self.ws.obj("s3").Print()

    def add_dummy_entry(self, legend, color, label):
        h1 = ROOT.TH1F(label, label, 10, 0, 10)
        h1.SetLineColor(color)
        legend.AddEntry(h1, label, "L")
        self.dummy_hists.append(h1)
Beispiel #2
0
class Fit2L:
    def __init__(self, file_name, br_name="mass"):
        self.file_name = file_name
        self.br_name = br_name
        min_x = 8
        max_x = 12
        self.obs = RooRealVar("obs", "m4l", min_x, max_x)
        self.nbins = int((max_x - min_x) * 20)
        self.obs.setBin(self.nbins)
        #self.ws = ROOT.RooWorkspace("combined", "combined")
        self.dummy_hists = []
        self.chi2_cut = 10000

        # dionia selection or onia selection
        self.dionia_selection = False
        # with 3mu4 trigger or not
        self.with_3mu4 = True
        # only unprescaled runs
        self.only_unprescaled = True

        # different onia_pt cuts, 0:noCut, 1:<5, 2:5-10, 3:10,20, 4:>20
        self.onia_pt_cut = 0
        self.onia_pt_cuts = [0, 5, 10, 20]
        #self.onia_pt_cuts = [0, 10, 20]

        # different muon pT cuts
        # suggested by Terry to look at onia mass that have one muon with pT (3, 4) GeV.
        self.use_low_pt_muon = False

        # it is found that these low pT muons contributes a lot the background 40%
        # while only gain 15% signal
        self.no_low_pt = False

        # if build new model
        self.new_model = False
        self.model_name = "model"

        # workspace components
        self.data = None
        self.model = None

    def pass_onia_pt_cut(self, pT):
        pT_cut = int(self.onia_pt_cut)
        if pT_cut >= len(self.onia_pt_cuts):
            return pT >= self.onia_pt_cuts[ len(self.onia_pt_cuts)-1 ]
        elif pT_cut > 0:
            return pT >= self.onia_pt_cuts[pT_cut-1] and pT < self.onia_pt_cuts[pT_cut]
        else:
            return True

    def print_onia_pt_cut(self):
        pT_cut = int(self.onia_pt_cut)
        if pT_cut >= len(self.onia_pt_cuts):
            return "p_{T}^{onia} in ["+str(self.onia_pt_cuts[ len(self.onia_pt_cuts)-1 ])+", #infty) GeV"
        elif pT_cut > 0:
            return "p_{T}^{onia} in ["+str(self.onia_pt_cuts[pT_cut-1])+", "+str(self.onia_pt_cuts[pT_cut])+") GeV"
        else:
            return "p_{T}^{onia} in [-#infty, #infty] GeV"

    def print_dionia_selection(self):
        if self.dionia_selection:
            return "di-onia selection"
        else:
            return "onia selection"

    def print_trigger(self):
        if self.with_3mu4:
            return "with trigger"
        else:
            return "no trigger"

    def get_ws_name(self):
        res = "ws_chi2Cut"+str(self.chi2_cut)+"_DiOnia"+str(self.dionia_selection)+"_pTCut"+str(self.onia_pt_cut)+"_withTrigger"+str(self.with_3mu4)
        if self.use_low_pt_muon:
            res += "_LowPtMuon"
        if self.no_low_pt:
            res += "_noLowPtMuon"

        res += ".root"
        return res

    def build_model(self):
        #mean = RooRealVar("mean", "mass of 1S", 9.46, 9.2, 9.7)
        #sigma = RooRealVar("sigma", "sigma of gaussian", 0.14, 0.09, 0.3)
        mean = RooRealVar("mean", "mass of 1S", 9.46)
        sigma = RooRealVar("sigma", "sigma of gaussian", 0.18)
        gaussian = ROOT.RooGaussian("gauss", "gauss", self.obs, mean, sigma)

        ## try Crystal Ball
        #alpha = RooRealVar("alpha", "alpha of CB", 5.9, 0, 100)
        #cb_n = RooRealVar("cb_n", "n of CB",  55.4, 0, 100)
        #gaussian = ROOT.RooCBShape("gauss", "gauss", self.obs, mean, sigma, alpha, cb_n)

        n_sig = RooRealVar("n_sig", "number of signal" , 5000, 0, 1E6)
        esig = ROOT.RooExtendPdf("esig", "esig", gaussian, n_sig)

        m2_shift = RooRealVar("m2_shift", "m2 shift", 0.56296)
        m2 = ROOT.RooFormulaVar("m2", "mass of 2S", "@0+ @1", RooArgList(mean, m2_shift))
        s2 = ROOT.RooFormulaVar("s2", "sigma*(unit+m2_shift)/mean", "@0*(1+@1/9.46)", RooArgList(sigma, m2_shift))
        g2 = ROOT.RooGaussian("g2", "gauss", self.obs, m2, s2)
        n2 = RooRealVar("n2", "number of 2S" , 800, 4, 100000)
        #n2 = ROOT.RooFormulaVar("n2", "number of 2S" ,"n_sig*0.26", RooArgList(n_sig))
        esig2 = ROOT.RooExtendPdf("esig2", "esig2", g2, n2)

        m3_shift = RooRealVar("m3_shift", "m3 shift", 0.8949)
        m3 = ROOT.RooFormulaVar("m3", "mass of 3S", "mean + m3_shift", RooArgList(mean, m3_shift))
        s3 = ROOT.RooFormulaVar("s3", "sigma*(unit+m2_shift)/mean", "@0*(1+@1/9.46)", RooArgList(sigma, m3_shift))
        g3 = ROOT.RooGaussian("g3", "gauss", self.obs, m3, s3)
        n3 = RooRealVar("n3", "number of 3S" , 1, 0, 2000)
        #n3 = ROOT.RooFormulaVar("n3", "number of 3S", "n2*0.45", RooArgList(n2))
        esig3 = ROOT.RooExtendPdf("esig3", "esig3", g3, n3)

        n_bkg = RooRealVar("n_bkg", "number of bkg" , 1000, 0, 1E7)
        p0 = RooRealVar("p0", "p0", -1E6, 1E6)
        p1 = RooRealVar("p1", "p1", -1E6, 1E6)
        p2 = RooRealVar("p2", "p2", -1E6, 1E6)
        p3 = RooRealVar("p3", "p3", -1E6, 1E6)
        p4 = RooRealVar("p4", "p4", -1E6, 1E6)
        #p0 = RooRealVar("p0", "p0", 9.93810e-02)
        #p1 = RooRealVar("p1", "p1", -3.51406e-02)
        #p2 = RooRealVar("p2", "p2", 5.92968e-03)
        #p3 = RooRealVar("p3", "p3", -6.53710e-03)
        #p4 = RooRealVar("p4", "p4", 9.76852e-03)

        bkg = ROOT.RooChebychev("bkg", "bkg", self.obs, RooArgList(p0, p1, p2, p3, p4))
        #bkg = ROOT.RooChebychev("bkg", "bkg", self.obs, RooArgList(p0, p1, p2))
        #bkg = ROOT.RooPolynomial("bkg", "bkg", self.obs, RooArgList(p0, p1, p2))
        ebkg = ROOT.RooExtendPdf("ebkg", "ebkg", bkg, n_bkg)
        model = ROOT.RooAddPdf(self.model_name, self.model_name, RooArgList(esig, esig2, esig3, ebkg))
        getattr(self.ws, "import")(model, ROOT.RooFit.RecycleConflictNodes())
        #getattr(self.ws, "import")(model, ROOT.RooFit.RenameConflictNodes("new"))

    def is_unprescaled_runs(self, tree):
        run_ = tree.run
        res = True
        if run_ >= 297730 and run_ < 307619:
            res = False
        return res

    def get_data(self):
        fin = ROOT.TFile.Open(self.file_name)
        tree = fin.Get("upsilon")
        nentries = tree.GetEntries()
        print "total: ", nentries
        print "onia pT cut: ", self.onia_pt_cut
        obs_set = RooArgSet(self.obs)
        data = ROOT.RooDataSet("data", "data", obs_set)
        for ientry in xrange(nentries):
            tree.GetEntry(ientry)
            if self.only_unprescaled and not self.is_unprescaled_runs(tree):
                continue

            for i,m4l in enumerate(getattr(tree, self.br_name)):
                #m4l = m4l/1000
                if m4l > self.obs.getMax() or m4l < self.obs.getMin():
                    continue
                # apply chi2 
                if tree.chi2[i] > self.chi2_cut:
                    continue
                # apply trigger 
                if self.with_3mu4 and not tree.trig_3mu4:
                    continue
                # apply dionia selection
                if self.dionia_selection and tree.pass_diOnia != 1:
                    continue

                has_one_lowPt = (tree.mu_pt_1[i] > 3E3 and tree.mu_pt_1[i] < 4E3) or (tree.mu_pt_2[i] > 3E3 and tree.mu_pt_2[i] < 4E3)
                if self.use_low_pt_muon and not has_one_lowPt:
                    continue

                if self.no_low_pt and has_one_lowPt:
                    continue

                # apply onia pT cut
                pT = tree.pt[i]
                if not self.pass_onia_pt_cut(pT):
                    continue

                self.obs.setVal(m4l)
                data.add(obs_set)

        getattr(self.ws, "import")(data)
        print "selected events: ", data.sumEntries()
        fin.Close()

    def fit(self):
        print "my configuration:",self.get_ws_name()
        if not hasattr(self, "ws"):
            # try to look for the workspace and reused the data
            f1 = ROOT.TFile.Open(self.get_ws_name())
            self.ws = ROOT.RooWorkspace("combined", "combined")
            if f1:
                self.f1 = f1
                ws = f1.Get("combined")
                if ws:
                    getattr(self.ws, "import")(ws.obj('data'))
                if self.new_model:
                    print "building new models"
                    self.build_model()
                else:
                    getattr(self.ws, "import")(ws.obj(self.model_name))
            else:
                self.build_model()
                self.get_data()

        data = self.ws.obj("data")
        self.obs.setRange("fit_range", 8.2, 11.7)
        model = self.ws.obj(self.model_name)
        model.Print()
        print "total data:", data.sumEntries()
        nll = model.createNLL(data)

        #self.change_model()

        model.fitTo(data, ROOT.RooFit.Range("fit_range"))
        #model.fitTo(data)

        nll_uncondition = nll.getVal()
        self.ws.saveSnapshot("splusb", self.ws.allVars())

        if hasattr(self, "f1"):
            self.f1.Close()
        else:
            self.ws.writeToFile(self.get_ws_name())

    def plot(self, title):
        ## plot
        if not hasattr(self, "ws"):
            f1 = ROOT.TFile.Open(self.get_ws_name())
            self.ws = f1.Get("combined")

        self.ws.loadSnapshot("splusb")

        ## get number of signal and background in signal region
        obs = self.ws.var('obs')
        obs.setRange("signal", 9.2, 9.7)
        nsig = self.ws.obj("n_sig").getVal()
        nbkg = self.ws.obj("n_bkg").getVal()
        nall = self.ws.obj('model').expectedEvents(RooArgSet(obs))
        print "full range:", nsig, nbkg, nall
        frac_sig_full = self.ws.obj("gauss").createIntegral(
            RooArgSet(obs)
        ).getVal()
        frac_sig = self.ws.obj("gauss").createIntegral(
            RooArgSet(obs),
            ROOT.RooFit.Range("signal")
        ).getVal()
        frac_bkg_full = self.ws.obj('bkg').createIntegral(
            RooArgSet(obs)
        ).getVal()
        frac_bkg = self.ws.obj("bkg").createIntegral(
            RooArgSet(obs),
            ROOT.RooFit.Range("signal")
        ).getVal()
        frac_all = self.ws.obj("model").createIntegral(
            RooArgSet(obs),
            ROOT.RooFit.Range("signal")
        ).getVal()
        print "fraction: ", frac_sig, frac_sig_full, frac_bkg, frac_bkg_full, frac_all
        nsig *= frac_sig/frac_sig_full
        nbkg *= frac_bkg/frac_bkg_full
        print "signal region:", nsig, nbkg

        # prepare for the plot
        frame = obs.frame(self.nbins)
        self.ws.obj("data").plotOn(frame)
        self.ws.obj("model").plotOn(frame, ROOT.RooFit.LineColor(2))
        self.ws.obj("bkg").plotOn(
            frame, ROOT.RooFit.LineColor(4),
            ROOT.RooFit.Normalization(self.ws.obj("n_bkg").getVal(), ROOT.RooAbsReal.NumEvent)
        )

        self.ws.obj("gauss").plotOn(
            frame, ROOT.RooFit.LineColor(3),
            ROOT.RooFit.Normalization(self.ws.obj("n_sig").getVal(), ROOT.RooAbsReal.NumEvent)
        )
        self.ws.obj("g2").plotOn(
            frame, ROOT.RooFit.LineColor(6),
            ROOT.RooFit.Normalization(self.ws.obj("n2").getVal(), ROOT.RooAbsReal.NumEvent)
        )
        self.ws.obj("g3").plotOn(
            frame, ROOT.RooFit.LineColor(8),
            ROOT.RooFit.Normalization(self.ws.obj("n3").getVal(), ROOT.RooAbsReal.NumEvent)
        )
        canvas = ROOT.TCanvas("canvas", "canvas", 600, 600)
        canvas.SetTopMargin(0.07)
        canvas.SetBottomMargin(0.14)
        frame.Draw()
        max_y = frame.GetMaximum()
        frame.GetXaxis().SetTitle(title)
        #frame.SetMinimum(max_y*0.2)
        #frame.SetMaximum(max_y*1.7)
        ## add legend
        y_start = 0.43
        x_start = 0.60
        # title and mass and sigma
        ROOT.myText(0.2, 0.955, 1, "{},{},{}".format(self.print_onia_pt_cut(), self.print_dionia_selection(), self.print_trigger()))
        mass_offset = 0.18
        if self.use_low_pt_muon:
            mass_offset = 0.7
        ROOT.myText(mass_offset, 0.87, 1, "m = {:.2f} GeV".format(self.ws.var("mean").getVal()))
        ROOT.myText(mass_offset, 0.87-0.05, 1, "#sigma = {:.2f} GeV".format(self.ws.var("sigma").getVal()))
        # left side
        ROOT.myText(0.2, y_start, 1, "Total: {:.0f}".format(self.ws.obj("data").sumEntries()))
        ROOT.myText(0.2, y_start-0.05, 1, "N(1S) = {:.1f}".format(self.ws.obj("n_sig").getVal()) )
        ROOT.myText(0.2, y_start-0.05*2, 1, "N(2S) = {:.1f}".format(self.ws.obj("n2").getVal()) )
        ROOT.myText(0.2, y_start-0.05*3, 1, "N(3S) = {:.1f}".format(self.ws.obj("n3").getVal()) )
        ROOT.myText(0.2, y_start-0.05*4, 1, "N(bkg) = {:.1f}".format(self.ws.obj("n_bkg").getVal()) )
        # right side
        ROOT.myText(x_start, y_start, 1, "In [9.2, 9.7] GeV")
        ROOT.myText(x_start, y_start-0.05, 1, "with #chi^{2} < "+str(self.chi2_cut))
        ROOT.myText(x_start, y_start-0.05*2, 1, "N(1S) = {:.1f}".format(nsig))
        ROOT.myText(x_start, y_start-0.05*3, 1, "N(bkg) = {:.1f}".format(nbkg))
        ROOT.myText(x_start, y_start-0.05*4, 1, "S/sqrt(B):{:.1f}".format(nsig/math.sqrt(nbkg)))

        canvas.SaveAs(self.get_ws_name().replace("root", "pdf").replace("ws_", "fit_"))
        canvas.SaveAs(self.get_ws_name().replace("root", "eps").replace("ws_", "fit_"))
        self.ws.obj("s2").Print()
        self.ws.obj("s3").Print()

    def change_model(self):
        self.ws.loadSnapshot("splusb")
        # free signal
        mean_ = self.ws.var("mean")
        mean_.setMin(9.2)
        mean_.setMax(9.7)
        mean_.setConstant(False)
        sigma_ = self.ws.var("sigma")
        sigma_.setMin(0.09)
        sigma_.setMax(0.3)
        sigma_.setConstant(False)
        # fix background
        self.ws.var("p0").setConstant(True)
        self.ws.var("p1").setConstant(True)
        self.ws.var("p2").setConstant(True)
        if self.ws.var('p3'):
            self.ws.var('p3').setConstant(True)
        if self.ws.var('p4'):
            self.ws.var('p4').setConstant(True)