diff --git a/CombinePdfs/bin/AZhMorphing-NoModel.cpp b/CombinePdfs/bin/AZhMorphing-NoModel.cpp new file mode 100644 index 00000000000..bec5f98ea19 --- /dev/null +++ b/CombinePdfs/bin/AZhMorphing-NoModel.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include +#include "boost/filesystem.hpp" +#include "CombineHarvester/CombineTools/interface/CombineHarvester.h" +#include "CombineHarvester/CombineTools/interface/Utilities.h" +#include "CombineHarvester/CombineTools/interface/HttSystematics.h" +#include "CombineHarvester/CombineTools/interface/BinByBin.h" +#include "CombineHarvester/CombinePdfs/interface/MorphFunctions.h" + +#include "RooWorkspace.h" +#include "RooRealVar.h" +#include "TH2.h" +#include "RooDataHist.h" +#include "RooHistFunc.h" +#include "RooFormulaVar.h" +#include "RooProduct.h" + +using namespace std; + +int main() { + ch::CombineHarvester cb; + + // cb.SetVerbosity(1); + + typedef vector> Categories; + typedef vector VString; + + // RooFit will be quite noisy if we don't set this + RooMsgService::instance().setGlobalKillBelow(RooFit::WARNING); + + //RooRealVar mA("mA", "mA", 260., 350.); + RooRealVar mA("mA", "mA", 300.); + + string auxiliaries = string(getenv("CMSSW_BASE")) + "/src/auxiliaries/"; + string aux_shapes = auxiliaries +"shapes/"; + string aux_pruning = auxiliaries +"pruning/"; + + VString chns = + {"et", "mt","tt", "em"}; + + string input_folders = "ULB"; + + map bkg_procs; + bkg_procs["et"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + bkg_procs["mt"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + bkg_procs["em"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + bkg_procs["tt"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + + map sm_procs; + sm_procs["et"] = {"ZH_ww125","ZH_tt125"}; + + + VString sig_procs={"AZh"}; + map signal_types = { + {"AZh", {"ggA_AZhLLtautau"}} + }; + + map cats; + cats["et_8TeV"] = { + {0, "eeet_zh"}, {1, "mmet_zh"}}; + cats["mt_8TeV"] = { + {0, "eemt_zh"}, {1, "mmmt_zh"}}; + cats["em_8TeV"] = { + {0, "eeem_zh"}, {1, "mmme_zh"}}; + cats["tt_8TeV"] = { + {0, "eett_zh"}, {1, "mmtt_zh"}}; + + vector masses = ch::MassesFromRange("220-350:10"); + + cout << ">> Creating processes and observations...\n"; + for (string era : {"8TeV"}) { + for (auto chn : chns) { + cb.AddObservations( + {"*"}, {"htt"}, {era}, {chn}, cats[chn+"_"+era]); + cb.AddProcesses( + {"*"}, {"htt"}, {era}, {chn}, bkg_procs[chn], cats[chn+"_"+era], false); + // cb.AddProcesses( + // {"*"},{"htt"}, {era}, {chn}, sm_procs[chn], cats[chn+"_"+era],false); + cb.AddProcesses( + masses, {"htt"}, {era}, {chn}, signal_types["AZh"], cats[chn+"_"+era], true); + } + } + + + cout << ">> Adding systematic uncertainties...\n"; + ch::AddSystematics_AZh(cb); + + cout << ">> Extracting histograms from input root files...\n"; + for (string era : {"8TeV"}) { + for (string chn : chns) { + string file = aux_shapes + input_folders + "/htt_AZh" + + ".inputs-AZh-" + era + ".root"; + cb.cp().channel({chn}).era({era}).backgrounds().ExtractShapes( + file, "$BIN/$PROCESS", "$BIN/$PROCESS_$SYSTEMATIC"); + cb.cp().channel({chn}).era({era}).process(signal_types["AZh"]).ExtractShapes( + file, "$BIN/AZh$MASS", "$BIN/AZh$MASS_$SYSTEMATIC"); + } + } + + + cout << ">> Generating bbb uncertainties...\n"; + auto bbb = ch::BinByBinFactory() + .SetAddThreshold(0.1) + .SetFixNorm(true); + + bbb.AddBinByBin(cb.cp().process({"Zjets"}), cb); + + + cout << ">> Setting standardised bin names...\n"; + ch::SetStandardBinNames(cb); + + + RooWorkspace ws("htt", "htt"); + + TFile demo("AZh_demo.root", "RECREATE"); + + bool do_morphing = true; + map mass_var = { + {"ggA_AZhLLtautau", &mA} + }; + if (do_morphing) { + auto bins = cb.bin_set(); + for (auto b : bins) { + auto procs = cb.cp().bin({b}).signals().process_set(); + for (auto p : procs) { + ch::BuildRooMorphing(ws, cb, b, p, *(mass_var[p]), + "norm", true, true, false, &demo); + } + } + } + demo.Close(); + cb.AddWorkspace(ws); + cb.cp().signals().ExtractPdfs(cb, "htt", "$BIN_$PROCESS_morph"); + cb.PrintAll(); + + + string folder = "output/AZh_cards_nomodel"; + boost::filesystem::create_directories(folder); + + TFile output((folder + "/htt_input.AZh.root").c_str(), "RECREATE"); + cb.cp().mass({"*"}).WriteDatacard(folder + "/htt_cmb_AZh.txt", output); + auto bins = cb.bin_set(); + for (auto b : bins) { + cb.cp().bin({b}).mass({"*"}).WriteDatacard( + folder + "/" + b + ".txt", output); + } + output.Close(); + + + cout << "\n>> Done!\n"; +} diff --git a/CombinePdfs/bin/AZhMorphing.cpp b/CombinePdfs/bin/AZhMorphing.cpp new file mode 100644 index 00000000000..255f21dc458 --- /dev/null +++ b/CombinePdfs/bin/AZhMorphing.cpp @@ -0,0 +1,232 @@ +#include +#include +#include +#include +#include +#include +#include +#include "boost/filesystem.hpp" +#include "CombineHarvester/CombineTools/interface/CombineHarvester.h" +#include "CombineHarvester/CombineTools/interface/Utilities.h" +#include "CombineHarvester/CombineTools/interface/HttSystematics.h" +#include "CombineHarvester/CombineTools/interface/BinByBin.h" +#include "CombineHarvester/CombinePdfs/interface/MorphFunctions.h" +#include "CombineHarvester/CombineTools/interface/TFileIO.h" + +#include "RooWorkspace.h" +#include "RooRealVar.h" +#include "TH2.h" +#include "RooDataHist.h" +#include "RooHistFunc.h" +#include "RooFormulaVar.h" +#include "RooProduct.h" + +using namespace std; + +int main() { + ch::CombineHarvester cb; + + // cb.SetVerbosity(1); + + typedef vector> Categories; + typedef vector VString; + + // RooFit will be quite noisy if we don't set this + RooMsgService::instance().setGlobalKillBelow(RooFit::WARNING); + + + string auxiliaries = string(getenv("CMSSW_BASE")) + "/src/auxiliaries/"; + string aux_shapes = auxiliaries +"shapes/"; + string aux_pruning = auxiliaries +"pruning/"; + + // In the first part of this code we will construct our MSSM signal model. + // This means declaring mA and tanb as our free parameters, and defining all + // other Higgs masses, cross sections and branching ratios as a function of + // these. Note that this example is very verbose and explicit in the + // construction - one could imagine developing a more generic and efficient + // tool to do this. + + // Define mA and tanb as RooRealVars + // Here I do not declare a range and it is decided automatically from + // the inputs once the first RooDataHist is created. + // There were some issues found with declaring a range by hand. + RooRealVar mA("mA", "mA", 300.); + RooRealVar tanb("tanb", "tanb", 1.); + + // All the other inputs we need to build the model can be found in one of the + // LHCXSWG scans. Here we use the low-tanb-high scenario. This file contains a + // large number of 2D histograms, all binned in mA and tanb, that each provide + // a different mass, xsec or BR. + TFile inputs(TString(auxiliaries) + + "models/out.low-tb-high-8TeV-tanbAll-nnlo.root"); + // Get the TH2Fs for the masses of the h and H bosons. + //TH2F h_mH = ch::OpenFromTFile(&inputs, "h_mH"); + //TH2F h_mh = ch::OpenFromTFile(&inputs, "h_mh"); + // Now two more steps are needed: we have to convert each TH2F to a + // RooDataHist, then build a RooHistFunc from the RooDataHist. These steps + // will be repeated for every TH2F we import below. + //RooDataHist dh_mH("dh_mH", "dh_mH", RooArgList(mA, tanb), + // RooFit::Import(h_mH)); + //RooDataHist dh_mh("dh_mh", "dh_mh", RooArgList(mA, tanb), + // RooFit::Import(h_mh)); + //RooHistFunc mH("mH", "mH", RooArgList(mA, tanb), dh_mH); + //RooHistFunc mh("mh", "mh", RooArgList(mA, tanb), dh_mh); + // There is also the possibility to interpolate mass values rather than just + // take the value of the enclosing histogram bin, but we'll leave this off for + // now. + // mH.setInterpolationOrder(1); + + // Now we'll do the same for the cross section + TH2F h_ggF_xsec_A = ch::OpenFromTFile(&inputs, "h_ggF_xsec_A"); + RooDataHist dh_ggF_xsec_A("dh_ggF_xsec_A", "dh_ggF_xsec_A", + RooArgList(mA, tanb), RooFit::Import(h_ggF_xsec_A)); + RooHistFunc ggF_xsec_A("ggF_xsec_A", "ggF_xsec_A", RooArgList(mA, tanb), + dh_ggF_xsec_A); + + // Now the branching ratios to hh, tau tau and bb + TH2F h_brZh0_A = ch::OpenFromTFile(&inputs, "h_brZh0_A"); + // Add factor of 2 here for h->tautau, h->bb and reverse + h_brZh0_A = 0.10099*h_brZh0_A; + TH2F h_brtautau_h = ch::OpenFromTFile(&inputs, "h_brtautau_h"); + RooDataHist dh_brZh0_A("dh_brZh0_A", "dh_brZh0_A", + RooArgList(mA, tanb), + RooFit::Import(h_brZh0_A)); + RooDataHist dh_brtautau_h("dh_brtautau_h", "dh_brtautau_h", + RooArgList(mA, tanb), + RooFit::Import(h_brtautau_h)); + RooHistFunc brZh0_A("brZh0_A", "brZh0_A", RooArgList(mA, tanb), + dh_brZh0_A); + RooHistFunc brtautau_h("brtautau_h", "brtautau_h", RooArgList(mA, tanb), + dh_brtautau_h); + // We can build the xsec * BR products + RooProduct ggF_xsec_br_A("ggF_xsec_br_A", "", + RooArgList(ggF_xsec_A, brZh0_A, brtautau_h)); + // Let's print out the values of all of these objects + cout << "mA: " << mA.getVal() << "\n"; + cout << "tanb: " << tanb.getVal() << "\n\n"; + cout << "ggF_xsec_A: " << ggF_xsec_A.getVal() << "\n"; + cout << "brZh0_A: " << brZh0_A.getVal()/0.10099<< "\n"; + cout << "brtautau_h: " << brtautau_h.getVal() << "\n"; + cout << "ggF_xsec_br_A: " << ggF_xsec_br_A.getVal() << "\n"; + + map xs_map; + xs_map["AZh"] = &ggF_xsec_br_A; + + VString chns = + {"et", "mt","tt", "em"}; + + string input_folders = "ULB"; + + map bkg_procs; + bkg_procs["et"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + bkg_procs["mt"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + bkg_procs["em"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + bkg_procs["tt"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + + map sm_procs; + sm_procs["et"] = {"ZH_ww125","ZH_tt125"}; + + + VString sig_procs={"AZh"}; + + map cats; + cats["et_8TeV"] = { + {0, "eeet_zh"}, {1, "mmet_zh"}}; + cats["mt_8TeV"] = { + {0, "eemt_zh"}, {1, "mmmt_zh"}}; + cats["em_8TeV"] = { + {0, "eeem_zh"}, {1, "mmme_zh"}}; + cats["tt_8TeV"] = { + {0, "eett_zh"}, {1, "mmtt_zh"}}; + + vector masses = ch::MassesFromRange("220-350:10"); + + cout << ">> Creating processes and observations...\n"; + for (string era : {"8TeV"}) { + for (auto chn : chns) { + cb.AddObservations( + {"*"}, {"htt"}, {era}, {chn}, cats[chn+"_"+era]); + cb.AddProcesses( + {"*"}, {"htt"}, {era}, {chn}, bkg_procs[chn], cats[chn+"_"+era], false); + // cb.AddProcesses( + // {"*"},{"htt"}, {era}, {chn}, sm_procs[chn], cats[chn+"_"+era],false); + cb.AddProcesses( + masses, {"htt"}, {era}, {chn}, sig_procs, cats[chn+"_"+era], true); + } + } + + + cout << ">> Adding systematic uncertainties...\n"; + ch::AddSystematics_AZh(cb); + + cout << ">> Extracting histograms from input root files...\n"; + for (string era : {"8TeV"}) { + for (string chn : chns) { + string file = aux_shapes + input_folders + "/htt_AZh" + + ".inputs-AZh-" + era + ".root"; + cb.cp().channel({chn}).era({era}).backgrounds().ExtractShapes( + file, "$BIN/$PROCESS", "$BIN/$PROCESS_$SYSTEMATIC"); + cb.cp().channel({chn}).era({era}).signals().ExtractShapes( + file, "$BIN/$PROCESS$MASS", "$BIN/$PROCESS$MASS_$SYSTEMATIC"); + } + } + + + cout << ">> Generating bbb uncertainties...\n"; + auto bbb = ch::BinByBinFactory() + .SetAddThreshold(0.1) + .SetFixNorm(true); + + bbb.AddBinByBin(cb.cp().process({"Zjets"}), cb); + + + cout << ">> Setting standardised bin names...\n"; + ch::SetStandardBinNames(cb); + + + RooWorkspace ws("htt", "htt"); + + TFile demo("AZh_demo.root", "RECREATE"); + + bool do_morphing = true; + string postfix = "_eff_acc"; + map mass_var = { + {"AZh", &mA} + }; + if (do_morphing) { + auto bins = cb.bin_set(); + for (auto b : bins) { + auto procs = cb.cp().bin({b}).signals().process_set(); + for (auto p : procs) { + string pdf_name = b + "_" + p + "_morph"; + ch::BuildRooMorphing(ws, cb, b, p, *(mass_var[p]), + "eff_acc", true, true, false, &demo); + std::string prod_name = pdf_name + "_eff_acc"; + RooAbsReal *norm = ws.function(prod_name.c_str()); + RooProduct full_norm((pdf_name + "_norm").c_str(), "", + RooArgList(*norm, *(xs_map[p]))); + ws.import(full_norm, RooFit::RecycleConflictNodes()); + } + } + } + demo.Close(); + cb.AddWorkspace(ws); + cb.cp().signals().ExtractPdfs(cb, "htt", "$BIN_$PROCESS_morph"); + cb.PrintAll(); + + + string folder = "output/AZh_cards"; + boost::filesystem::create_directories(folder); + + TFile output((folder + "/htt_input.AZh.root").c_str(), "RECREATE"); + cb.cp().mass({"*"}).WriteDatacard(folder + "/htt_cmb_AZh.txt", output); + auto bins = cb.bin_set(); + for (auto b : bins) { + cb.cp().bin({b}).mass({"*"}).WriteDatacard( + folder + "/" + b + ".txt", output); + } + output.Close(); + + + cout << "\n>> Done!\n"; +} diff --git a/CombinePdfs/bin/AdaptChargedHiggs.cpp b/CombinePdfs/bin/AdaptChargedHiggs.cpp new file mode 100644 index 00000000000..d3c17ff9380 --- /dev/null +++ b/CombinePdfs/bin/AdaptChargedHiggs.cpp @@ -0,0 +1,115 @@ +#include +#include +#include "boost/algorithm/string/predicate.hpp" +#include "boost/program_options.hpp" +#include "CombineHarvester/CombineTools/interface/CombineHarvester.h" +#include "CombineHarvester/CombineTools/interface/Utilities.h" +#include "CombineHarvester/CombineTools/interface/TFileIO.h" +#include "CombineHarvester/CombinePdfs/interface/MorphFunctions.h" + +#include "RooWorkspace.h" +#include "RooRealVar.h" + +using namespace std; +using boost::starts_with; +namespace po = boost::program_options; + +int main(int argc, char* argv[]) { + std::string mass = "MH"; + po::variables_map vm; + po::options_description config("configuration"); + config.add_options() + ("mass,m", po::value(&mass)->default_value(mass)); + po::store(po::command_line_parser(argc, argv).options(config).run(), vm); + po::notify(vm); + + typedef vector> Categories; + typedef vector VString; + + // We will need to source some inputs from the "auxiliaries" repo + string auxiliaries = string(getenv("CMSSW_BASE")) + "/src/auxiliaries/"; + string aux_cards = auxiliaries +"datacards/HIG-14-020.r6636"; + + auto masses = ch::MassesFromRange("80,90,100,120,140,150,155,160"); + + // RooFit will be quite noisy if we don't set this + // RooMsgService::instance().setGlobalKillBelow(RooFit::WARNING); + + RooRealVar mHp(mass.c_str(), mass.c_str(), 80., 160.); + + + TH1::AddDirectory(false); + ch::CombineHarvester cb; + for (auto m : masses) { + cb.ParseDatacard( + aux_cards + "/combine_datacard_hplushadronic_m" + m + ".txt", "hplus", + "8TeV", "tauhad", 0, m); + } + + SetStandardBinNames(cb); + + // backgrounds are the same in each card, so can drop all but one set + // and make the mass generic + cb.FilterAll([&](ch::Object *obj) { + return (!obj->signal() && obj->mass() != masses[0]); + }); + cb.ForEachObj([&](ch::Object *obj) { + if (!obj->signal()) obj->set_mass("*"); + if (obj->signal() && starts_with(obj->process(), "HH")) { + obj->set_process("tt_HptaunubHptaunub"); + } + if (obj->signal() && starts_with(obj->process(), "HW")) { + obj->set_process("tt_HptaunubWb"); + } + }); + + // Also need to rename bbb shape systematics on signal as they all + // have a mass-point specific name, and BuildRooMorphing depends on + // the names being identical + cb.ForEachSyst([&](ch::Systematic *obj) { + if (obj->signal() && starts_with(obj->name(), "HH"+obj->mass())) { + std::string old_name = obj->name(); + std::string new_name = old_name; + boost::replace_all(new_name, "HH"+obj->mass(), "tt_HptaunubHptaunub"); + obj->set_name(new_name); + cb.RenameParameter(old_name, new_name); + } + if (obj->signal() && starts_with(obj->name(), "HW"+obj->mass())) { + std::string old_name = obj->name(); + std::string new_name = old_name; + boost::replace_all(new_name, "HW"+obj->mass(), "tt_HptaunubWb"); + obj->set_name(new_name); + cb.RenameParameter(old_name, new_name); + } + }); + + cb.PrintAll(); + + RooWorkspace ws("hplus", "hplus"); + + TFile demo("charged_higgs.root", "RECREATE"); + + bool do_morphing = true; + if (do_morphing) { + auto bins = cb.bin_set(); + for (auto b : bins) { + auto procs = cb.cp().bin({b}).signals().process_set(); + for (auto p : procs) { + ch::BuildRooMorphing(ws, cb, b, p, mHp, + "norm", true, true, true, &demo); + } + } + } + demo.Close(); + cb.AddWorkspace(ws); + cb.cp().signals().ExtractPdfs(cb, "hplus", "$BIN_$PROCESS_morph"); + cb.PrintAll(); + + string folder = "output/mssm_nomodel"; + boost::filesystem::create_directories(folder); + TFile output((folder + "/hplus_tauhad.input.mssm.root").c_str(), "RECREATE"); + cb.cp().mass({"*"}).WriteDatacard(folder + "/hplus_tauhad_mssm.txt", output); + output.Close(); +} + + diff --git a/CombinePdfs/bin/BuildFile.xml b/CombinePdfs/bin/BuildFile.xml index 27911fc1161..841ae2feac6 100644 --- a/CombinePdfs/bin/BuildFile.xml +++ b/CombinePdfs/bin/BuildFile.xml @@ -1,7 +1,12 @@ + + + + + diff --git a/CombinePdfs/bin/HhhMSSMCombinationExample.cpp b/CombinePdfs/bin/HhhMSSMCombinationExample.cpp index 647f411c859..5887d506388 100644 --- a/CombinePdfs/bin/HhhMSSMCombinationExample.cpp +++ b/CombinePdfs/bin/HhhMSSMCombinationExample.cpp @@ -127,7 +127,7 @@ int main() { TH2F h_bbH4f_xsec_A = ch::OpenFromTFile(&inputs, "h_bbH4f_xsec_A"); TH2F h_bbH_xsec_h = SantanderMatching(h_bbH4f_xsec_h, h_bbH5f_xsec_h, &h_mh); TH2F h_bbH_xsec_H = SantanderMatching(h_bbH4f_xsec_H, h_bbH5f_xsec_H, &h_mH); - TH2F h_bbH_xsec_A = SantanderMatching(h_bbH4f_xsec_H, h_bbH5f_xsec_H, nullptr); + TH2F h_bbH_xsec_A = SantanderMatching(h_bbH4f_xsec_A, h_bbH5f_xsec_A, nullptr); RooDataHist dh_bbH_xsec_h("dh_bbH_xsec_h", "dh_bbH_xsec_h", RooArgList(mA, tanb), RooFit::Import(h_bbH_xsec_h)); RooDataHist dh_bbH_xsec_H("dh_bbH_xsec_H", "dh_bbH_xsec_H", @@ -390,8 +390,11 @@ int main() { for (auto p : procs) { std::cout << "morphing process: " << p << std::endl; string pdf_name = b + "_" + p + "_morph"; + // Set option to force signal to 0 outside of template range for H->hh only + bool force_template_limit = true; + if(b.find("8")!=string::npos && b.find("9")!=string::npos) force_template_limit=false; ch::BuildRooMorphing(ws, cb, b, p, *(mass_var[p]), - "eff_acc", true, true, &demo); + "eff_acc", true, true, force_template_limit, &demo); std::string prod_name = pdf_name + "_eff_acc"; RooAbsReal *norm = ws.function(prod_name.c_str()); RooProduct full_norm((pdf_name + "_norm").c_str(), "", diff --git a/CombinePdfs/bin/HhhMorphingExample-NoModel.cpp b/CombinePdfs/bin/HhhMorphingExample-NoModel.cpp new file mode 100644 index 00000000000..a0819378ca0 --- /dev/null +++ b/CombinePdfs/bin/HhhMorphingExample-NoModel.cpp @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include +#include +#include "boost/filesystem.hpp" +#include "CombineHarvester/CombineTools/interface/CombineHarvester.h" +#include "CombineHarvester/CombineTools/interface/Utilities.h" +#include "CombineHarvester/CombineTools/interface/HttSystematics.h" +#include "CombineHarvester/CombinePdfs/interface/MorphFunctions.h" +#include "CombineHarvester/CombineTools/interface/TFileIO.h" + +#include "RooWorkspace.h" +#include "RooRealVar.h" +#include "TH2.h" +#include "RooDataHist.h" +#include "RooHistFunc.h" +#include "RooFormulaVar.h" +#include "RooProduct.h" + +using namespace std; + +int main() { + ch::CombineHarvester cb; + + // cb.SetVerbosity(1); + + typedef vector> Categories; + typedef vector VString; + + string auxiliaries = string(getenv("CMSSW_BASE")) + "/src/auxiliaries/"; + string aux_shapes = auxiliaries +"shapes/"; + string aux_pruning = auxiliaries +"pruning/"; + + // RooFit will be quite noisy if we don't set this + RooMsgService::instance().setGlobalKillBelow(RooFit::WARNING); + + RooRealVar mA("mA", "mA", 260., 350.); + RooRealVar mH("mH", "mH", 260., 350.); + RooRealVar mh("mh", "mh", 260., 350.); + + VString chns = + {"et", "mt","tt"}; + + map input_folders = { + {"et", "Imperial"}, + {"mt", "Imperial"}, + {"tt", "Italians"} + }; + + map bkg_procs; + bkg_procs["et"] = {"ZTT", "W", "QCD", "ZL", "ZJ", "TT", "VV"}; + bkg_procs["mt"] = {"ZTT", "W", "QCD", "ZL", "ZJ", "TT", "VV"}; + bkg_procs["tt"] = {"ZTT", "W", "QCD", "ZLL", "TT", "VV"}; + + /*map sm_procs; + sm_procs["et"] = {"ggH_SM125","VH_SM125","qqH_SM125"}; + sm_procs["mt"] = {"ggH_SM125","VH_SM125","qqH_SM125"}; + sm_procs["tt"] = {"ggH_SM125","VH_SM125","qqH_SM125"}; + */ + + //VString sig_procs={"ggHTohhTo2Tau2B"}; + map signal_types = { + {"ggHTohhTo2Tau2B", {"ggH_Hhhbbtautau"}} + }; + + map cats; + cats["et_8TeV"] = { + {0, "eleTau_2jet0tag"}, {1, "eleTau_2jet1tag"}, + {2, "eleTau_2jet2tag"}}; + + cats["mt_8TeV"] = { + {0, "muTau_2jet0tag"}, {1, "muTau_2jet1tag"}, + {2, "muTau_2jet2tag"}}; + + cats["tt_8TeV"] = { + {0, "tauTau_2jet0tag"}, {1, "tauTau_2jet1tag"}, + {2, "tauTau_2jet2tag"}}; + + + + vector masses = ch::MassesFromRange("260-350:10"); + + cout << ">> Creating processes and observations...\n"; + for (string era : {"8TeV"}) { + for (auto chn : chns) { + cb.AddObservations( + {"*"}, {"htt"}, {era}, {chn}, cats[chn+"_"+era]); + cb.AddProcesses( + {"*"}, {"htt"}, {era}, {chn}, bkg_procs[chn], cats[chn+"_"+era], false); + // cb.AddProcesses( + // {"*"},{"htt"}, {era}, {chn}, sm_procs[chn], cats[chn+"_"+era],false); + cb.AddProcesses( + masses, {"htt"}, {era}, {chn}, signal_types["ggHTohhTo2Tau2B"], cats[chn+"_"+era], true); + } + } + + //Remove W background from 2jet1tag and 2jet2tag categories for tt channel + cb.FilterProcs([](ch::Process const* p) { + return (p->bin() == "tauTau_2jet1tag") && p->process() == "W"; + }); + cb.FilterProcs([](ch::Process const* p) { + return (p->bin() == "tauTau_2jet2tag") && p->process() == "W"; + }); + + cout << ">> Adding systematic uncertainties...\n"; + ch::AddSystematics_hhh_et_mt(cb); + ch::AddSystematics_hhh_tt(cb); + + cout << ">> Extracting histograms from input root files...\n"; + for (string era : {"8TeV"}) { + for (string chn : chns) { + string file = aux_shapes + input_folders[chn] + "/htt_" + chn + + ".inputs-Hhh-" + era + ".root"; + cb.cp().channel({chn}).era({era}).backgrounds().ExtractShapes( + file, "$BIN/$PROCESS", "$BIN/$PROCESS_$SYSTEMATIC"); + cb.cp().channel({chn}).era({era}).process(signal_types["ggHTohhTo2Tau2B"]).ExtractShapes( + file, "$BIN/ggHTohhTo2Tau2B$MASS", "$BIN/ggHTohhTo2Tau2B$MASS_$SYSTEMATIC"); + } + } + + + cout << ">> Merging bin errors...\n"; + ch::CombineHarvester cb_et = move(cb.cp().channel({"et"})); + for (string era : {"8TeV"}) { + cb_et.cp().era({era}).bin_id({0, 1, 2}).process({"QCD","W","ZL","ZJ","VV","ZTT","TT"}) + .MergeBinErrors(0.1, 0.5); + } + ch::CombineHarvester cb_mt = move(cb.cp().channel({"mt"})); + for (string era : {"8TeV"}) { + cb_mt.cp().era({era}).bin_id({0, 1, 2}).process({"QCD","W","ZL","ZJ","VV","ZTT","TT"}) + .MergeBinErrors(0.1, 0.5); + } + ch::CombineHarvester cb_tt = move(cb.cp().channel({"tt"})); + for (string era : {"8TeV"}) { + cb_tt.cp().era({era}).bin_id({0, 1, 2}).process({"QCD","W","ZLL","VV","ZTT","TT"}) + .MergeBinErrors(0.1, 0.5); + } + + cout << ">> Generating bbb uncertainties...\n"; + cb_mt.cp().bin_id({0, 1, 2}).process({"ZL","ZJ","W", "QCD","TT", "ZTT","VV"}) + .AddBinByBin(0.1, true, &cb); + + cb_et.cp().bin_id({0, 1, 2}).process({"ZL", "ZJ", "QCD", "W", "TT","ZTT","VV"}) + .AddBinByBin(0.1, true, &cb); + + cb_tt.cp().bin_id({0, 1, 2}).era({"8TeV"}).process({"ZL", "ZJ","W", "TT", "QCD", "ZTT","VV"}) + .AddBinByBin(0.1, true, &cb); + + cout << ">> Setting standardised bin names...\n"; + ch::SetStandardBinNames(cb); + + + RooWorkspace ws("htt", "htt"); + + TFile demo("Hhh_demo.root", "RECREATE"); + + bool do_morphing = true; + map mass_var = { + {"ggH_Hhhbbtautau", &mH} + }; + if (do_morphing) { + auto bins = cb.bin_set(); + for (auto b : bins) { + auto procs = cb.cp().bin({b}).signals().process_set(); + for (auto p : procs) { + ch::BuildRooMorphing(ws, cb, b, p, *(mass_var[p]), + "norm", true, true, true, &demo); + } + } + } + demo.Close(); + cb.AddWorkspace(ws); + cb.cp().signals().ExtractPdfs(cb, "htt", "$BIN_$PROCESS_morph"); + cb.PrintAll(); + + + string folder = "output/hhh_cards_nomodel"; + boost::filesystem::create_directories(folder); + + TFile output((folder + "/htt_input.Hhh.root").c_str(), "RECREATE"); + cb.cp().mass({"*"}).WriteDatacard(folder + "/htt_cmb_Hhh.txt", output); + auto bins = cb.bin_set(); + for (auto b : bins) { + cb.cp().bin({b}).mass({"*"}).WriteDatacard( + folder + "/" + b + ".txt", output); + } + output.Close(); + + cout << "\n>> Done!\n"; +} diff --git a/CombinePdfs/bin/HhhMorphingExample.cpp b/CombinePdfs/bin/HhhMorphingExample.cpp index 85e458fec9f..3d54403ce2f 100644 --- a/CombinePdfs/bin/HhhMorphingExample.cpp +++ b/CombinePdfs/bin/HhhMorphingExample.cpp @@ -244,7 +244,7 @@ int main() { for (auto p : procs) { string pdf_name = b + "_" + p + "_morph"; ch::BuildRooMorphing(ws, cb, b, p, *(mass_var[p]), - "eff_acc", true, true, &demo); + "eff_acc", true, true, true, &demo); std::string prod_name = pdf_name + "_eff_acc"; RooAbsReal *norm = ws.function(prod_name.c_str()); RooProduct full_norm((pdf_name + "_norm").c_str(), "", diff --git a/CombinePdfs/bin/MorphingMSSM-NoModel.cpp b/CombinePdfs/bin/MorphingMSSM-NoModel.cpp index c48a1ac9c58..e7da005f89d 100644 --- a/CombinePdfs/bin/MorphingMSSM-NoModel.cpp +++ b/CombinePdfs/bin/MorphingMSSM-NoModel.cpp @@ -119,7 +119,7 @@ int main() { auto procs = cb.cp().bin({b}).signals().process_set(); for (auto p : procs) { ch::BuildRooMorphing(ws, cb, b, p, *(mass_var[p]), - "norm", true, true, &demo); + "norm", true, true, false, &demo); } } } diff --git a/CombinePdfs/bin/MorphingMSSM.cpp b/CombinePdfs/bin/MorphingMSSM.cpp index 026b3c999ec..f3efcb853a5 100644 --- a/CombinePdfs/bin/MorphingMSSM.cpp +++ b/CombinePdfs/bin/MorphingMSSM.cpp @@ -289,7 +289,7 @@ int main() { for (auto p : procs) { string pdf_name = b + "_" + p + "_morph"; ch::BuildRooMorphing(ws, cb, b, p, *(mass_var[p]), - "eff_acc", true, true, &demo); + "eff_acc", true, true, false, &demo); std::string prod_name = pdf_name + "_eff_acc"; RooAbsReal *norm = ws.function(prod_name.c_str()); RooProduct full_norm((pdf_name + "_norm").c_str(), "", diff --git a/CombinePdfs/bin/MorphingMSSMUpdate-NoModel.cpp b/CombinePdfs/bin/MorphingMSSMUpdate-NoModel.cpp new file mode 100644 index 00000000000..2c302885687 --- /dev/null +++ b/CombinePdfs/bin/MorphingMSSMUpdate-NoModel.cpp @@ -0,0 +1,223 @@ +#include +#include +#include +#include "boost/algorithm/string/predicate.hpp" +#include "boost/program_options.hpp" +#include "CombineHarvester/CombineTools/interface/CombineHarvester.h" +#include "CombineHarvester/CombineTools/interface/Utilities.h" +#include "CombineHarvester/CombineTools/interface/TFileIO.h" +#include "CombineHarvester/CombineTools/interface/HttSystematics.h" +#include "CombineHarvester/CombinePdfs/interface/MorphFunctions.h" +#include "CombineHarvester/CombineTools/interface/BinByBin.h" + +#include "RooWorkspace.h" +#include "RooRealVar.h" +#include "TH2.h" + +using namespace std; +using boost::starts_with; +namespace po = boost::program_options; + +int main(int argc, char** argv) { + string SM125= ""; + string mass = "mA"; + po::variables_map vm; + po::options_description config("configuration"); + config.add_options() + ("mass,m", po::value(&mass)->default_value(mass)) + ("SM125,h", po::value(&SM125)->default_value(SM125)); + po::store(po::command_line_parser(argc, argv).options(config).run(), vm); + po::notify(vm); + + typedef vector> Categories; + typedef vector VString; + + // We will need to source some inputs from the "auxiliaries" repo + //string SM125 = ""; + //if(argc>1) SM125 = string(argv[1]); + string auxiliaries = string(getenv("CMSSW_BASE")) + "/src/auxiliaries/"; + string aux_shapes = auxiliaries +"shapes/"; + string aux_pruning = auxiliaries +"pruning/"; + string input_dir = string(getenv("CMSSW_BASE")) + "/src/CombineHarvester/CombineTools/input"; + + VString chns = + {"mt", "et", "tt", "em", "mm"}; + + map input_folders = { + {"mt", "LLR"}, + {"et", "LLR"}, + {"tt", "LLR"}, + {"em", "MIT"}, + {"mm", "DESY-KIT"}, + }; + + // RooFit will be quite noisy if we don't set this + // RooMsgService::instance().setGlobalKillBelow(RooFit::WARNING); + + RooRealVar mA(mass.c_str(), mass.c_str(), 90., 1000.); + RooRealVar mH("mH", "mH", 90., 1000.); + RooRealVar mh("mh", "mh", 90., 1000.); + + map bkg_procs; + bkg_procs["mt"] = {"ZTT", "QCD", "W", "ZJ", "ZL", "TT", "VV"}; + bkg_procs["et"] = {"ZTT", "QCD", "W", "ZJ", "ZL", "TT", "VV"}; + bkg_procs["tt"] = {"ZTT", "QCD", "W", "ZJ", "ZL", "TT", "VV"}; + bkg_procs["em"] = {"Ztt", "ttbar", "EWK", "Fakes"}; + bkg_procs["mm"] = {"ZTT", "ZMM", "QCD", "TTJ", "WJets", "Dibosons"}; + + TH1::AddDirectory(false); + ch::CombineHarvester cb; + + VString SM_procs = {"ggH_SM125", "qqH_SM125", "VH_SM125"}; + + map cats; + cats["mt_8TeV"] = { + {10, "muTau_nobtag_low"}, {11, "muTau_nobtag_medium"}, {12, "muTau_nobtag_high"}, {13, "muTau_btag_low"}, {14, "muTau_btag_high"}}; + cats["et_8TeV"] = { + {10, "eleTau_nobtag_low"}, {11, "eleTau_nobtag_medium"}, {12, "eleTau_nobtag_high"}, {13, "eleTau_btag_low"}, {14, "eleTau_btag_high"}}; + cats["tt_8TeV"] = { + {10, "tauTau_nobtag_low"}, {11, "tauTau_nobtag_medium"}, {12, "tauTau_nobtag_high"}, {13, "tauTau_btag_low"}, {14, "tauTau_btag_high"}}; + cats["em_8TeV"] = { + {8, "emu_nobtag"}, {9, "emu_btag"}}; + cats["mm_8TeV"] = { + {8, "mumu_nobtag"}, {9, "mumu_btag"}}; + + auto masses = ch::MassesFromRange( + "90,100,120-140:10,140-200:20,200-500:50,600-1000:100"); + + cout << "Adding observations and backgrounds..."; + for (auto chn : chns) { + cb.AddObservations({"*"}, {"htt"}, {"8TeV"}, {chn}, cats[chn+"_8TeV"]); + cb.AddProcesses({"*"}, {"htt"}, {"8TeV"}, {chn}, bkg_procs[chn], cats[chn+"_8TeV"], false); + if(SM125==string("signal_SM125")) cb.AddProcesses({"*"}, {"htt"}, {"8TeV"}, {chn}, SM_procs, cats[chn+"_8TeV"], true); + else if(SM125==string("bkg_SM125")) cb.AddProcesses({"*"}, {"htt"}, {"8TeV"}, {chn}, SM_procs, cats[chn+"_8TeV"], false); + } + cout << " done\n"; + + cout << "Adding signal processes..."; + // Unlike in previous MSSM H->tautau analyses we will create a separate + // process for each Higgs in the datacards + map signal_types = { + {"ggH", {"ggh_htautau", "ggH_Htautau", "ggA_Atautau"}}, + {"bbH", {"bbh_htautau", "bbH_Htautau", "bbA_Atautau"}} + }; + if(mass=="MH"){ + signal_types = { + {"ggH", {"ggH"}}, + {"bbH", {"bbH"}} + }; + } + for (auto chn : chns) { + cb.AddProcesses(masses, {"htt"}, {"8TeV"}, {chn}, signal_types["ggH"], cats[chn+"_8TeV"], true); + cb.AddProcesses(masses, {"htt"}, {"8TeV"}, {chn}, signal_types["bbH"], cats[chn+"_8TeV"], true); + } + cout << " done\n"; + + cout << "Adding systematic uncertainties..."; + ch::AddMSSMUpdateSystematics_et_mt(cb); + ch::AddMSSMUpdateSystematics_em(cb); + ch::AddMSSMUpdateSystematics_mm(cb); + ch::AddMSSMUpdateSystematics_tt(cb); + cout << " done\n"; + + cout << "Extracting histograms from input root files..."; + for (string chn : chns) { + string file = aux_shapes + input_folders[chn] + "/htt_" + chn + + ".inputs-mssm-" + "8TeV" + "-0.root"; + cb.cp().channel({chn}).era({"8TeV"}).backgrounds().ExtractShapes + (file, "$CHANNEL/$PROCESS", "$CHANNEL/$PROCESS_$SYSTEMATIC"); + if(SM125==string("signal_SM125")) cb.cp().channel({chn}).era({"8TeV"}).process(SM_procs).ExtractShapes(file, "$BIN/$PROCESS", "$BIN/$PROCESS_$SYSTEMATIC"); + // We have to map each Higgs signal process to the same histogram, i.e: + // {ggh, ggH, ggA} --> ggH + // {bbh, bbH, bbA} --> bbH + cb.cp().channel({chn}).era({"8TeV"}).process(signal_types["ggH"]).ExtractShapes + (file, "$CHANNEL/ggH$MASS", "$CHANNEL/ggH$MASS_$SYSTEMATIC"); + cb.cp().channel({chn}).era({"8TeV"}).process(signal_types["bbH"]).ExtractShapes + (file, "$CHANNEL/bbH$MASS", "$CHANNEL/bbH$MASS_$SYSTEMATIC"); + } + cout << " done\n"; + + cout << "Scaling signal process rates for acceptance...\n"; + for (string e : {"8TeV"}) { + for (string p : {"ggH", "bbH"}) { + cout << "Scaling for process " << p << " and era " << e << "\n"; + auto gr = ch::TGraphFromTable( + "input/xsecs_brs/mssm_" + p + "_" + e + "_accept.txt", "mPhi", + "accept"); + cb.cp().process(signal_types[p]).era({e}).ForEachProc([&](ch::Process *proc) { + double m = boost::lexical_cast(proc->mass()); + proc->set_rate(proc->rate() * gr.Eval(m)); + }); + } + } + cout << "done\n"; + + cout << "Generating bbb uncertainties..."; + auto bbb = ch::BinByBinFactory() + .SetAddThreshold(0.05) + .SetFixNorm(true); + bbb.AddBinByBin(cb.cp().process({"ZTT", "QCD", "W", "ZJ", "ZL", "TT", "VV", "Ztt", "ttbar", "EWK", "Fakes", "ZMM", "TTJ", "WJets", "Dibosons"}), cb); + cout << " done\n"; + + cout << "Setting standardised bin names..."; + ch::SetStandardBinNames(cb); + cout << " done\n"; + + cout << "Pruning bbb uncertainties...\n"; + VString droplist = ch::ParseFileLines(aux_pruning + "uncertainty-pruning-drop-150602-mssm-taupt-CH.txt"); + cout << ">> Droplist contains " << droplist.size() << " entries\n"; + + set to_drop; + for (auto x : droplist) to_drop.insert(x); + auto pre_drop = cb.syst_name_set(); + cb.syst_name(droplist, false); + auto post_drop = cb.syst_name_set(); + cout << ">> Systematics dropped: " << pre_drop.size() - post_drop.size() << "\n"; + cout << "done\n"; + + cout << "Creating workspace...\n"; + RooWorkspace ws("htt", "htt"); + + TFile demo("htt_mssm_demo.root", "RECREATE"); + + bool do_morphing = true; + map mass_var = { + {"ggh_htautau", &mh}, {"ggH_Htautau", &mH}, {"ggA_Atautau", &mA}, + {"bbh_htautau", &mh}, {"bbH_Htautau", &mH}, {"bbA_Atautau", &mA} + }; + if(mass=="MH"){ + mass_var = { + {"ggH", &mA}, + {"bbH", &mA} + }; + } + if (do_morphing) { + auto bins = cb.bin_set(); + for (auto b : bins) { + auto procs = cb.cp().bin({b}).process(ch::JoinStr({signal_types["ggH"], signal_types["bbH"]})).process_set(); + for (auto p : procs) { + ch::BuildRooMorphing(ws, cb, b, p, *(mass_var[p]),"norm", true, true, false, &demo); + } + } + } + demo.Close(); + cb.AddWorkspace(ws); + cb.cp().process(ch::JoinStr({signal_types["ggH"], signal_types["bbH"]})).ExtractPdfs(cb, "htt", "$BIN_$PROCESS_morph"); + cb.PrintAll(); + cout << "done\n"; + + string folder = "output/mssm_nomodel"; + boost::filesystem::create_directories(folder); + + cout << "Writing datacards ..."; + TFile output((folder + "/htt_mssm_input.root").c_str(), "RECREATE"); + for (string chn : chns) { + auto bins = cb.cp().channel({chn}).bin_set(); + for (auto b : bins) { + cb.cp().channel({chn}).bin({b}).mass({"*"}).WriteDatacard(folder + "/" + b + ".txt", output); + } + } + cb.cp().mass({"*"}).WriteDatacard(folder + "/htt_mssm.txt", output); + output.Close(); + cout << " done\n"; +} diff --git a/CombinePdfs/interface/MorphFunctions.h b/CombinePdfs/interface/MorphFunctions.h index ae8c2b58ee0..0f8d033871a 100644 --- a/CombinePdfs/interface/MorphFunctions.h +++ b/CombinePdfs/interface/MorphFunctions.h @@ -35,7 +35,7 @@ inline TH1F AsTH1F(TH1 const* hist) { void BuildRooMorphing(RooWorkspace& ws, CombineHarvester& cb, std::string const& bin, std::string const& process, RooAbsReal& mass_var, std::string norm_postfix, - bool allow_morph, bool verbose, TFile * file = nullptr); + bool allow_morph, bool verbose, bool force_template_limit=false, TFile * file = nullptr); TGraph GraphFromSpline(RooSpline1D const* spline); diff --git a/CombinePdfs/python/AZh.py b/CombinePdfs/python/AZh.py new file mode 100644 index 00000000000..bd5ceea436e --- /dev/null +++ b/CombinePdfs/python/AZh.py @@ -0,0 +1,160 @@ +from HiggsAnalysis.CombinedLimit.PhysicsModel import * +import os +import ROOT +import math +import itertools + +class MSSMAZhHiggsModel(PhysicsModel): + def __init__(self, modelname): + PhysicsModel.__init__(self) + # Define the known production and decay processes + # These are strings we will look for in the process names to + # determine the correct normalisation scaling + self.ERAS = ['7TeV', '8TeV', '13TeV'] + # We can't add anything here that we're not actually going to have + # loaded from the model files. Should integrate this into the options + # somehow. + eras = ['8TeV'] + self.modelname = modelname + self.PROC_SETS = [] + self.PROC_SETS.append( + ([ 'ggA' ], [ 'AZhLLtautau' ], eras) + ) + + def setModelBuilder(self, modelBuilder): + """We're not supposed to overload this method, but we have to because + this is our only chance to import things into the workspace while it + is completely empty. This is primary so we can define some of our MSSM + Higgs boson masses as functions instead of the free variables that would + be imported later as dependents of the normalisation terms.""" + # First call the parent class implementation + PhysicsModel.setModelBuilder(self, modelBuilder) + if self.modelname == "low-tb-high_8TeV" : filename = 'out.low-tb-high-8TeV-tanbAll-nnlo.root' + if self.modelname == "low-tb-high_13TeV" : filename = 'low-tb-high_13TeV.root' + if self.modelname == "hMSSM_8TeV" : filename = 'hMSSM_8TeV.root' + if self.modelname == "hMSSM_13TeV" : filename = 'hMSSM_13TeV.root' + self.buildModel(os.environ['CMSSW_BASE']+'/src/auxiliaries/models/'+filename) + + def doHistFunc(self, name, hist, varlist, interpolate=1): + "method to conveniently create a RooHistFunc from a TH1/TH2 input" + dh = ROOT.RooDataHist('dh_%s'%name, 'dh_%s'%name, ROOT.RooArgList(*varlist), ROOT.RooFit.Import(hist)) + hfunc = ROOT.RooHistFunc(name, name, ROOT.RooArgSet(*varlist), dh) + hfunc.setInterpolationOrder(interpolate) + self.modelBuilder.out._import(hfunc, ROOT.RooFit.RecycleConflictNodes()) + return self.modelBuilder.out.function(name) + + def buildModel(self, filename): + mA = ROOT.RooRealVar('mA', 'mA', 300.) + tanb = ROOT.RooRealVar('tanb', 'tanb', 2.) + f = ROOT.TFile(filename) + #Take care of different histogram names for hMSSM: + if 'hMSSM' in self.modelname or '13TeV' in self.modelname or '14TeV' in self.modelname: + ggF_xsec_A_str = "xs_gg_A" + brtautau_h_str = "br_h_tautau" + brZh0_A_str="br_A_Zh" + else : + ggF_xsec_A_str = "h_ggF_xsec_A" + brtautau_h_str = "h_brtautau_h" + brZh0_A_str="h_brZh0_A" + + ggF_xsec_A = self.doHistFunc('xsec_ggA_8TeV', f.Get(ggF_xsec_A_str), [mA, tanb]) + total_br_hist = ROOT.TH2F() + total_br_hist = (f.Get(brtautau_h_str)) * (f.Get(brZh0_A_str)) + total_br_hist *= 0.10099 + brAZhLLtautau = self.doHistFunc('br_AZhLLtautau', total_br_hist, [mA, tanb]) + + + # Next step: creating theory uncertainties + # 1) for each syst source build kappaHi and kappaLo TH1s by doing a *careful* divide + # between nominal and shifted TH2s => "kappa_hi_xsec_ggh_8TeV_scale" + # 2) create nuisance parameter (QCD_scale_ggH?) and constraint. Like: + # def preProcessNuisances(self,nuisances): + # if self.add_bbH and not any(row for row in nuisances if row[0] == "QCDscale_bbH"): + # nuisances.append(("QCDscale_bbH",False, "param", [ "0", "1"], [] ) ) + # --> probably have to create the param ourself first, preProcessNuisances doesn't + # happen until later (after doParametersOfInterest) + # 3) create AsymPow and add to the norm product + + def doParametersOfInterest(self): + """Create POI and other parameters, and define the POI set.""" + # print '>> In doParametersOfInterest, workspace contents:' + self.modelBuilder.doVar("r[1,0,20]") + self.modelBuilder.out.var('mA').setConstant(True) + self.modelBuilder.out.var('tanb').setConstant(True) + for proc_set in self.PROC_SETS: + for (P, D, E) in itertools.product(*proc_set): + print (P, D, E) + terms = ['xsec_%s_%s' % (P, E), 'br_%s'%D] + terms += ['r'] + self.modelBuilder.factory_('prod::scaling_%s_%s_%s(%s)' % (P,D,E,','.join(terms))) + self.modelBuilder.out.function('scaling_%s_%s_%s' % (P,D,E)).Print('') + + # self.modelBuilder.out.Print() + self.modelBuilder.doSet('POI', 'r') + + def getHiggsProdDecMode(self, bin, process): + """Return a triple of (production, decay, energy)""" + print process + P = '' + D = '' + if "_" in process: + (P, D) = process.split("_") + else: + raise RuntimeError, 'Expected signal process %s to be of the form PROD_DECAY' % process + E = None + for era in self.ERAS: + if era in bin: + if E: raise RuntimeError, "Validation Error: bin string %s contains multiple known energies" % bin + E = era + if not E: + raise RuntimeError, 'Did not find a valid energy in bin string %s' % bin + return (P, D, E) + + def getYieldScale(self,bin,process): + if self.DC.isSignal[process]: + (P, D, E) = self.getHiggsProdDecMode(bin, process) + scaling = 'scaling_%s_%s_%s' % (P, D, E) + print 'Scaling %s/%s as %s' % (bin, process, scaling) + return scaling + else: + return 1 + +class BRChargedHiggs(PhysicsModel): + def __init__(self): + PhysicsModel.__init__(self) + + def doParametersOfInterest(self): + """Create POI and other parameters, and define the POI set.""" + self.modelBuilder.doVar('BR[0,0,1]'); + + self.modelBuilder.doSet('POI','BR') + + self.modelBuilder.factory_('expr::Scaling_HH("@0*@0", BR)') + self.modelBuilder.factory_('expr::Scaling_WH("2 * (1-@0)*@0", BR)') + #self.modelBuilder.factory_('expr::Scaling_tt("(1-@0)*(1-@0)", BR)') + self.modelBuilder.factory_('expr::Scaling_tt("1 - (@0+@1)", Scaling_HH, Scaling_WH)') + + self.processScaling = { 'tt_ttHchHch':'HH', 'tt_ttHchW':'WH', 'tt':'tt' } + + # self.modelBuilder.out.Print() + + def getYieldScale(self,bin,process): + for prefix, model in self.processScaling.iteritems(): + if process == prefix: + print 'Scaling %s/%s as %s' % (bin, process, 'Scaling_'+model) + return 'Scaling_'+model + return 1 + + + + +#8TeV +hMSSM_8TeV = MSSMAZhHiggsModel("hMSSM_8TeV") +lowtbhigh_8TeV = MSSMAZhHiggsModel("low-tb-high_8TeV") + +#13TeV +hMSSM_13TeV = MSSMAZhHiggsModel("hMSSM_13TeV") +lowtbhigh_13TeV = MSSMAZhHiggsModel("low-tb-high_13TeV") + +brChargedHiggs = BRChargedHiggs() + diff --git a/CombinePdfs/python/Hhh.py b/CombinePdfs/python/Hhh.py new file mode 100644 index 00000000000..a49f071f4a3 --- /dev/null +++ b/CombinePdfs/python/Hhh.py @@ -0,0 +1,169 @@ +from HiggsAnalysis.CombinedLimit.PhysicsModel import * +import os +import ROOT +import math +import itertools + +class MSSMHhhHiggsModel(PhysicsModel): + def __init__(self, modelname): + PhysicsModel.__init__(self) + # Define the known production and decay processes + # These are strings we will look for in the process names to + # determine the correct normalisation scaling + self.ERAS = ['7TeV', '8TeV', '13TeV'] + # We can't add anything here that we're not actually going to have + # loaded from the model files. Should integrate this into the options + # somehow. + eras = ['8TeV'] + self.modelname = modelname + self.PROC_SETS = [] + self.PROC_SETS.append( + ([ 'ggH' ], [ 'Hhhbbtautau' ], eras) + ) + + def setModelBuilder(self, modelBuilder): + """We're not supposed to overload this method, but we have to because + this is our only chance to import things into the workspace while it + is completely empty. This is primary so we can define some of our MSSM + Higgs boson masses as functions instead of the free variables that would + be imported later as dependents of the normalisation terms.""" + # First call the parent class implementation + PhysicsModel.setModelBuilder(self, modelBuilder) + if self.modelname == "low-tb-high_8TeV" : filename = 'out.low-tb-high-8TeV-tanbAll-nnlo.root' + if self.modelname == "low-tb-high_13TeV" : filename = 'low-tb-high_13TeV.root' + if self.modelname == "hMSSM_8TeV" : filename = 'hMSSM_8TeV.root' + if self.modelname == "hMSSM_13TeV" : filename = 'hMSSM_13TeV.root' + self.buildModel(os.environ['CMSSW_BASE']+'/src/auxiliaries/models/'+filename) + + def doHistFunc(self, name, hist, varlist, interpolate=1): + "method to conveniently create a RooHistFunc from a TH1/TH2 input" + dh = ROOT.RooDataHist('dh_%s'%name, 'dh_%s'%name, ROOT.RooArgList(*varlist), ROOT.RooFit.Import(hist)) + hfunc = ROOT.RooHistFunc(name, name, ROOT.RooArgSet(*varlist), dh) + hfunc.setInterpolationOrder(interpolate) + self.modelBuilder.out._import(hfunc, ROOT.RooFit.RecycleConflictNodes()) + return self.modelBuilder.out.function(name) + + def buildModel(self, filename): + mA = ROOT.RooRealVar('mA', 'mA', 300.) + tanb = ROOT.RooRealVar('tanb', 'tanb', 1.) + f = ROOT.TFile(filename) + #Take care of different histogram names for hMSSM: + if 'hMSSM' in self.modelname or '13TeV' in self.modelname or '14TeV' in self.modelname: + mH_str = "m_H" + mh_str = "m_h" + ggF_xsec_H_str = "xs_gg_H" + brtautau_h_str = "br_h_tautau" + brbb_h_str = "br_h_bb" + brh0h0_H_str="br_H_hh" + else : + mH_str = "h_mH" + mh_str = "h_mh" + ggF_xsec_H_str = "h_ggF_xsec_H" + brtautau_h_str = "h_brtautau_h" + brbb_h_str = "h_brbb_h" + brh0h0_H_str="h_brh0h0_H" + + mH = self.doHistFunc('mH', f.Get(mH_str), [mA, tanb]) + mh = self.doHistFunc('mh', f.Get(mh_str), [mA, tanb]) + ggF_xsec_H = self.doHistFunc('xsec_ggH_8TeV', f.Get(ggF_xsec_H_str), [mA, tanb]) + total_br_hist = ROOT.TH2F() + total_br_hist = (f.Get(brtautau_h_str)) * (f.Get(brbb_h_str)) * (f.Get(brh0h0_H_str)) + total_br_hist *= 2 + brHhhbbtautau = self.doHistFunc('br_Hhhbbtautau', total_br_hist, [mA, tanb]) + + # Next step: creating theory uncertainties + # 1) for each syst source build kappaHi and kappaLo TH1s by doing a *careful* divide + # between nominal and shifted TH2s => "kappa_hi_xsec_ggh_8TeV_scale" + # 2) create nuisance parameter (QCD_scale_ggH?) and constraint. Like: + # def preProcessNuisances(self,nuisances): + # if self.add_bbH and not any(row for row in nuisances if row[0] == "QCDscale_bbH"): + # nuisances.append(("QCDscale_bbH",False, "param", [ "0", "1"], [] ) ) + # --> probably have to create the param ourself first, preProcessNuisances doesn't + # happen until later (after doParametersOfInterest) + # 3) create AsymPow and add to the norm product + + def doParametersOfInterest(self): + """Create POI and other parameters, and define the POI set.""" + # print '>> In doParametersOfInterest, workspace contents:' + self.modelBuilder.doVar("r[1,0,20]") + self.modelBuilder.out.var('mA').setConstant(True) + self.modelBuilder.out.var('tanb').setConstant(True) + for proc_set in self.PROC_SETS: + for (P, D, E) in itertools.product(*proc_set): + print (P, D, E) + terms = ['xsec_%s_%s' % (P, E), 'br_%s'%D] + terms += ['r'] + self.modelBuilder.factory_('prod::scaling_%s_%s_%s(%s)' % (P,D,E,','.join(terms))) + self.modelBuilder.out.function('scaling_%s_%s_%s' % (P,D,E)).Print('') + + # self.modelBuilder.out.Print() + self.modelBuilder.doSet('POI', 'r') + + def getHiggsProdDecMode(self, bin, process): + """Return a triple of (production, decay, energy)""" + print process + P = '' + D = '' + if "_" in process: + (P, D) = process.split("_") + else: + raise RuntimeError, 'Expected signal process %s to be of the form PROD_DECAY' % process + E = None + for era in self.ERAS: + if era in bin: + if E: raise RuntimeError, "Validation Error: bin string %s contains multiple known energies" % bin + E = era + if not E: + raise RuntimeError, 'Did not find a valid energy in bin string %s' % bin + return (P, D, E) + + def getYieldScale(self,bin,process): + if self.DC.isSignal[process]: + print "is signal" + # something going wrong here + (P, D, E) = self.getHiggsProdDecMode(bin, process) + scaling = 'scaling_%s_%s_%s' % (P, D, E) + print 'Scaling %s/%s as %s' % (bin, process, scaling) + return scaling + else: + return 1 + +class BRChargedHiggs(PhysicsModel): + def __init__(self): + PhysicsModel.__init__(self) + + def doParametersOfInterest(self): + """Create POI and other parameters, and define the POI set.""" + self.modelBuilder.doVar('BR[0,0,1]'); + + self.modelBuilder.doSet('POI','BR') + + self.modelBuilder.factory_('expr::Scaling_HH("@0*@0", BR)') + self.modelBuilder.factory_('expr::Scaling_WH("2 * (1-@0)*@0", BR)') + #self.modelBuilder.factory_('expr::Scaling_tt("(1-@0)*(1-@0)", BR)') + self.modelBuilder.factory_('expr::Scaling_tt("1 - (@0+@1)", Scaling_HH, Scaling_WH)') + + self.processScaling = { 'tt_ttHchHch':'HH', 'tt_ttHchW':'WH', 'tt':'tt' } + + # self.modelBuilder.out.Print() + + def getYieldScale(self,bin,process): + for prefix, model in self.processScaling.iteritems(): + if process == prefix: + print 'Scaling %s/%s as %s' % (bin, process, 'Scaling_'+model) + return 'Scaling_'+model + return 1 + + + + +#8TeV +hMSSM_8TeV = MSSMHhhHiggsModel("hMSSM_8TeV") +lowtbhigh_8TeV = MSSMHhhHiggsModel("low-tb-high_8TeV") + +#13TeV +hMSSM_13TeV = MSSMHhhHiggsModel("hMSSM_13TeV") +lowtbhigh_13TeV = MSSMHhhHiggsModel("low-tb-high_13TeV") + +brChargedHiggs = BRChargedHiggs() + diff --git a/CombinePdfs/python/MSSM.py b/CombinePdfs/python/MSSM.py index ff16d0d9e9b..0b3b4a272bf 100644 --- a/CombinePdfs/python/MSSM.py +++ b/CombinePdfs/python/MSSM.py @@ -5,27 +5,39 @@ import itertools class MSSMHiggsModel(PhysicsModel): - def __init__(self): + def __init__(self, modelname): PhysicsModel.__init__(self) # Define the known production and decay processes # These are strings we will look for in the process names to # determine the correct normalisation scaling - self.ERAS = ['7TeV', '8TeV', '13TeV'] + self.ERAS = ['7TeV', '8TeV', '13TeV', '14TeV'] # We can't add anything here that we're not actually going to have # loaded from the model files. Should integrate this into the options # somehow. eras = ['8TeV'] + self.modelname = modelname self.PROC_SETS = [] self.PROC_SETS.append( - ([ 'ggh', 'bbh' ], [ 'htautau' ], eras) + ([ 'ggh', 'bbh' ], [ 'htautau' ], eras) ) self.PROC_SETS.append( - ([ 'ggH', 'bbH' ], [ 'Htautau' ], eras) + ([ 'ggH', 'bbH' ], [ 'Htautau' ], eras) ) self.PROC_SETS.append( - ([ 'ggA', 'bbA' ], [ 'Atautau' ], eras) + ([ 'ggA', 'bbA' ], [ 'Atautau' ], eras) ) - + # self.model = 'out.mhmax-mu+200-8TeV-tanbHigh-nnlo.root' + # self.era = '8TeV' + + + #def setPhysicsOptions(self, physOptions): + # """ + # Options are: model. + # """ + # for po in physOptions: + # if po.startswith("model="): self.model = po.replace("model=", "") + # if po.startswith("era=") : self.era = po.replace("era=", "") + def setModelBuilder(self, modelBuilder): """We're not supposed to overload this method, but we have to because this is our only chance to import things into the workspace while it @@ -34,7 +46,36 @@ def setModelBuilder(self, modelBuilder): be imported later as dependents of the normalisation terms.""" # First call the parent class implementation PhysicsModel.setModelBuilder(self, modelBuilder) - self.buildModel(os.environ['CMSSW_BASE']+'/src/auxiliaries/models/out.mhmax-mu+200-8TeV-tanbHigh-nnlo.root') + if self.modelname == "mhmax_7TeV" : filename = 'out.mhmax-mu+200-7TeV-tanbHigh-nnlo.root' + if self.modelname == "mhmax_8TeV" : filename = 'out.mhmax-mu+200-8TeV-tanbHigh-nnlo.root' + if self.modelname == "mhmax_13TeV" : filename = 'newmhmax_mu200_13TeV.root' + if self.modelname == "mhmax_14TeV" : filename = 'newmhmax_mu200_14TeV.root' + if self.modelname == "mhmodp_7TeV" : filename = 'out.mhmodp-7TeV-tanbHigh-nnlo.root' + if self.modelname == "mhmodp_8TeV" : filename = 'out.mhmodp-8TeV-tanbHigh-nnlo.root' + if self.modelname == "mhmodp_13TeV" : filename = 'mhmodp_mu200_13TeV.root' + if self.modelname == "mhmodp_14TeV" : filename = 'mhmodp_mu200_14TeV.root' + if self.modelname == "mhmodm_7TeV" : filename = 'out.mhmodm-7TeV-tanbHigh-nnlo.root' + if self.modelname == "mhmodm_8TeV" : filename = 'out.mhmodm-8TeV-tanbHigh-nnlo.root' + if self.modelname == "mhmodm_13TeV" : filename = 'mhmodm_13TeV.root' + if self.modelname == "mhmodm_14TeV" : filename = 'mhmodm_14TeV.root' + if self.modelname == "lightstau_7TeV" : filename = 'out.lightstau1-7TeV-tanbHigh-nnlo.root' + if self.modelname == "lightstau_8TeV" : filename = 'out.lightstau1-8TeV-tanbHigh-nnlo.root' + if self.modelname == "lightstau_13TeV" : filename = 'lightstau1_13TeV.root' + if self.modelname == "lightstau_14TeV" : filename = 'lightstau1_14TeV.root' + if self.modelname == "lightstopmod_7TeV" : filename = 'out.lightstopmod-7TeV-tanbHigh-nnlo.root' + if self.modelname == "lightstopmod_8TeV" : filename = 'out.lightstopmod-8TeV-tanbHigh-nnlo.root' + if self.modelname == "lightstopmod_13TeV" : filename = 'lightstopmpd_13TeV.root' + if self.modelname == "lightstopmod_14TeV" : filename = 'lightstopmod_14TeV.root' + if self.modelname == "tauphobic_7TeV" : filename = 'out.tauphobic-7TeV-tanbAll-nnlo.root' + if self.modelname == "tauphobic_8TeV" : filename = 'out.tauphobic-8TeV-tanbAll-nnlo.root' + if self.modelname == "tauphobic_13TeV" : filename = 'tauphobic_13TeV.root' + if self.modelname == "tauphobic_14TeV" : filename = 'tauphobic_14TeV.root' + if self.modelname == "hMSSM_8TeV" : filename = 'hMSSM_8TeV.root' + if self.modelname == "hMSSM_13TeV" : filename = 'hMSSM_13TeV.root' + if self.modelname == "low-tb-high_8TeV" : filename = 'low-tb-high_8TeV.root' + if self.modelname == "low-tb-high_13TeV" : filename = 'low-tb-high_13TeV.root' + self.buildModel(os.environ['CMSSW_BASE']+'/src/auxiliaries/models/'+filename) + #self.buildModel(os.environ['CMSSW_BASE']+'/src/auxiliaries/models/'+self.model) def doHistFunc(self, name, hist, varlist, interpolate=1): "method to conveniently create a RooHistFunc from a TH1/TH2 input" @@ -44,37 +85,114 @@ def doHistFunc(self, name, hist, varlist, interpolate=1): self.modelBuilder.out._import(hfunc, ROOT.RooFit.RecycleConflictNodes()) return self.modelBuilder.out.function(name) + def doAsymPow(self, name, h_kappa_lo, h_kappa_hi, param, varlist): + "create AsymPow rate scaler given two TH2 inputs corresponding to kappa_hi and kappa_lo" + param_var = self.modelBuilder.out.var(param) + if not param_var: + self.modelBuilder.doVar('%s[0,-7,7]'%param) + param_var = self.modelBuilder.out.var(param) + print param_var + hi = self.doHistFunc('%s_hi'%name, h_kappa_hi, varlist) + lo = self.doHistFunc('%s_lo'%name, h_kappa_lo, varlist) + asym = ROOT.AsymPow('systeff_%s'%name, '', lo, hi, param_var) + self.modelBuilder.out._import(asym) + return self.modelBuilder.out.function('systeff_%s'%name) + def santanderMatching(self, h4f, h5f, mass = None): res = h4f.Clone() for x in xrange(1, h4f.GetNbinsX() + 1): for y in xrange(1, h4f.GetNbinsY() +1): mh = h4f.GetXaxis().GetBinCenter(x) if mass is None else mass.GetBinContent(x, y) - t = math.log(mh / 4.75) - 2. + t = math.log(mh / 4.92) - 2. fourflav = h4f.GetBinContent(x, y) fiveflav = h5f.GetBinContent(x, y) sigma = (1. / (1. + t)) * (fourflav + t * fiveflav) res.SetBinContent(x, y, sigma) return res - + def safeTH2DivideForKappa(self, h1, h2): + """Divides two TH2s taking care of exceptions like divide by zero + and potentially doing more checks in the future""" + res = h1.Clone() + for x in xrange(1, h1.GetNbinsX() + 1): + for y in xrange(1, h2.GetNbinsY() +1): + val_h1 = h1.GetBinContent(x, y) + val_h2 = h2.GetBinContent(x, y) + if val_h1 == 0. or val_h2 == 0.: + print ('Warning: dividing histograms %s and %s at bin (%i,%i)=(%g, %g) ' + 'with values: %g/%g, will set the kappa to 1.0 here' % ( + h1.GetName(), h2.GetName(), x, y, h1.GetXaxis().GetBinCenter(x), + h1.GetYaxis().GetBinCenter(y), val_h1, val_h2 + )) + new_val = 1. + else: + new_val = val_h1 / val_h2 + res.SetBinContent(x, y, new_val) + return res + def buildModel(self, filename): mA = ROOT.RooRealVar('mA', 'mA', 344., 90., 1000.) tanb = ROOT.RooRealVar('tanb', 'tanb', 9., 1., 60.) f = ROOT.TFile(filename) - mH = self.doHistFunc('mH', f.Get('h_mH'), [mA, tanb]) - mh = self.doHistFunc('mh', f.Get('h_mh'), [mA, tanb]) - ggF_xsec_h = self.doHistFunc('xsec_ggh_8TeV', f.Get('h_ggF_xsec_h'), [mA, tanb]) - ggF_xsec_H = self.doHistFunc('xsec_ggH_8TeV', f.Get('h_ggF_xsec_H'), [mA, tanb]) - ggF_xsec_A = self.doHistFunc('xsec_ggA_8TeV', f.Get('h_ggF_xsec_A'), [mA, tanb]) - bbH_xsec_h = self.doHistFunc('xsec_bbh_8TeV', self.santanderMatching(f.Get('h_bbH4f_xsec_h'), f.Get('h_bbH_xsec_h'), f.Get('h_mh')), [mA, tanb]) - bbH_xsec_H = self.doHistFunc('xsec_bbH_8TeV', self.santanderMatching(f.Get('h_bbH4f_xsec_H'), f.Get('h_bbH_xsec_H'), f.Get('h_mH')), [mA, tanb]) - bbH_xsec_A = self.doHistFunc('xsec_bbA_8TeV', self.santanderMatching(f.Get('h_bbH4f_xsec_A'), f.Get('h_bbH_xsec_A')), [mA, tanb]) - brtautau_h = self.doHistFunc('br_htautau', f.Get('h_brtautau_h'), [mA, tanb]) - brtautau_H = self.doHistFunc('br_Htautau', f.Get('h_brtautau_H'), [mA, tanb]) - ggF_xsec_A = self.doHistFunc('br_Atautau', f.Get('h_brtautau_A'), [mA, tanb]) + #Take care of different histogram names: (May can be reduced if ultimately changed to 13TeV/14TeV) + if 'hMSSM' in self.modelname or 'low-tb-high' in self.modelname or '13TeV' in self.modelname or '14TeV' in self.modelname: + mH_str = "m_H" + mh_str = "m_h" + ggF_xsec_h_str = "xs_gg_h" + ggF_xsec_H_str = "xs_gg_H" + ggF_xsec_A_str = "xs_gg_A" + bbH_4f_xsec_h_str = "xs_bb4F_h" + bbH_4f_xsec_H_str = "xs_bb4F_H" + bbH_4f_xsec_A_str = "xs_bb4F_A" + bbH_xsec_h_str = "xs_bb5F_h" + bbH_xsec_H_str = "xs_bb5F_H" + bbH_xsec_A_str = "xs_bb5F_A" + brtautau_h_str = "br_h_tautau" + brtautau_H_str = "br_H_tautau" + brtautau_A_str = "br_A_tautau" + ggF_xsec_h_scale_hi_str = "xs_gg_h_scaleUp" + ggF_xsec_h_scale_lo_str = "xs_gg_h_scaleDown" + else : + mH_str = "h_mH" + mh_str = "h_mh" + ggF_xsec_h_str = "h_ggF_xsec_h" + ggF_xsec_H_str = "h_ggF_xsec_H" + ggF_xsec_A_str = "h_ggF_xsec_A" + bbH_4f_xsec_h_str = "h_bbH4f_xsec_h" + bbH_4f_xsec_H_str = "h_bbH4f_xsec_H" + bbH_4f_xsec_A_str = "h_bbH4f_xsec_A" + bbH_xsec_h_str = "h_bbH_xsec_h" + bbH_xsec_H_str = "h_bbH_xsec_H" + bbH_xsec_A_str = "h_bbH_xsec_A" + brtautau_h_str = "h_brtautau_h" + brtautau_H_str = "h_brtautau_H" + brtautau_A_str = "h_brtautau_A" + ggF_xsec_h_scale_hi_str = "h_ggF_xsec05_h" + ggF_xsec_h_scale_lo_str = "h_ggF_xsec20_h" + mH = self.doHistFunc('mH', f.Get(mH_str), [mA, tanb]) + mh = self.doHistFunc('mh', f.Get(mh_str), [mA, tanb]) + ggF_xsec_h = self.doHistFunc('xsec_ggh_8TeV', f.Get(ggF_xsec_h_str), [mA, tanb]) + ggF_xsec_H = self.doHistFunc('xsec_ggH_8TeV', f.Get(ggF_xsec_H_str), [mA, tanb]) + ggF_xsec_A = self.doHistFunc('xsec_ggA_8TeV', f.Get(ggF_xsec_A_str), [mA, tanb]) + bbH_xsec_h = self.doHistFunc('xsec_bbh_8TeV', self.santanderMatching(f.Get(bbH_4f_xsec_h_str), f.Get(bbH_xsec_h_str), f.Get(mh_str)), [mA, tanb]) + bbH_xsec_H = self.doHistFunc('xsec_bbH_8TeV', self.santanderMatching(f.Get(bbH_4f_xsec_H_str), f.Get(bbH_xsec_H_str), f.Get(mH_str)), [mA, tanb]) + bbH_xsec_A = self.doHistFunc('xsec_bbA_8TeV', self.santanderMatching(f.Get(bbH_4f_xsec_A_str), f.Get(bbH_xsec_A_str)), [mA, tanb]) + brtautau_h = self.doHistFunc('br_htautau', f.Get(brtautau_h_str), [mA, tanb]) + brtautau_H = self.doHistFunc('br_Htautau', f.Get(brtautau_H_str), [mA, tanb]) + brtautau_A = self.doHistFunc('br_Atautau', f.Get(brtautau_A_str), [mA, tanb]) + #print f.Get(brtautau_h_str).GetBinContent(f.Get(brtautau_h_str).GetXaxis().FindBin(mA.getVal()),f.Get(brtautau_h_str).GetYaxis().FindBin(tanb.getVal())), brtautau_h.getVal() + + # h_ggF_xsec_h_scale_hi = self.safeTH2DivideForKappa(f.Get(ggF_xsec_h_scale_hi_str), f.Get(ggF_xsec_h_str)) + # h_ggF_xsec_h_scale_lo = self.safeTH2DivideForKappa(f.Get(ggF_xsec_h_scale_lo_str), f.Get(ggF_xsec_h_str)) + # ggF_xsec_h_scale = self.doAsymPow('systeff_ggF_xsec_h_scale_8TeV', h_ggF_xsec_h_scale_lo, h_ggF_xsec_h_scale_hi, 'ggF_xsec_h_scale_8TeV', [mA, tanb]) + # ftest = ROOT.TFile('model_debug.root', 'RECREATE') + # ftest.WriteTObject(h_ggF_xsec_h_scale_hi, 'h_ggF_xsec_h_scale_hi') + # ftest.WriteTObject(h_ggF_xsec_h_scale_lo, 'h_ggF_xsec_h_scale_lo') + # ftest.Close() # Next step: creating theory uncertainties - # 1) for each syst source build kappaHi and kappaLo TH1s by doing a *careful* divide + # 1) for each syst source build kappaHi and kappaLo TH2s by doing a *careful* divide # between nominal and shifted TH2s => "kappa_hi_xsec_ggh_8TeV_scale" + # 2) create nuisance parameter (QCD_scale_ggH?) and constraint. Like: # def preProcessNuisances(self,nuisances): # if self.add_bbH and not any(row for row in nuisances if row[0] == "QCDscale_bbH"): @@ -83,6 +201,9 @@ def buildModel(self, filename): # happen until later (after doParametersOfInterest) # 3) create AsymPow and add to the norm product + # def preProcessNuisances(self,nuisances): + # nuisances.append(("ggF_xsec_h_scale_8TeV",False, "param", [ "0", "1"], [] ) ) + def doParametersOfInterest(self): """Create POI and other parameters, and define the POI set.""" # print '>> In doParametersOfInterest, workspace contents:' @@ -126,6 +247,70 @@ def getYieldScale(self,bin,process): else: return 1 +class BRChargedHiggs(PhysicsModel): + def __init__(self): + PhysicsModel.__init__(self) + + def doParametersOfInterest(self): + """Create POI and other parameters, and define the POI set.""" + self.modelBuilder.doVar('BR[0,0,1]'); + + self.modelBuilder.doSet('POI','BR') + + self.modelBuilder.factory_('expr::Scaling_HH("@0*@0", BR)') + self.modelBuilder.factory_('expr::Scaling_WH("2 * (1-@0)*@0", BR)') + #self.modelBuilder.factory_('expr::Scaling_tt("(1-@0)*(1-@0)", BR)') + self.modelBuilder.factory_('expr::Scaling_tt("1 - (@0+@1)", Scaling_HH, Scaling_WH)') + + self.processScaling = { 'tt_ttHchHch':'HH', 'tt_ttHchW':'WH', 'tt':'tt' } + + # self.modelBuilder.out.Print() + + def getYieldScale(self,bin,process): + for prefix, model in self.processScaling.iteritems(): + if process == prefix: + print 'Scaling %s/%s as %s' % (bin, process, 'Scaling_'+model) + return 'Scaling_'+model + return 1 + + + +#7TeV +mhmax_7TeV = MSSMHiggsModel("mhmax_7TeV") +mhmodp_7TeV = MSSMHiggsModel("mhmodp_7TeV") +mhmodm_7TeV = MSSMHiggsModel("mhmodm_7TeV") +lightstau_7TeV = MSSMHiggsModel("lightstau_7TeV") +lightstopmod_7TeV = MSSMHiggsModel("lightstopmod_7TeV") +tauphobic_7TeV = MSSMHiggsModel("tauphobic_7TeV") + +#8TeV +mhmax_8TeV = MSSMHiggsModel("mhmax_8TeV") +mhmodp_8TeV = MSSMHiggsModel("mhmodp_8TeV") +mhmodm_8TeV = MSSMHiggsModel("mhmodm_8TeV") +lightstau_8TeV = MSSMHiggsModel("lightstau_8TeV") +lightstopmod_8TeV = MSSMHiggsModel("lightstopmod_8TeV") +tauphobic_8TeV = MSSMHiggsModel("tauphobic_8TeV") +hMSSM_8TeV = MSSMHiggsModel("hMSSM_8TeV") +lowtbhigh_8TeV = MSSMHiggsModel("low-tb-high_8TeV") + +#13TeV +mhmax_13TeV = MSSMHiggsModel("mhmax_13TeV") +mhmodp_13TeV = MSSMHiggsModel("mhmodp_13TeV") +mhmodm_13TeV = MSSMHiggsModel("mhmodm_13TeV") +lightstau_13TeV = MSSMHiggsModel("lightstau_13TeV") +lightstopmod_13TeV = MSSMHiggsModel("lightstopmod_13TeV") +tauphobic_13TeV = MSSMHiggsModel("tauphobic_13TeV") +hMSSM_13TeV = MSSMHiggsModel("hMSSM_13TeV") +lowtbhigh_13TeV = MSSMHiggsModel("low-tb-high_13TeV") + +#14TeV +mhmax_14TeV = MSSMHiggsModel("mhmax_14TeV") +mhmodp_14TeV = MSSMHiggsModel("mhmodp_14TeV") +mhmodm_14TeV = MSSMHiggsModel("mhmodm_14TeV") +lightstau_14TeV = MSSMHiggsModel("lightstau_14TeV") +lightstopmod_14TeV = MSSMHiggsModel("lightstopmod_14TeV") +tauphobic_14TeV = MSSMHiggsModel("tauphobic_14TeV") -MSSM = MSSMHiggsModel() +#BR Charged Higgs +brChargedHiggs = BRChargedHiggs() diff --git a/CombinePdfs/python/MSSMv2.py b/CombinePdfs/python/MSSMv2.py new file mode 100644 index 00000000000..fb7df51c86c --- /dev/null +++ b/CombinePdfs/python/MSSMv2.py @@ -0,0 +1,255 @@ +from HiggsAnalysis.CombinedLimit.PhysicsModel import * +import os +import ROOT +import math +import itertools +import pprint +import sys + +class MSSMHiggsModel(PhysicsModel): + def __init__(self): + PhysicsModel.__init__(self) + self.filePrefix = '' + self.modelFiles = {} + self.h_dict = {} + self.h_dict[0] = { + 'mH' : 'h_mH', + 'mh' : 'h_mh', + 'mHp' : 'h_mHp', + 'br_tHpb' : 'h_brHpb_t', + 'br_Hptaunu' : 'h_brtaunu_Hp', + 'br_Hhh' : 'h_brh0h0_H', + 'br_AZh' : 'h_brZh0_A' + } + self.h_dict[1] = { + 'mH' : 'm_H', + 'mh' : 'm_h', + 'mHp' : 'm_Hp', + 'br_tHpb' : 'br_t_Hpb', + 'br_Hptaunu' : 'br_Hp_taunu', + 'br_Hhh' : 'br_H_hh', + 'br_AZh' : 'br_A_Zh' + } + for X in ['h', 'H', 'A']: + self.h_dict[0].update({ + 'xs_gg%s'%X : 'h_ggF_xsec_%s'%X, + 'xs_bb4f%s'%X : 'h_bbH4f_xsec_%s'%X, + 'xs_bb5f%s'%X : 'h_bbH_xsec_%s'%X, + 'br_%stautau'%X : 'h_brtautau_%s'%X, + 'br_%sbb'%X : 'h_brbb_%s'%X, + }) + self.h_dict[1].update({ + 'xs_gg%s'%X : 'xs_gg_%s'%X, + 'xs_bb4f%s'%X : 'xs_bb4F_%s'%X, + 'xs_bb5f%s'%X : 'xs_bb5F_%s'%X, + 'br_%stautau'%X : 'br_%s_tautau'%X, + 'br_%sbb'%X : 'br_%s_bb'%X + }) + # Define the known production and decay processes + # These are strings we will look for in the process names to + # determine the correct normalisation scaling + self.ERAS = ['7TeV', '8TeV', '13TeV', '14TeV'] + self.PROC_SETS = [] + + def setPhysicsOptions(self,physOptions): + for po in physOptions: + if po.startswith('filePrefix='): + self.filePrefix = po.replace('filePrefix=', '') + print 'Set file prefix to: %s' % self.filePrefix + if po.startswith('modelFiles='): + cfgList = po.replace('modelFiles=', '').split(':') + for cfg in cfgList: + cfgSplit = cfg.split(',') + if len(cfgSplit) != 3: + raise RuntimeError, 'Model file argument %s should be in the format ERA,FILE,VERSION' % cfg + self.modelFiles[cfgSplit[0]] = (cfgSplit[1], int(cfgSplit[2])) + pprint.pprint(self.modelFiles) + + def setModelBuilder(self, modelBuilder): + """We're not supposed to overload this method, but we have to because + this is our only chance to import things into the workspace while it + is completely empty. This is primary so we can define some of our MSSM + Higgs boson masses as functions instead of the free variables that would + be imported later as dependents of the normalisation terms.""" + # First call the parent class implementation + PhysicsModel.setModelBuilder(self, modelBuilder) + self.buildModel() + + def doHistFunc(self, name, hist, varlist, interpolate=0): + "method to conveniently create a RooHistFunc from a TH1/TH2 input" + print 'Doing histFunc %s...' % name + dh = ROOT.RooDataHist('dh_%s'%name, 'dh_%s'%name, ROOT.RooArgList(*varlist), ROOT.RooFit.Import(hist)) + hfunc = ROOT.RooHistFunc(name, name, ROOT.RooArgSet(*varlist), dh) + hfunc.setInterpolationOrder(interpolate) + self.modelBuilder.out._import(hfunc, ROOT.RooFit.RecycleConflictNodes()) + return self.modelBuilder.out.function(name) + + def doAsymPow(self, name, h_kappa_lo, h_kappa_hi, param, varlist): + "create AsymPow rate scaler given two TH2 inputs corresponding to kappa_hi and kappa_lo" + param_var = self.modelBuilder.out.var(param) + if not param_var: + self.modelBuilder.doVar('%s[0,-7,7]'%param) + param_var = self.modelBuilder.out.var(param) + print param_var + hi = self.doHistFunc('%s_hi'%name, h_kappa_hi, varlist) + lo = self.doHistFunc('%s_lo'%name, h_kappa_lo, varlist) + asym = ROOT.AsymPow('systeff_%s'%name, '', lo, hi, param_var) + self.modelBuilder.out._import(asym) + return self.modelBuilder.out.function('systeff_%s'%name) + + def santanderMatching(self, h4f, h5f, mass = None): + res = h4f.Clone() + for x in xrange(1, h4f.GetNbinsX() + 1): + for y in xrange(1, h4f.GetNbinsY() +1): + mh = h4f.GetXaxis().GetBinCenter(x) if mass is None else mass.GetBinContent(x, y) + if mh <= 0: + print 'santanderMatching: Have mh = %f at (%f,%f), using h4f value' % (mh, h4f.GetXaxis().GetBinCenter(x), h4f.GetYaxis().GetBinCenter(y)) + res.SetBinContent(x, y, h4f.GetBinContent(x, y)) + else: + t = math.log(mh / 4.92) - 2. + fourflav = h4f.GetBinContent(x, y) + fiveflav = h5f.GetBinContent(x, y) + sigma = (1. / (1. + t)) * (fourflav + t * fiveflav) + res.SetBinContent(x, y, sigma) + return res + + def safeTH2DivideForKappa(self, h1, h2): + """Divides two TH2s taking care of exceptions like divide by zero + and potentially doing more checks in the future""" + res = h1.Clone() + for x in xrange(1, h1.GetNbinsX() + 1): + for y in xrange(1, h2.GetNbinsY() +1): + val_h1 = h1.GetBinContent(x, y) + val_h2 = h2.GetBinContent(x, y) + if val_h1 == 0. or val_h2 == 0.: + print ('Warning: dividing histograms %s and %s at bin (%i,%i)=(%g, %g) ' + 'with values: %g/%g, will set the kappa to 1.0 here' % ( + h1.GetName(), h2.GetName(), x, y, h1.GetXaxis().GetBinCenter(x), + h1.GetYaxis().GetBinCenter(y), val_h1, val_h2 + )) + new_val = 1. + else: + new_val = val_h1 / val_h2 + res.SetBinContent(x, y, new_val) + return res + + def buildModel(self): + # It's best not to set ranges for the model parameters here. + # RooFit will create them automatically from the x- and y-axis + # ranges of the input histograms + mA = ROOT.RooRealVar('mA', 'mA', 120.) + tanb = ROOT.RooRealVar('tanb', 'tanb', 20.) + pars = [mA, tanb] + # ggF_xsec_h_scale_hi_str = "xs_gg_h_scaleUp" + # ggF_xsec_h_scale_lo_str = "xs_gg_h_scaleDown" + # ggF_xsec_h_scale_hi_str = "h_ggF_xsec05_h" + # ggF_xsec_h_scale_lo_str = "h_ggF_xsec20_h" + doneMasses = False + + for era, (file, version) in self.modelFiles.iteritems(): + hd = self.h_dict[version] + f = ROOT.TFile(self.filePrefix + file) + + # We take the masses from the 1st model file, under the + # assumption that they are the same in all model files + if not doneMasses: + self.doHistFunc('mH', f.Get(hd['mH']), pars) + self.doHistFunc('mh', f.Get(hd['mh']), pars) + self.doHistFunc('mHp', f.Get(hd['mHp']), pars) + doneMasses = True + + # Do the xsecs and BRs for the three neutral Higgs bosons + for X in ['h', 'H', 'A']: + self.doHistFunc('xs_gg%s_%s' % (X, era), f.Get(hd['xs_gg%s'%X]), pars) + # Build the Santander-matched bbX cross section. The matching depends + # on the mass of the Higgs boson in question, so for the h and H we + # pass the mh or mH TH2 as an additional argument + self.doHistFunc('xs_bb%s_%s' % (X, era), self.santanderMatching( + f.Get(hd['xs_bb4f%s'%X]), + f.Get(hd['xs_bb5f%s'%X]), + None if X=='A' else f.Get(hd['m%s'%X])), pars) + self.doHistFunc('br_%stautau_%s' % (X, era), f.Get(hd['br_%stautau'%X]), pars) + self.doHistFunc('br_%sbb_%s' % (X, era), f.Get(hd['br_%sbb'%X]), pars) + # Make a note of what we've built, will be used to create scaling expressions later + self.PROC_SETS.append(([ 'gg%s'%X, 'bb%s'%X ], [ '%stautau'%X, '%sbb'%X], [era]) + ) + # Do the BRs for the charged Higgs + self.doHistFunc('br_tHpb_%s'%era, f.Get(hd['br_tHpb']), pars) + self.doHistFunc('br_Hptaunu_%s'%era, f.Get(hd['br_Hptaunu']), pars) + self.PROC_SETS.append((['tt'], ['HptaunubHptaunub', 'HptaunubWb', 'WbWb'], [era])) + # And the extra term we need for H->hh + self.doHistFunc('br_Hhh_%s'%era, f.Get(hd['br_Hhh']), pars) + self.PROC_SETS.append((['ggH'], ['Hhhbbtautau'], [era])) + # And the extra term we need for A->Zh + self.doHistFunc('br_AZh_%s'%era, f.Get(hd['br_AZh']), pars) + self.PROC_SETS.append((['ggA'], ['AZhLLtautau'], [era])) + + # h_ggF_xsec_h_scale_hi = self.safeTH2DivideForKappa(f.Get(ggF_xsec_h_scale_hi_str), f.Get(ggF_xsec_h_str)) + # h_ggF_xsec_h_scale_lo = self.safeTH2DivideForKappa(f.Get(ggF_xsec_h_scale_lo_str), f.Get(ggF_xsec_h_str)) + # ggF_xsec_h_scale = self.doAsymPow('systeff_ggF_xsec_h_scale_8TeV', h_ggF_xsec_h_scale_lo, h_ggF_xsec_h_scale_hi, 'ggF_xsec_h_scale_8TeV', [mA, tanb]) + # ftest = ROOT.TFile('model_debug.root', 'RECREATE') + # ftest.WriteTObject(h_ggF_xsec_h_scale_hi, 'h_ggF_xsec_h_scale_hi') + # ftest.WriteTObject(h_ggF_xsec_h_scale_lo, 'h_ggF_xsec_h_scale_lo') + # ftest.Close() + + # def preProcessNuisances(self,nuisances): + # nuisances.append(("ggF_xsec_h_scale_8TeV",False, "param", [ "0", "1"], [] ) ) + + def doParametersOfInterest(self): + """Create POI and other parameters, and define the POI set.""" + self.modelBuilder.doVar("r[1,0,20]") + self.modelBuilder.doSet('POI', 'r') + # We don't intend on actually floating these in any fits... + self.modelBuilder.out.var('mA').setConstant(True) + self.modelBuilder.out.var('tanb').setConstant(True) + + # Build the intermediate terms for charged Higgs scaling + for E in self.modelFiles: + self.modelBuilder.doVar('xs_tt_%s[1.0]' % E) + self.modelBuilder.factory_('expr::br_HptaunubHptaunub_%s("@0*@0*@1*@1", br_tHpb_%s,br_Hptaunu_%s)' % (E,E,E)) + self.modelBuilder.factory_('expr::br_HptaunubWb_%s("2 * (1-@0)*@0*@1", br_tHpb_%s,br_Hptaunu_%s)' % (E,E,E)) + self.modelBuilder.factory_('expr::br_WbWb_%s("(1-@0)*(1-@0)", br_tHpb_%s)' % (E,E)) + + # Build the intermediate terms for H->hh->bbtautau and A->Zh->LLtautau scaling + for E in self.modelFiles: + self.modelBuilder.factory_('expr::br_Hhhbbtautau_%s("2*@0*@1*@2", br_Hhh_%s,br_htautau_%s,br_hbb_%s)' % (E,E,E,E)) + self.modelBuilder.factory_('expr::br_AZhLLtautau_%s("0.10099*@0*@1", br_AZh_%s,br_htautau_%s)' % (E,E,E)) + + for proc_set in self.PROC_SETS: + for (P, D, E) in itertools.product(*proc_set): + # print (P, D, E) + terms = ['xs_%s_%s' % (P, E), 'br_%s_%s'% (D, E)] + terms += ['r'] + self.modelBuilder.factory_('prod::scaling_%s_%s_%s(%s)' % (P,D,E,','.join(terms))) + self.modelBuilder.out.function('scaling_%s_%s_%s' % (P,D,E)).Print('') + + + def getHiggsProdDecMode(self, bin, process): + """Return a triple of (production, decay, energy)""" + P = '' + D = '' + if "_" in process: + (P, D) = process.split("_") + else: + raise RuntimeError, 'Expected signal process %s to be of the form PROD_DECAY' % process + E = None + for era in self.ERAS: + if era in bin: + if E: raise RuntimeError, "Validation Error: bin string %s contains multiple known energies" % bin + E = era + if not E: + raise RuntimeError, 'Did not find a valid energy in bin string %s' % bin + return (P, D, E) + + def getYieldScale(self,bin,process): + if self.DC.isSignal[process]: + (P, D, E) = self.getHiggsProdDecMode(bin, process) + scaling = 'scaling_%s_%s_%s' % (P, D, E) + print 'Scaling %s/%s as %s' % (bin, process, scaling) + return scaling + else: + return 1 + + +MSSM = MSSMHiggsModel() + diff --git a/CombinePdfs/python/ModelIndependent.py b/CombinePdfs/python/ModelIndependent.py new file mode 100644 index 00000000000..98ec6df5825 --- /dev/null +++ b/CombinePdfs/python/ModelIndependent.py @@ -0,0 +1,257 @@ +from HiggsAnalysis.CombinedLimit.PhysicsModel import * +import os +import ROOT +import math +import itertools + +class BRChargedHiggs(PhysicsModel): + def __init__(self): + PhysicsModel.__init__(self) + + def doParametersOfInterest(self): + """Create POI and other parameters, and define the POI set.""" + self.modelBuilder.doVar('BR[0,0,1]'); + + self.modelBuilder.doSet('POI','BR') + + self.modelBuilder.factory_('expr::Scaling_HH("@0*@0", BR)') + self.modelBuilder.factory_('expr::Scaling_WH("2 * (1-@0)*@0", BR)') + #self.modelBuilder.factory_('expr::Scaling_tt("(1-@0)*(1-@0)", BR)') + self.modelBuilder.factory_('expr::Scaling_tt("1 - (@0+@1)", Scaling_HH, Scaling_WH)') + + self.processScaling = { 'tt_ttHchHch':'HH', 'tt_ttHchW':'WH', 'tt':'tt' } + + # self.modelBuilder.out.Print() + + def getYieldScale(self,bin,process): + for prefix, model in self.processScaling.iteritems(): + if process == prefix: + print 'Scaling %s/%s as %s' % (bin, process, 'Scaling_'+model) + return 'Scaling_'+model + return 1 + +class MSSMLikeHiggsModel(PhysicsModel): + """ + Base class for all further derivatives. At the moment this allows for ggH and bbA for production and hbb, htt, hmm for + decay. Supported run periods are 7Tev and 8TeV. The decay channels and run period has to be part of the datacards name. + The production channel is taken from the samples name following the CMSHCG convention for production channels. + """ + #def getHiggsSignalYieldScale(self, production, decay, energy): + # raise RuntimeError, "Not implemented" + def getYieldScale(self,bin,process): + """ + Split in production and decay channels. Call getHiggsSignalYieldScale. Return 1 for backgrounds. + """ + if not self.DC.isSignal[process]: return 1 + processSource = process + decaySource = self.options.fileName+":"+bin # by default, decay comes from the datacard name or bin label + if "_" in process: (processSource, decaySource) = process.split("_") + if processSource not in ["ggh", "bbh", "ggA", "bbA", "ggH", "bbH"]: + raise RuntimeError, "Validation Error: signal process %s not among the allowed ones." % processSource + foundDecay = None + for D in [ "htautau", "Atautau", "Htautau", "hbb", "htt", "hmm" ]: + if D in decaySource: + if foundDecay: raise RuntimeError, "Validation Error: decay string %s contains multiple known decay names" % decaySource + foundDecay = D + if not foundDecay: raise RuntimeError, "Validation Error: decay string %s does not contain any known decay name" % decaySource + foundEnergy = None + for D in [ "7TeV", "8TeV" ]: + if D in decaySource: + if foundEnergy: raise RuntimeError, "Validation Error: decay string %s contains multiple known energies" % decaySource + foundEnergy = D + if not foundEnergy: + foundEnergy = "7TeV" ## To ensure backward compatibility + print "Warning: decay string %s does not contain any known energy, assuming %s" % (decaySource, foundEnergy) + return self.getHiggsSignalYieldScale(processSource, foundDecay, foundEnergy) + +class FloatingMSSMXSHiggs(MSSMLikeHiggsModel): + """ + Trivial model to float ggA and bbA independently. At the moment only ggA and bbh are supported. Extensions to the other + production channels channels are given in comments. Also the principle how to deal with manipulations of the POI's and + other defined roofit variables are shown in comments. + """ + def __init__(self): + MSSMLikeHiggsModel.__init__(self) # not using 'super(x,self).__init__' since I don't understand it + self.modes = [ "ggA", "bbA" ] + self.mARange = [] + self.ggARange = ['0','20'] + self.bbARange = ['0','20'] + def setPhysicsOptions(self,physOptions): + """ + Options are: modes. Examples for options like mARange are given in comments. + """ + for po in physOptions: + if po.startswith("modes="): self.modes = po.replace("modes=","").split(",") + if po.startswith("mARange="): + self.mARange = po.replace("mARange=","").split(":") + if len(self.mARange) != 2: + raise RuntimeError, "Definition of mA range requires two extrema, separated by ':'" + elif float(self.mARange[0]) >= float(self.mARange[1]): + raise RuntimeError, "Extrema for mA range defined with inverterd order. Second element must be larger than first element" + if po.startswith("ggARange="): + self.ggARange = po.replace("ggARange=","").split(":") + if len(self.ggARange) != 2: + raise RuntimeError, "ggA signal strength range requires minimal and maximal value" + elif float(self.ggARange[0]) >= float(self.ggARange[1]): + raise RuntimeError, "minimal and maximal range swapped. Second value must be larger first one" + if po.startswith("bbARange="): + self.bbARange = po.replace("bbARange=","").split(":") + if len(self.bbARange) != 2: + raise RuntimeError, "bbA signal strength range requires minimal and maximal value" + elif float(self.bbARange[0]) >= float(self.bbARange[1]): + raise RuntimeError, "minimal and maximal range swapped. Second value must be larger first one" + def doParametersOfInterest(self): + """ + Create POI and other parameters, and define the POI set. E.g. Evaluate cross section for given values of mA and tanb + """ + # Define signal strengths on ggA and bbA as POI, NOTE: the range of the POIs is defined here + self.modelBuilder.doVar("r_ggA[%s,%s,%s]" % (str((float(self.ggARange[0])+float(self.ggARange[1]))/2.), self.ggARange[0], self.ggARange[1])); + self.modelBuilder.doVar("r_bbA[%s,%s,%s]" % (str((float(self.bbARange[0])+float(self.bbARange[1]))/2.), self.bbARange[0], self.bbARange[1])); + self.modelBuilder.doVar("r_ggh[0]" ); + self.modelBuilder.doVar("r_bbh[0]" ); + self.modelBuilder.doVar("r_ggH[0]" ); + self.modelBuilder.doVar("r_bbH[0]" ); + poi = ",".join(["r_"+m for m in self.modes]) + ## Define Higgs boson mass as another parameter. It will be floating if mARange is set otherwise it will be treated + ## as fixed. NOTE: this is only left here as an extended example. It's not useful to have mA floating at the moment. + if self.modelBuilder.out.var("mA"): + if len(self.mARange): + print 'mA will be left floating within', self.mARange[0], 'and', self.mARange[1] + self.modelBuilder.out.var("mA").setRange(float(self.mARange[0]),float(self.mARange[1])) + self.modelBuilder.out.var("mA").setConstant(False) + poi+=',mA' + else: + print 'mA will be assumed to be', self.options.mass + self.modelBuilder.out.var("mA").removeRange() + self.modelBuilder.out.var("mA").setVal(self.options.mass) + else: + if len(self.mARange): + print 'mA will be left floating within', self.mARange[0], 'and', self.mARange[1] + self.modelBuilder.doVar("mA[%s,%s]" % (self.mARange[0],self.mARange[1])) + poi+=',mA' + else: + print 'mA (not there before) will be assumed to be', self.options.mass + self.modelBuilder.doVar("mA[%g]" % self.options.mass) + if self.modelBuilder.out.var("mh"): + self.modelBuilder.out.var("mh").removeRange() + self.modelBuilder.out.var("mh").setVal(340) + if self.modelBuilder.out.var("mH"): + self.modelBuilder.out.var("mH").removeRange() + self.modelBuilder.out.var("mH").setVal(340) + ## define set of POIs + self.modelBuilder.doSet("POI",poi) + def getHiggsSignalYieldScale(self,production,decay, energy): + if production == "ggh" or production == "bbh" or production == "ggA" or production == "bbA" or production == "ggH" or production == "bbH" : + ## This is the trivial model that we follow now. We just pass on the values themselves. Yes this is just a + ## trivial renaming, but we leave it in as an example. Instead also r_ggA and r_bbA could be passed on directly + ## in the return function instead of the newly defined variables ggA_yields or bbA_yield. + self.modelBuilder.factory_('expr::%s_yield("@0", r_%s)' % (production, production)) + return "%s_yield" % production + raise RuntimeError, "Unknown production mode '%s'" % production + +class FloatingMSSMXSHiggs2(MSSMLikeHiggsModel): + """ + Trivial model to float ggH and bbH independently. At the moment only ggH and bbh are supported. Extensions to the other + production channels channels are given in comments. Also the principle how to deal with manipulations of the POI's and + other defined roofit variables are shown in comments. + """ + def __init__(self): + MSSMLikeHiggsModel.__init__(self) # not using 'super(x,self).__init__' since I don't understand it + #self.tanb = None + self.modes = [ "ggH", "bbH" ] + self.mHRange = [] + self.ggHRange = ['0','20'] + self.bbHRange = ['0','20'] + def setPhysicsOptions(self,physOptions): + """ + Options are: modes. Examples for options like mARange and tanb are given in comments. + """ + for po in physOptions: + #if po.startswith("tanb="): self.tanb = po.replace("tanb=", "") + if po.startswith("modes="): self.modes = po.replace("modes=","").split(",") + #if po.startswith("mARange="): + # self.mARange = po.replace("mARange=","").split(":") + # if len(self.mARange) != 2: + # raise RuntimeError, "Definition of mA range requires two extrema, separated by ':'" + # elif float(self.mARange[0]) >= float(self.mARange[1]): + # raise RuntimeError, "Extrema for mA range defined with inverterd order. Second element must be larger than first element" + if po.startswith("ggHRange="): + self.ggHRange = po.replace("ggHRange=","").split(":") + if len(self.ggHRange) != 2: + raise RuntimeError, "ggH signal strength range requires minimal and maximal value" + elif float(self.ggHRange[0]) >= float(self.ggHRange[1]): + raise RuntimeError, "minimal and maximal range swapped. Second value must be larger first one" + if po.startswith("bbHRange="): + self.bbHRange = po.replace("bbHRange=","").split(":") + if len(self.bbHRange) != 2: + raise RuntimeError, "bbH signal strength range requires minimal and maximal value" + elif float(self.bbHRange[0]) >= float(self.bbHRange[1]): + raise RuntimeError, "minimal and maximal range swapped. Second value must be larger first one" + def doParametersOfInterest(self): + """ + Create POI and other parameters, and define the POI set. E.g. Evaluate cross section for given values of mA and tanb + """ + ## Example: evaluate cross sections for given values of tanb and mA, the names of the variables are ggH_xsec and bbH_xsec, + ## the values are given by the value in brakets. In principle three values can be passed on: value, lower bound and upper + ## bound. + # + #mssm_scan = mssm_xsec_tools("{CMSSW_BASE}/src/{PATH}".format(CMSSW_BASE=os.environ['CMSSW_BASE'], PATH="HiggsAnalysis/HiggsToTauTau/data/out.mhmax-mu+200-7TeV-nnlo.root")) + #mssm_xsec = mssm_scan.query(self.options.mass, float(self.tanb)) + #self.modelBuilder.doVar("bbH_xsec[%g]" % (mssm_xsec['higgses']['A']['xsec']['santander']*mssm_xsec['higgses']['A']['BR'])) + #self.modelBuilder.doVar("ggH_xsec[%g]" % (mssm_xsec['higgses']['A']['xsec']['ggF' ]*mssm_xsec['higgses']['A']['BR'])) + # + ## Define signal strengths on ggH and bbH as POI, NOTE: the range of the POIs is defined here + self.modelBuilder.doVar("r_ggH[%s,%s,%s]" % (str((float(self.ggHRange[0])+float(self.ggHRange[1]))/2.), self.ggHRange[0], self.ggHRange[1])); + self.modelBuilder.doVar("r_bbH[%s,%s,%s]" % (str((float(self.bbHRange[0])+float(self.bbHRange[1]))/2.), self.bbHRange[0], self.bbHRange[1])); + poi = ",".join(["r_"+m for m in self.modes]) + ## Define Higgs boson mass as another parameter. It will be floating if mARange is set otherwise it will be treated + ## as fixed. NOTE: this is only left here as an extended example. It's not useful to have mA floating at the moment. + if self.modelBuilder.out.var("MH"): + if len(self.mHRange): + print 'MH will be left floating within', self.mHRange[0], 'and', self.mHRange[1] + self.modelBuilder.out.var("MH").setRange(float(self.mHRange[0]),float(self.mHRange[1])) + self.modelBuilder.out.var("MH").setConstant(False) + poi+=',MH' + else: + print 'MH will be assumed to be', self.options.mass + self.modelBuilder.out.var("MH").removeRange() + self.modelBuilder.out.var("MH").setVal(self.options.mass) + else: + if len(self.mHRange): + print 'MH will be left floating within', self.mHRange[0], 'and', self.mHRange[1] + self.modelBuilder.doVar("MH[%s,%s]" % (self.mHRange[0],self.mHRange[1])) + poi+=',MH' + else: + print 'MH (not there before) will be assumed to be', self.options.mass + self.modelBuilder.doVar("MH[%g]" % self.options.mass) + ## define set of POIs + self.modelBuilder.doSet("POI",poi) + def getHiggsSignalYieldScale(self,production,decay, energy): + if production == "ggH" or production == "bbH": + ## This is an example how to multiply the yields r_ggH and r_bbH with the roofit variables ggH_xsec and bbH_xsec + ## that have been defined above, with the help of a roofit expression. + # + #self.modelBuilder.factory_('expr::%s_yield("@0*@1", r_%s, %s_xsec)' % (production, production, production)) + # + ## This is the trivial model that we follow now. We just pass on the values themselves. Yes this is just a + ## trivial renaming, but we leave it in as an example. Instead also r_ggH and r_bbH could be passed on directly + ## in the return function instead of the newly defined variables ggH_yields or bbH_yield. + self.modelBuilder.factory_('expr::%s_yield("@0", r_%s)' % (production, production)) + return "%s_yield" % production + ## This just corresponds to entry points to extend the model to other production channels like qqH, ttH, VH. + # + #if production == "qqH": return ("r_qqH" if "qqH" in self.modes else 1) + #if production == "ttH": return ("r_ttH" if "ttH" in self.modes else 1) + #if production in [ "WH", "ZH", "VH" ]: return ("r_VH" if "VH" in self.modes else 1) + # + raise RuntimeError, "Unknown production mode '%s'" % production + + + + +#BR Charged Higgs +brChargedHiggs = BRChargedHiggs() + +#Model independent xs*BR limits +floatingMSSMXSHiggs = FloatingMSSMXSHiggs() #with A,H,h +floatingMSSMXSHiggs2 = FloatingMSSMXSHiggs2() #with A diff --git a/CombinePdfs/scripts/Hhh_asymptotic_grid.json b/CombinePdfs/scripts/Hhh_asymptotic_grid.json new file mode 100644 index 00000000000..84edaf38335 --- /dev/null +++ b/CombinePdfs/scripts/Hhh_asymptotic_grid.json @@ -0,0 +1,8 @@ +{ + "opts" : "--singlePoint 1.0 ", + "POIs" : ["mA", "tanb"], + "grids" : [ + ["150:500|5", "1.0:4.0|0.1",""] + ], + "hist_binning" : [70, 150, 500, 40, 1.0, 4.0] +} diff --git a/CombinePdfs/scripts/mssm_1Dlimit_grid.json b/CombinePdfs/scripts/mssm_1Dlimit_grid.json new file mode 100644 index 00000000000..cb279b93e4e --- /dev/null +++ b/CombinePdfs/scripts/mssm_1Dlimit_grid.json @@ -0,0 +1,8 @@ +{ + "opts" : "", + "POIs" : ["mA"], + "grids" : [ + ["90:1000|10"] + ], + "r" : ["r_ggA"] +} diff --git a/CombinePdfs/scripts/mssm_asymptotic_grid.json b/CombinePdfs/scripts/mssm_asymptotic_grid.json index 2906aa40be9..ec1528062a7 100644 --- a/CombinePdfs/scripts/mssm_asymptotic_grid.json +++ b/CombinePdfs/scripts/mssm_asymptotic_grid.json @@ -2,7 +2,8 @@ "opts" : "--singlePoint 1.0", "POIs" : ["mA", "tanb"], "grids" : [ - ["100:200|10", "1:60|5"] + ["130:160|30", "1:3|1", "0"], + ["130:160|30", "4:60|20", ""] ], - "hist_binning" : [10, 100, 200, 12, 1, 60] + "hist_binning" : [87, 130, 1000, 60, 1, 60] } diff --git a/CombinePdfs/scripts/mssm_hybrid_grid.json b/CombinePdfs/scripts/mssm_hybrid_grid.json index 4e7c018fe76..24f664f5895 100644 --- a/CombinePdfs/scripts/mssm_hybrid_grid.json +++ b/CombinePdfs/scripts/mssm_hybrid_grid.json @@ -1,8 +1,8 @@ { - "opts" : "--testStat=LHC --frequentist -d htt_mt_mssm.root --singlePoint 1.0 --saveHybridResult --clsAcc 0 --fullBToys --fork 2", + "opts" : "--testStat=TEV --frequentist -d htt_mt_mssm.root --singlePoint 1.0 --saveHybridResult --clsAcc 0 --fullBToys --fork 0", "POIs" : ["mA", "tanb"], "grids" : [ - ["500", "1:60|2"] + ["500", "25", ""] ], - "toys_per_cycle" : 5000 -} \ No newline at end of file + "toys_per_cycle" : 500 +} diff --git a/CombinePdfs/scripts/testBinning.py b/CombinePdfs/scripts/testBinning.py new file mode 100755 index 00000000000..9474a5b09f5 --- /dev/null +++ b/CombinePdfs/scripts/testBinning.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +import sys +import ROOT +import math +from functools import partial +import Tools.Plotting.plotting as plot +import os.path +import bisect + +ROOT.PyConfig.IgnoreCommandLineOptions = True +ROOT.gROOT.SetBatch(ROOT.kTRUE) + +h1 = ROOT.TH1F('h1', '', 100, 0, 10) +for i in xrange(1, 101): + h1.SetBinContent(i, float(i)) +h2 = ROOT.TH1F('h2', '', 10, 0, 10) +for i in xrange(1, 11): + h2.SetBinContent(i, float(i)) + +h1.Print('range') +h2.Print('range') + +r1 = ROOT.RooRealVar('r1', 'r1', 0) + +rdh1 = ROOT.RooDataHist('rdh1', 'rdh1', ROOT.RooArgList(r1), ROOT.RooFit.Import(h1)) +rhf1 = ROOT.RooHistFunc('rhf1', 'rhf1', ROOT.RooArgSet(r1), rdh1) + +wsp = ROOT.RooWorkspace('w', 'w') +getattr(wsp, 'import')(rhf1, ROOT.RooFit.RecycleConflictNodes()) + + +rdh2 = ROOT.RooDataHist('rdh2', 'rdh2', ROOT.RooArgList(r1), ROOT.RooFit.Import(h2)) +rhf2 = ROOT.RooHistFunc('rhf2', 'rhf2', ROOT.RooArgSet(r1), rdh2) + +getattr(wsp, 'import')(rhf2, ROOT.RooFit.RecycleConflictNodes()) + +# r1.Print() + +r1 = wsp.var('r1') +rhf1 = wsp.function('rhf1') +rhf2 = wsp.function('rhf2') + +r1.setBins(10) + +x = 0.05 +while x <= 10.: + r1.setVal(x) + print 'r1 = %f; rhf1 = %f; rhf2 = %f' % (x, rhf1.getVal(), rhf2.getVal()) + x += 0.1 diff --git a/CombinePdfs/src/MorphFunctions.cc b/CombinePdfs/src/MorphFunctions.cc index f3bf9122d6c..5487652224e 100644 --- a/CombinePdfs/src/MorphFunctions.cc +++ b/CombinePdfs/src/MorphFunctions.cc @@ -18,7 +18,7 @@ namespace ch { void BuildRooMorphing(RooWorkspace& ws, CombineHarvester& cb, std::string const& bin, std::string const& process, RooAbsReal& mass_var, std::string norm_postfix, - bool allow_morph, bool verbose, TFile * file) { + bool allow_morph, bool verbose, bool force_template_limit, TFile * file) { // To keep the code concise we'll make some using-declarations here using std::set; using std::vector; @@ -72,6 +72,16 @@ void BuildRooMorphing(RooWorkspace& ws, CombineHarvester& cb, // Make a list of the names of shape systematics affecting this process vector ss_vec = Set2Vec(cb_bp.cp().syst_type({"shape"}).syst_name_set()); + // Now check if all shape systematics are present for all mass points + for (auto const& s : m_str_vec) { + if (cb_bp.cp().syst_type({"shape"}).mass({s}).syst_name_set().size() != + ss_vec.size()) { + throw std::runtime_error(FNERROR( + "Some mass points do not have the full set of shape systematics, " + "this is currently unsupported")); + } + } + unsigned ss = ss_vec.size(); // number of shape systematics // ls = "lnN systematic" @@ -366,9 +376,40 @@ void BuildRooMorphing(RooWorkspace& ws, CombineHarvester& cb, // Can do more sophistical spline interpolation if we want, but let's use // simple LINEAR interpolation for now TString interp = "LINEAR"; + + // Here when the force_template_limit option is requested + // we have to add some extra terms to the vector of masses to ensure that + // the signal pdf goes to 0 outside of the MC template range. + + vector new_m_vec(m_vec); + // Insert an entry at either end of the vector for a mass just slightly + // outside of the range + new_m_vec.insert(new_m_vec.begin(),m_vec[0]-1E-6); + new_m_vec.push_back(m_vec[m-1]+1E-6); + // Create a corresponding rate array with 0 entries for these new masses + multi_array new_rate_arr(extents[m+2]); + new_rate_arr[0] = 0.0; + for(unsigned i = 0; i < m; ++i) new_rate_arr[i+1] = rate_arr[i] ; + new_rate_arr[m+1] = 0.0; + + if (verbose && force_template_limit) { + std::cout << ">>>> Forcing rate to 0 outside of template range:" << "\n"; + for(unsigned mi = 0; mi < m+2; ++mi) { + std::cout << boost::format("%-10.5g") % new_m_vec[mi]; + } + std::cout << "\n"; + for(unsigned mi = 0; mi < m+2; ++mi) { + std::cout << boost::format("%-10.5g") % new_rate_arr[mi]; + } + std::cout << "\n"; + } // Create the 1D spline directly from the rate array - RooSpline1D rate_spline("interp_rate_"+key, "", mass_var, m, m_vec.data(), - rate_arr.data(), interp); + RooSpline1D rate_spline("interp_rate_"+key, "", mass_var, + force_template_limit ? m+2 : m, + force_template_limit ? new_m_vec.data() : m_vec.data(), + force_template_limit ? new_rate_arr.data() : rate_arr.data(), + interp); + if (file) { TGraph tmp(m, m_vec.data(), rate_arr.data()); gDirectory->WriteTObject(&tmp, "interp_rate_"+key); diff --git a/CombineTools/bin/AZhExample.cpp b/CombineTools/bin/AZhExample.cpp new file mode 100644 index 00000000000..3347d6a4a41 --- /dev/null +++ b/CombineTools/bin/AZhExample.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include +#include +#include "boost/filesystem.hpp" +#include "CombineHarvester/CombineTools/interface/CombineHarvester.h" +#include "CombineHarvester/CombineTools/interface/Utilities.h" +#include "CombineHarvester/CombineTools/interface/HttSystematics.h" +#include "CombineHarvester/CombineTools/interface/BinByBin.h" + +using namespace std; + +int main() { + ch::CombineHarvester cb; + + // cb.SetVerbosity(1); + + typedef vector> Categories; + typedef vector VString; + + string auxiliaries = string(getenv("CMSSW_BASE")) + "/src/auxiliaries/"; + string aux_shapes = auxiliaries +"shapes/"; + string aux_pruning = auxiliaries +"pruning/"; + + VString chns = + {"et", "mt","tt", "em"}; + + string input_folders = "ULB"; + + map bkg_procs; + bkg_procs["et"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + bkg_procs["mt"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + bkg_procs["em"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + bkg_procs["tt"] = {"ZZ","GGToZZ2L2L","TTZ","WWZ","ZZZ","WZZ","Zjets"}; + + map sm_procs; + sm_procs["et"] = {"ZH_ww125","ZH_tt125"}; + + + VString sig_procs={"AZh"}; + + map cats; + cats["et_8TeV"] = { + {0, "eeet_zh"}, {1, "mmet_zh"}}; + cats["mt_8TeV"] = { + {0, "eemt_zh"}, {1, "mmmt_zh"}}; + cats["em_8TeV"] = { + {0, "eeem_zh"}, {1, "mmme_zh"}}; + cats["tt_8TeV"] = { + {0, "eett_zh"}, {1, "mmtt_zh"}}; + + vector masses = ch::MassesFromRange("220-350:10"); + + cout << ">> Creating processes and observations...\n"; + for (string era : {"8TeV"}) { + for (auto chn : chns) { + cb.AddObservations( + {"*"}, {"htt"}, {era}, {chn}, cats[chn+"_"+era]); + cb.AddProcesses( + {"*"}, {"htt"}, {era}, {chn}, bkg_procs[chn], cats[chn+"_"+era], false); + // cb.AddProcesses( + // {"*"},{"htt"}, {era}, {chn}, sm_procs[chn], cats[chn+"_"+era],false); + cb.AddProcesses( + masses, {"htt"}, {era}, {chn}, sig_procs, cats[chn+"_"+era], true); + } + } + + + cout << ">> Adding systematic uncertainties...\n"; + ch::AddSystematics_AZh(cb); + + cout << ">> Extracting histograms from input root files...\n"; + for (string era : {"8TeV"}) { + for (string chn : chns) { + string file = aux_shapes + input_folders + "/htt_AZh" + + ".inputs-AZh-" + era + ".root"; + cb.cp().channel({chn}).era({era}).backgrounds().ExtractShapes( + file, "$BIN/$PROCESS", "$BIN/$PROCESS_$SYSTEMATIC"); + cb.cp().channel({chn}).era({era}).signals().ExtractShapes( + file, "$BIN/$PROCESS$MASS", "$BIN/$PROCESS$MASS_$SYSTEMATIC"); + } + } + + + cout << ">> Generating bbb uncertainties...\n"; + auto bbb = ch::BinByBinFactory() + .SetAddThreshold(0.1) + .SetFixNorm(true); + + bbb.AddBinByBin(cb.cp().process({"Zjets"}), cb); + + + cout << ">> Setting standardised bin names...\n"; + ch::SetStandardBinNames(cb); + + string folder = "output/AZh_cards"; + boost::filesystem::create_directories(folder); + for (string chn :chns){ + boost::filesystem::create_directories(folder+"/"+chn); + for (auto m:masses){ + boost::filesystem::create_directories(folder+"/"+chn+"/"+m); + boost::filesystem::create_directories(folder+"/cmb/"+m); + } + } + + + for (string chn : chns) { + TFile output((folder+ "/htt_" + chn + ".input.root").c_str(), + "RECREATE"); + auto bins = cb.cp().channel({chn}).bin_set(); + + for (auto b : bins) { + for (auto m : masses) { + cout << ">> Writing datacard for bin: " << b << " and mass: " << m + << "\r" << flush; + cb.cp().channel({chn}).bin({b}).mass({m, "*"}).WriteDatacard( + folder+"/"+chn+"/"+m+"/"+b + ".txt", output); + cb.cp().channel({chn}).bin({b}).mass({m, "*"}).WriteDatacard( + folder+"/cmb/"+m+"/"+b + ".txt", output); + } + } + output.Close(); + } + + for (string chn:chns) { + for (auto m:masses) { + boost::filesystem::copy_file((folder+"/htt_"+chn+".input.root").c_str(),(folder+"/"+chn+"/"+m+"/htt_"+chn+".input.root").c_str(),boost::filesystem::copy_option::overwrite_if_exists); + } + } + + cout << "\n>> Done!\n"; +} diff --git a/CombineTools/bin/BuildFile.xml b/CombineTools/bin/BuildFile.xml index ce7c95ddea9..55c54525124 100644 --- a/CombineTools/bin/BuildFile.xml +++ b/CombineTools/bin/BuildFile.xml @@ -2,9 +2,11 @@ + + diff --git a/CombineTools/bin/MSSMUpdate.cpp b/CombineTools/bin/MSSMUpdate.cpp new file mode 100644 index 00000000000..8f7642fd94e --- /dev/null +++ b/CombineTools/bin/MSSMUpdate.cpp @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include +#include +#include +#include "boost/filesystem.hpp" +#include "CombineHarvester/CombineTools/interface/CombineHarvester.h" +#include "CombineHarvester/CombineTools/interface/Utilities.h" +#include "CombineHarvester/CombineTools/interface/HttSystematics.h" +#include "CombineHarvester/CombineTools/interface/CardWriter.h" +#include "CombineHarvester/CombineTools/interface/CopyTools.h" +#include "CombineHarvester/CombineTools/interface/BinByBin.h" + +using namespace std; + +int main(int argc, char** argv) { + ch::CombineHarvester cb; + + typedef vector> Categories; + typedef vector VString; + + + string SM125 = ""; + if(argc>1) SM125 = string(argv[1]); + string auxiliaries = string(getenv("CMSSW_BASE")) + "/src/auxiliaries/"; + string aux_shapes = auxiliaries +"shapes/"; + string aux_pruning = auxiliaries +"pruning/"; + string input_dir = string(getenv("CMSSW_BASE")) + "/src/CombineHarvester/CombineTools/input"; + + VString chns = + {"mt", "et", "tt", "em", "mm"}; + + map input_folders = { + {"mt", "LLR"}, + {"et", "LLR"}, + {"tt", "LLR"}, + {"em", "MIT"}, + {"mm", "DESY-KIT"}, + }; + + map bkg_procs; + bkg_procs["mt"] = {"ZTT", "QCD", "W", "ZJ", "ZL", "TT", "VV"}; + bkg_procs["et"] = {"ZTT", "QCD", "W", "ZJ", "ZL", "TT", "VV"}; + bkg_procs["tt"] = {"ZTT", "QCD", "W", "ZJ", "ZL", "TT", "VV"}; + bkg_procs["em"] = {"Ztt", "ttbar", "EWK", "Fakes"}; + bkg_procs["mm"] = {"ZTT", "ZMM", "QCD", "TTJ", "WJets", "Dibosons"}; + + VString sig_procs = {"ggH", "bbH"}; + VString SM_procs = {"ggH_SM125", "qqH_SM125", "VH_SM125"}; + + map cats; + cats["mt_8TeV"] = { + {10, "muTau_nobtag_low"}, {11, "muTau_nobtag_medium"}, {12, "muTau_nobtag_high"}, {13, "muTau_btag_low"}, {14, "muTau_btag_high"}}; + cats["et_8TeV"] = { + {10, "eleTau_nobtag_low"}, {11, "eleTau_nobtag_medium"}, {12, "eleTau_nobtag_high"}, {13, "eleTau_btag_low"}, {14, "eleTau_btag_high"}}; + cats["tt_8TeV"] = { + {10, "tauTau_nobtag_low"}, {11, "tauTau_nobtag_medium"}, {12, "tauTau_nobtag_high"}, {13, "tauTau_btag_low"}, {14, "tauTau_btag_high"}}; + cats["em_8TeV"] = { + {8, "emu_nobtag"}, {9, "emu_btag"}}; + cats["mm_8TeV"] = { + {8, "mumu_nobtag"}, {9, "mumu_btag"}}; + + auto masses = ch::MassesFromRange( + "90,100,120-140:10,140-200:20,200-500:50,600-1000:100"); + + for (auto chn : chns) { + cb.AddObservations( + {"*"}, {"htt"}, {"8TeV"}, {chn}, cats[chn+"_8TeV"]); + cb.AddProcesses( + {"*"}, {"htt"}, {"8TeV"}, {chn}, bkg_procs[chn], cats[chn+"_8TeV"], false); + cb.AddProcesses( + masses, {"htt"}, {"8TeV"}, {chn}, sig_procs, cats[chn+"_8TeV"], true); + if(SM125==string("signal_SM125")) cb.AddProcesses({"*"}, {"htt"}, {"8TeV"}, {chn}, SM_procs, cats[chn+"_8TeV"], true); + else if(SM125==string("bkg_SM125")) cb.AddProcesses({"*"}, {"htt"}, {"8TeV"}, {chn}, SM_procs, cats[chn+"_8TeV"], false); + } + + ch::AddMSSMUpdateSystematics_et_mt(cb); + ch::AddMSSMUpdateSystematics_em(cb); + ch::AddMSSMUpdateSystematics_mm(cb); + ch::AddMSSMUpdateSystematics_tt(cb); + + cout << ">> Extracting histograms from input root files...\n"; + for (string chn : chns) { + string file = aux_shapes + input_folders[chn] + "/htt_" + chn + + ".inputs-mssm-" + "8TeV" + "-0.root"; + cb.cp().channel({chn}).era({"8TeV"}).backgrounds().ExtractShapes( + file, "$BIN/$PROCESS", "$BIN/$PROCESS_$SYSTEMATIC"); + cb.cp().channel({chn}).era({"8TeV"}).process(sig_procs).ExtractShapes( + file, "$BIN/$PROCESS$MASS", "$BIN/$PROCESS$MASS_$SYSTEMATIC"); + if(SM125==string("signal_SM125")) cb.cp().channel({chn}).era({"8TeV"}).process(SM_procs).ExtractShapes(file, "$BIN/$PROCESS", "$BIN/$PROCESS_$SYSTEMATIC"); + } + + map signal_types = { + //{"ggH", {"ggh", "ggH", "ggA"}}, + //{"bbH", {"bbh", "bbH", "bbA"}} + {"ggH", {"ggH"}}, + {"bbH", {"bbH"}} //SM_procs hier dazu? + }; + cout << "Scaling signal process rates for acceptance...\n"; + for (string e : {"8TeV"}) { + for (string p : sig_procs) { //SM_procs hier dazu? + cout << "Scaling for process " << p << " and era " << e << "\n"; + auto gr = ch::TGraphFromTable( + input_dir + "/xsecs_brs/mssm_" + p + "_" + e + "_accept.txt", "mPhi", + "accept"); + cb.cp().process(signal_types[p]).era({e}).ForEachProc([&](ch::Process *proc) { + double m = boost::lexical_cast(proc->mass()); + proc->set_rate(proc->rate() * gr.Eval(m)); + }); + } + } + + cout << ">> Merging bin errors and generating bbb uncertainties...\n"; + auto bbb = ch::BinByBinFactory() + .SetAddThreshold(0.05) + .SetFixNorm(true); + bbb.AddBinByBin(cb.cp().process({"ZTT", "QCD", "W", "ZJ", "ZL", "TT", "VV", "Ztt", "ttbar", "EWK", "Fakes", "ZMM", "TTJ", "WJets", "Dibosons"}), cb); + + cout << ">> Setting standardised bin names...\n"; + ch::SetStandardBinNames(cb); + VString droplist = ch::ParseFileLines( + aux_pruning + "uncertainty-pruning-drop-150602-mssm-taupt-CH.txt"); + cout << ">> Droplist contains " << droplist.size() << " entries\n"; + + set to_drop; + for (auto x : droplist) to_drop.insert(x); + auto pre_drop = cb.syst_name_set(); + cb.syst_name(droplist, false); + auto post_drop = cb.syst_name_set(); + cout << ">> Systematics dropped: " << pre_drop.size() - post_drop.size() + << "\n"; + + string folder = "output/mssm_cards/LIMITS"; + boost::filesystem::create_directories(folder); + boost::filesystem::create_directories(folder + "/common"); + for (auto m : masses) { + boost::filesystem::create_directories(folder + "/" + m); + } + + for (string chn : chns) { + TFile output((folder + "/common/htt_" + chn + ".input.root").c_str(), + "RECREATE"); + auto bins = cb.cp().channel({chn}).bin_set(); + for (auto b : bins) { + for (auto m : masses) { + cout << ">> Writing datacard for bin: " << b << " and mass: " << m + << "\r" << flush; + cb.cp().channel({chn}).bin({b}).mass({m, "*"}).WriteDatacard( + folder + "/" + m + "/" + b + ".txt", output); + } + } + output.Close(); + } + + + cout << "\n>> Done!\n"; +} diff --git a/CombineTools/interface/HttSystematics.h b/CombineTools/interface/HttSystematics.h index 7bbaf01261c..37c1301bee8 100644 --- a/CombineTools/interface/HttSystematics.h +++ b/CombineTools/interface/HttSystematics.h @@ -12,18 +12,32 @@ void AddSystematics_em(CombineHarvester& cb); void AddSystematics_ee_mm(CombineHarvester& cb); void AddSystematics_tt(CombineHarvester& cb); - // Legacy MSSM analysis systematics // Implemented in src/HttSystematics_MSSMLegacy.cc void AddMSSMSystematics(CombineHarvester& cb, CombineHarvester src); void AddMSSMSystematics(CombineHarvester& cb); +// Update MSSM analysis systematics +// Implemented in src/HttSystematics_MSSMUpdate.cc +void AddMSSMUpdateSystematics_et_mt(CombineHarvester& cb, CombineHarvester src); +void AddMSSMUpdateSystematics_et_mt(CombineHarvester& cb); +void AddMSSMUpdateSystematics_em(CombineHarvester& cb, CombineHarvester src); +void AddMSSMUpdateSystematics_em(CombineHarvester& cb); +void AddMSSMUpdateSystematics_mm(CombineHarvester& cb, CombineHarvester src); +void AddMSSMUpdateSystematics_mm(CombineHarvester& cb); +void AddMSSMUpdateSystematics_tt(CombineHarvester& cb, CombineHarvester src); +void AddMSSMUpdateSystematics_tt(CombineHarvester& cb); + // Hhh systematics // Implemented in src/HhhSystematics.cc void AddSystematics_hhh_et_mt(CombineHarvester& cb, CombineHarvester src); void AddSystematics_hhh_et_mt(CombineHarvester& cb); void AddSystematics_hhh_tt(CombineHarvester& cb, CombineHarvester src); void AddSystematics_hhh_tt(CombineHarvester& cb); +// AZh systematics +// Implemented in src/AZhSystematics.cc +void AddSystematics_AZh(CombineHarvester& cb, CombineHarvester src); +void AddSystematics_AZh(CombineHarvester& cb); } #endif diff --git a/CombineTools/interface/Utilities.h b/CombineTools/interface/Utilities.h index c19c554f576..d768cb3b0ca 100644 --- a/CombineTools/interface/Utilities.h +++ b/CombineTools/interface/Utilities.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "boost/algorithm/string.hpp" #include "boost/lexical_cast.hpp" #include "boost/regex.hpp" @@ -113,6 +114,9 @@ std::vector> GenerateCombinations( std::vector ParseFileLines(std::string const& file_name); + +bool is_float(std::string const& str); + /** * Generate a vector of mass values using ranges and intervals specified in a * string diff --git a/CombineTools/python/combine/CombineToolBase.py b/CombineTools/python/combine/CombineToolBase.py index 5b7f4f2c502..106b68f084e 100755 --- a/CombineTools/python/combine/CombineToolBase.py +++ b/CombineTools/python/combine/CombineToolBase.py @@ -17,6 +17,30 @@ 'PWD': os.environ['PWD'] }) +JOB_NAF_PREFIX = """#!/bin/sh +ulimit -s unlimited +cd %(CMSSW_BASE)s/src +linux_ver=`lsb_release -s -r` +echo $linux_ver +if [[ $linux_ver < 6.0 ]]; +then + eval "`/afs/desy.de/common/etc/local/ini/ini.pl cmssw_cvmfs`" + export SCRAM_ARCH=slc5_amd64_gcc472 + export SCRAM_ARCH=%(SCRAM_ARCH)s +else + source /cvmfs/cms.cern.ch/cmsset_default.sh + export SCRAM_ARCH=slc6_amd64_gcc472 + export SCRAM_ARCH=%(SCRAM_ARCH)s +fi + +eval `scramv1 runtime -sh` +cd %(PWD)s +""" % ({ + 'CMSSW_BASE': os.environ['CMSSW_BASE'], + 'SCRAM_ARCH': os.environ['SCRAM_ARCH'], + 'PWD': os.environ['PWD'] +}) + CRAB_PREFIX = """ set -x set -e @@ -85,7 +109,7 @@ def __init__(self): def attach_job_args(self, group): group.add_argument('--job-mode', default=self.job_mode, choices=[ - 'interactive', 'script', 'lxbatch', 'crab3'], help='Task execution mode') + 'interactive', 'script', 'lxbatch', 'NAF', 'crab3'], help='Task execution mode') group.add_argument('--task-name', default=self.task_name, help='Task name, used for job script and log filenames for batch system tasks') group.add_argument('--parallel', type=int, default=self.parallel, @@ -96,6 +120,10 @@ def attach_job_args(self, group): help='Print commands to the screen but do not run them') group.add_argument('--sub-opts', default=self.bopts, help='Options for batch/crab submission') + group.add_argument('--memory', type=int, + help='Request memory for job [MB]') + group.add_argument('--crab-area', + help='crab working area') group.add_argument('--custom-crab', default=self.custom_crab, help='python file containing a function with name signature "custom_crab(config)" that can be used to modify the default crab configuration') @@ -115,6 +143,8 @@ def set_args(self, known, unknown): self.passthru.extend(unknown) self.bopts = self.args.sub_opts self.custom_crab = self.args.custom_crab + self.memory = self.args.memory + self.crab_area = self.args.crab_area def put_back_arg(self, arg_name, target_name): if hasattr(self.args, arg_name): @@ -125,7 +155,8 @@ def create_job_script(self, commands, script_filename, do_log = False): fname = script_filename logname = script_filename.replace('.sh', '.log') with open(fname, "w") as text_file: - text_file.write(JOB_PREFIX) + if self.job_mode=='lxbatch': text_file.write(JOB_PREFIX) + elif self.job_mode=='NAF': text_file.write(JOB_NAF_PREFIX) for i, command in enumerate(commands): tee = 'tee' if i == 0 else 'tee -a' log_part = '\n' @@ -162,7 +193,7 @@ def flush_queue(self): result = pool.map( partial(run_command, self.dry_run), self.job_queue) script_list = [] - if self.job_mode in ['script', 'lxbatch']: + if self.job_mode in ['script', 'lxbatch', 'NAF']: for i, j in enumerate(range(0, len(self.job_queue), self.merge)): script_name = 'job_%s_%i.sh' % (self.task_name, i) # each job is given a slice from the list of combine commands of length 'merge' @@ -176,6 +207,11 @@ def flush_queue(self): full_script = os.path.abspath(script) logname = full_script.replace('.sh', '_%J.log') run_command(self.dry_run, 'bsub -o %s %s %s' % (logname, self.bopts, full_script)) + if self.job_mode == 'NAF': + for script in script_list: + full_script = os.path.abspath(script) + logname = full_script.replace('.sh', '_%J.log') + run_command(self.dry_run, 'qsub -o %s %s %s' % (logname, self.bopts, full_script)) if self.job_mode == 'crab3': #import the stuff we need from CRABAPI.RawCommand import crabCommand @@ -198,12 +234,16 @@ def flush_queue(self): outscript.write('fi') outscript.write(CRAB_POSTFIX) outscript.close() - from HiggsAnalysis.HiggsToTauTau.combine.crab import config + from CombineHarvester.CombineTools.combine.crab import config config.General.requestName = self.task_name config.JobType.scriptExe = outscriptname config.JobType.inputFiles.extend(wsp_files) config.Data.totalUnits = jobs - config.Data.publishDataName = config.General.requestName + config.Data.outputDatasetTag = config.General.requestName + if self.memory is not None: + config.JobType.maxMemoryMB = self.memory + if self.crab_area is not None: + config.General.workArea = self.crab_area if self.custom_crab is not None: d = {} execfile(self.custom_crab, d) diff --git a/CombineTools/python/combine/EnhancedCombine.py b/CombineTools/python/combine/EnhancedCombine.py index 9a2b46850bc..90b7a1d9054 100755 --- a/CombineTools/python/combine/EnhancedCombine.py +++ b/CombineTools/python/combine/EnhancedCombine.py @@ -3,6 +3,7 @@ from CombineHarvester.CombineTools.combine.opts import OPTS from CombineHarvester.CombineTools.combine.CombineToolBase import CombineToolBase +from CombineHarvester.CombineTools.mssm_multidim_fit_boundaries import mssm_multidim_fit_boundaries as bounds class EnhancedCombine(CombineToolBase): @@ -18,6 +19,10 @@ def attach_intercept_args(self, group): '-m', '--mass', help='Supports range strings for multiple masses, e.g. "120:130:5,140 will produce three combine calls with mass values of 120, 125, 130 and 140"') group.add_argument( '--points', help='For use with "-M MultiDimFit --algo grid" to split scan points into separate jobs') + group.add_argument( + '--singlePoint', help='Supports range strings for multiple points to test, uses the same format as the --mass argument') + group.add_argument( + '--Pmodel', help='Set range of physical parameters depending on the given mass and given physics model') group.add_argument('--name', '-n', default='.Test', help='Name used to label the combine output file, can be modified by other options') @@ -46,6 +51,21 @@ def run_method(self): subbed_vars[('MASS',)] = [(mval,) for mval in mass_vals] self.passthru.extend(['-m', '%(MASS)s']) + if self.args.singlePoint is not None: + single_points = utils.split_vals(self.args.singlePoint) + subbed_vars[('SINGLEPOINT',)] = [(pval,) for pval in single_points] + self.passthru.extend(['--singlePoint', '%(SINGLEPOINT)s']) + self.args.name += '.POINT.%(SINGLEPOINT)s' + + if self.args.Pmodel is not None: + subbed_vars = {} + mass_vals = utils.split_vals(self.args.mass) + if self.args.Pmodel == 'bbH' : t=1 + elif self.args.Pmodel == 'ggH' : t=0 + else : exit("Physic model '%s' not recognized" % self.args.Pmodel) + subbed_vars[('MASS', 'MODEL', 'BOUND')] = [(mval, self.args.Pmodel, bounds["ggH-bbH", mval][t]) for mval in mass_vals] + self.passthru.extend(['--setPhysicsModelParameters', 'r_%(MODEL)s=r_%(MODEL)s', '--setPhysicsModelParameterRanges', 'r_%(MODEL)s=0,%(BOUND)s', '--freezeNuisances MH']) + if self.args.points is not None: self.passthru.extend(['--points', self.args.points]) if (self.args.split_points is not None and diff --git a/CombineTools/python/combine/Impacts.py b/CombineTools/python/combine/Impacts.py index 960ec2df090..4615c413300 100755 --- a/CombineTools/python/combine/Impacts.py +++ b/CombineTools/python/combine/Impacts.py @@ -56,14 +56,14 @@ def run_method(self): name = self.args.name if self.args.name is not None else '' named = [] if self.args.named is not None: - named = args.named.split(',') + named = self.args.named.split(',') # Put intercepted args back passthru.extend(['-m', mh]) passthru.extend(['-d', ws]) pass_str = ' '.join(passthru) paramList = [] if self.args.redefineSignalPOIs is not None: - poiList = args.redefineSignalPOIs.split(',') + poiList = self.args.redefineSignalPOIs.split(',') else: poiList = utils.list_from_workspace(ws, 'w', 'ModelConfig_POI') #print 'Have nuisance parameters: ' + str(paramList) @@ -87,8 +87,8 @@ def run_method(self): res = { } res["POIs"] = [] res["params"] = [] - # for poi in poiList: - # res["POIs"].append({"name" : poi, "fit" : initialRes[poi][poi]}) + for poi in poiList: + res["POIs"].append({"name" : poi, "fit" : initialRes[poi][poi]}) missing = [ ] for param in paramList: @@ -97,7 +97,7 @@ def run_method(self): if self.args.doFits: self.job_queue.append('combine -M MultiDimFit -n _paramFit_%(name)s_%(param)s --algo singles --redefineSignalPOIs %(param)s,%(poistr)s -P %(param)s --floatOtherPOIs 1 --saveInactivePOI 1 %(pass_str)s --altCommit' % vars()) else: - paramScanRes = get_singles_results('higgsCombine_paramFit_%(name)s_%(param)s.MultiDimFit.mH%(mh)s.root' % vars(), [param], poiList + [param]) + paramScanRes = utils.get_singles_results('higgsCombine_paramFit_%(name)s_%(param)s.MultiDimFit.mH%(mh)s.root' % vars(), [param], poiList + [param]) if paramScanRes is None: missing.append(param) continue @@ -109,7 +109,7 @@ def run_method(self): jsondata = json.dumps(res, sort_keys=True, indent=2, separators=(',', ': ')) print jsondata if self.args.output is not None: - with open(args.output, 'w') as out_file: + with open(self.args.output, 'w') as out_file: out_file.write(jsondata) if len(missing) > 0: print 'Missing inputs: ' + ','.join(missing) diff --git a/CombineTools/python/combine/ImpactsFromScans.py b/CombineTools/python/combine/ImpactsFromScans.py new file mode 100755 index 00000000000..0c7e16c1970 --- /dev/null +++ b/CombineTools/python/combine/ImpactsFromScans.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python + +import argparse +import os +import re +import sys +import json +import math +import itertools +import stat +import glob +import ROOT +from array import array +from multiprocessing import Pool +import CombineHarvester.CombineTools.combine.utils as utils +from CombineHarvester.CombineTools.combine.opts import OPTS + +from CombineHarvester.CombineTools.combine.CombineToolBase import CombineToolBase + +class ImpactsFromScans(CombineToolBase): + description = 'Calculate nuisance parameter impacts' + requires_root = True + + def __init__(self): + CombineToolBase.__init__(self) + + def attach_intercept_args(self, group): + CombineToolBase.attach_intercept_args(self, group) + group.add_argument('--name', '-n', default='Test') + group.add_argument('-m', '--mass', required=True) + + def get_fixed_results(self, file, POIs): + """Extracts the output from the MultiDimFit singles mode + Note: relies on the list of parameters that were run (scanned) being correct""" + res = {} + f = ROOT.TFile(file) + if f is None or f.IsZombie(): + return None + t = f.Get("limit") + for i, evt in enumerate(t): + if i != 1: continue + for POI in POIs: + res[POI] = getattr(evt, POI) + return res + + + def attach_args(self, group): + CombineToolBase.attach_args(self, group) + # group.add_argument('--offset', default=0, type=int, + # help='Start the loop over parameters with this offset (default: %(default)s)') + # group.add_argument('--advance', default=1, type=int, + # help='Advance this many parameters each step in the loop (default: %(default)s') + group.add_argument('--input-json', + help=('json file and dictionary containing the fit values, of form file:key1:key2..')) + group.add_argument('--do-fits', action='store_true', + help=('Actually do the fits')) + def run_method(self): + mass = self.args.mass + self.put_back_arg('mass', '-m') + in_json = self.args.input_json.split(':') + with open(in_json[0]) as jsonfile: + js = json.load(jsonfile) + for key in in_json[1:]: + js = js[key] + POIs = [str(x) for x in js.keys()] + print POIs + for POI in POIs: + if not self.args.do_fits: break + arg_str = '-M MultiDimFit --algo fixed --saveInactivePOI 1 --floatOtherPOIs 1 -P %s' % POI + cmd_hi = arg_str + ' -n %s --setPhysicsModelParametersForGrid %s=%f' % (self.args.name+'.%s.Hi'%POI, POI, js[POI]["Val"] + js[POI]["ErrorHi"]) + cmd_lo = arg_str + ' -n %s --setPhysicsModelParametersForGrid %s=%f' % (self.args.name+'.%s.Lo'%POI, POI, js[POI]["Val"] + js[POI]["ErrorLo"]) + self.job_queue.append('combine %s %s' % (cmd_hi, ' '.join(self.passthru))) + self.job_queue.append('combine %s %s' % (cmd_lo, ' '.join(self.passthru))) + self.flush_queue() + if self.args.do_fits: + print '>> Re-run without --do-fits to harvest the results' + return + res = {} + for POI in POIs: + res[POI] = {} + name_hi = 'higgsCombine%s.%s.Hi.MultiDimFit.mH%s.root' % (self.args.name, POI, mass) + name_lo = 'higgsCombine%s.%s.Lo.MultiDimFit.mH%s.root' % (self.args.name, POI, mass) + res_hi = self.get_fixed_results(name_hi, POIs) + res_lo = self.get_fixed_results(name_lo, POIs) + for fPOI in POIs: + res[POI][fPOI] = [res_lo[fPOI], js[fPOI]["Val"], res_hi[fPOI]] + print res + cor = ROOT.TMatrixDSym(len(POIs)) + cov = ROOT.TMatrixDSym(len(POIs)) + for i,p in enumerate(POIs): + cor[i][i] = ROOT.Double(1.) # diagonal correlation is 1 + cov[i][i] = ROOT.Double(pow((res[p][p][2] - res[p][p][0])/2.,2.)) # symmetrized error + for i,ip in enumerate(POIs): + for j,jp in enumerate(POIs): + if i == j: continue + val_i = ((res[ip][jp][2] - res[ip][jp][0])/2.)/math.sqrt(cov[j][j]) + val_j = ((res[jp][ip][2] - res[jp][ip][0])/2.)/math.sqrt(cov[i][i]) + correlation = (val_i+val_j)/2. # take average correlation? + #correlation = min(val_i,val_j, key=abs) # take the max? + cor[i][j] = correlation + cor[j][i] = correlation + covariance = correlation * math.sqrt(cov[i][i]) * math.sqrt(cov[j][j]) + cov[i][j] = covariance + cov[j][i] = covariance + cor.Print() + fout = ROOT.TFile('covariance_%s.root' % self.args.name, 'RECREATE') + fout.WriteTObject(cor, 'cor') + h_cor = self.fix_TH2(ROOT.TH2D(cor), POIs) + fout.WriteTObject(h_cor, 'h_cor') + fout.WriteTObject(cov, 'cov') + h_cov = self.fix_TH2(ROOT.TH2D(cov), POIs) + fout.WriteTObject(h_cov, 'h_cov') + xvars = [] + muvars = [] + xvec = ROOT.RooArgList() + mu = ROOT.RooArgList() + for POI in POIs: + xvars.append(ROOT.RooRealVar(POI, '', js[POI]["Val"], -10, 10)) + muvars.append(ROOT.RooRealVar(POI+'_In', '', js[POI]["Val"], -10, 10)) + muvars[-1].setConstant(True) + xvec.add(xvars[-1]) + mu.add(muvars[-1]) + xvec.Print('v') + mu.Print('v') + pdf = ROOT.RooMultiVarGaussian('pdf', '', xvec, mu, cov) + dat = ROOT.RooDataSet('global_obs', '', ROOT.RooArgSet(mu)) + dat.add(ROOT.RooArgSet(mu)) + pdf.Print() + dat.Print() + #fitRes = pdf.fitTo(dat, ROOT.RooFit.Minimizer('Minuit2', 'Migrad'), ROOT.RooFit.Hesse(True), ROOT.RooFit.Save(True)) + #fitRes.Print('v') + wsp = ROOT.RooWorkspace('w', '') + getattr(wsp, 'import')(pdf) + getattr(wsp, 'import')(dat) + wsp.Write() + fout.Close() + + + def fix_TH2(self, h, labels): + h_fix = h.Clone() + for y in range(1, h.GetNbinsY() + 1): + for x in range(1, h.GetNbinsX() + 1): + h_fix.SetBinContent( + x, y, h.GetBinContent(x, h.GetNbinsY() + 1 - y)) + for x in range(1, h_fix.GetNbinsX() + 1): + h_fix.GetXaxis().SetBinLabel(x, labels[x - 1]) + for y in range(1, h_fix.GetNbinsY() + 1): + h_fix.GetYaxis().SetBinLabel(y, labels[-y]) + return h_fix + + + + +# self.job_queue.append('combine -M MultiDimFit -n _initialFit_%(name)s_POI_%(poi)s --algo singles --redefineSignalPOIs %(poistr)s --floatOtherPOIs 1 --saveInactivePOI 1 -P %(poi)s %(pass_str)s --altCommit' % vars()) +# else: +# self.job_queue.append('combine -M MultiDimFit -n _initialFit_%(name)s --algo singles --redefineSignalPOIs %(poistr)s %(pass_str)s --altCommit' % vars()) +# self.flush_queue() +# sys.exit(0) +# initialRes = utils.get_singles_results('higgsCombine_initialFit_%(name)s.MultiDimFit.mH%(mh)s.root' % vars(), poiList, poiList) +# if len(named) > 0: +# paramList = named +# else: +# paramList = utils.list_from_workspace(ws, 'w', 'ModelConfig_NuisParams') +# print 'Have nuisance parameters: ' + str(len(paramList)) +# prefit = utils.prefit_from_workspace(ws, 'w', paramList) +# res = { } +# res["POIs"] = [] +# res["params"] = [] +# # for poi in poiList: +# # res["POIs"].append({"name" : poi, "fit" : initialRes[poi][poi]}) +# +# missing = [ ] +# for param in paramList: +# pres = { } +# # print 'Doing param ' + str(counter) + ': ' + param +# if self.args.doFits: +# self.job_queue.append('combine -M MultiDimFit -n _paramFit_%(name)s_%(param)s --algo singles --redefineSignalPOIs %(param)s,%(poistr)s -P %(param)s --floatOtherPOIs 1 --saveInactivePOI 1 %(pass_str)s --altCommit' % vars()) +# else: +# paramScanRes = get_singles_results('higgsCombine_paramFit_%(name)s_%(param)s.MultiDimFit.mH%(mh)s.root' % vars(), [param], poiList + [param]) +# if paramScanRes is None: +# missing.append(param) +# continue +# pres.update({"name" : param, "fit" : paramScanRes[param][param], "prefit" : prefit[param]}) +# for p in poiList: +# pres.update({p : paramScanRes[param][p], 'impact_'+p : (paramScanRes[param][p][2] - paramScanRes[param][p][0])/2.}) +# res['params'].append(pres) +# self.flush_queue() +# jsondata = json.dumps(res, sort_keys=True, indent=2, separators=(',', ': ')) +# print jsondata +# if self.args.output is not None: +# with open(args.output, 'w') as out_file: +# out_file.write(jsondata) +# if len(missing) > 0: +# print 'Missing inputs: ' + ','.join(missing) + diff --git a/CombineTools/python/combine/LimitGrids.py b/CombineTools/python/combine/LimitGrids.py index 7805653270b..ba3d9726c55 100755 --- a/CombineTools/python/combine/LimitGrids.py +++ b/CombineTools/python/combine/LimitGrids.py @@ -4,10 +4,13 @@ import glob import sys import re +from math import floor from array import array import CombineHarvester.CombineTools.combine.utils as utils from CombineHarvester.CombineTools.combine.CombineToolBase import CombineToolBase +from CombineHarvester.CombineTools.mssm_multidim_fit_boundaries import mssm_multidim_fit_boundaries as bounds +import CombineHarvester.CombineTools.plotting as plot class AsymptoticGrid(CombineToolBase): description = 'Calculate asymptotic limits on parameter grids' @@ -39,11 +42,11 @@ def run_method(self): with open(self.args.config) as json_file: cfg = json.load(json_file) # to do - have to handle the case where it doesn't exist - points = [] + points = []; blacklisted_points = [] for igrid in cfg['grids']: - assert(len(igrid) == 2) - points.extend(itertools.product(utils.split_vals(igrid[0]), utils.split_vals(igrid[1]))) - + assert(len(igrid) == 3) + if igrid[2]=='' : points.extend(itertools.product(utils.split_vals(igrid[0]), utils.split_vals(igrid[1]))) + else : blacklisted_points.extend(itertools.product(utils.split_vals(igrid[0]), utils.split_vals(igrid[1]), utils.split_vals(igrid[2]))) POIs = cfg['POIs'] file_dict = { } @@ -76,27 +79,418 @@ def run_method(self): xvals = [] yvals = [] - zvals = [] + zvals_m2s = []; zvals_m1s = []; zvals_exp = []; zvals_p1s = []; zvals_p2s = []; zvals_obs = [] for key,val in file_dict.iteritems(): for filename in val: fin = ROOT.TFile(filename) if fin.IsZombie(): continue tree = fin.Get('limit') for evt in tree: - if evt.quantileExpected == -1: - print 'At point %s have observed CLs = %f' % (key, evt.limit) + if abs(evt.quantileExpected+1)<0.01: xvals.append(float(key[0])) yvals.append(float(key[1])) - zvals.append(float(evt.limit)) - graph = ROOT.TGraph2D(len(zvals), array('d', xvals), array('d', yvals), array('d', zvals)) - h_bins = cfg['hist_binning'] - hist = ROOT.TH2F('h_observed', '', h_bins[0], h_bins[1], h_bins[2], h_bins[3], h_bins[4], h_bins[5]) - for i in xrange(1, hist.GetNbinsX()+1): - for j in xrange(1, hist.GetNbinsY()+1): - hist.SetBinContent(i, j, graph.Interpolate(hist.GetXaxis().GetBinCenter(i), hist.GetYaxis().GetBinCenter(j))) + #print 'At point %s have observed CLs = %f' % (key, evt.limit) + zvals_obs.append(float(evt.limit)) + if abs(evt.quantileExpected-0.025)<0.01: + #print 'At point %s have -2sigma CLs = %f' % (key, evt.limit) + zvals_m2s.append(float(evt.limit)) + if abs(evt.quantileExpected-0.16)<0.01: + #print 'At point %s have -1sigma CLs = %f' % (key, evt.limit) + zvals_m1s.append(float(evt.limit)) + if abs(evt.quantileExpected-0.5)<0.01: + #print 'At point %s have expected CLs = %f' % (key, evt.limit) + zvals_exp.append(float(evt.limit)) + if abs(evt.quantileExpected-0.84)<0.01: + #print 'At point %s have +1sigma CLs = %f' % (key, evt.limit) + zvals_p1s.append(float(evt.limit)) + if abs(evt.quantileExpected-0.975)<0.01: + #print 'At point %s have +2sigma CLs = %f' % (key, evt.limit) + zvals_p2s.append(float(evt.limit)) + for POI1, POI2, CLs in blacklisted_points: + xvals.append(float(POI1)) + yvals.append(float(POI2)) + zvals_m2s.append(float(CLs)) + zvals_m1s.append(float(CLs)) + zvals_exp.append(float(CLs)) + zvals_p1s.append(float(CLs)) + zvals_p2s.append(float(CLs)) + zvals_obs.append(float(CLs)) + graph_m2s = ROOT.TGraph2D(len(zvals_m2s), array('d', xvals), array('d', yvals), array('d', zvals_m2s)) + graph_m1s = ROOT.TGraph2D(len(zvals_m1s), array('d', xvals), array('d', yvals), array('d', zvals_m1s)) + graph_exp = ROOT.TGraph2D(len(zvals_exp), array('d', xvals), array('d', yvals), array('d', zvals_exp)) + graph_p1s = ROOT.TGraph2D(len(zvals_p1s), array('d', xvals), array('d', yvals), array('d', zvals_p1s)) + graph_p2s = ROOT.TGraph2D(len(zvals_p2s), array('d', xvals), array('d', yvals), array('d', zvals_p2s)) + graph_obs = ROOT.TGraph2D(len(zvals_obs), array('d', xvals), array('d', yvals), array('d', zvals_obs)) + #h_bins = cfg['hist_binning'] + #hist = ROOT.TH2F('h_observed', '', h_bins[0], h_bins[1], h_bins[2], h_bins[3], h_bins[4], h_bins[5]) + #for i in xrange(1, hist.GetNbinsX()+1): + # for j in xrange(1, hist.GetNbinsY()+1): + # hist.SetBinContent(i, j, graph.Interpolate(hist.GetXaxis().GetBinCenter(i), hist.GetYaxis().GetBinCenter(j))) fout = ROOT.TFile('asymptotic_grid.root', 'RECREATE') - fout.WriteTObject(graph, 'observed') - fout.WriteTObject(hist) + fout.WriteTObject(graph_m2s, 'minus2sigma') + fout.WriteTObject(graph_m1s, 'minus1sigma') + fout.WriteTObject(graph_exp, 'expected') + fout.WriteTObject(graph_p1s, 'plus1sigma') + fout.WriteTObject(graph_p2s, 'plus2sigma') + fout.WriteTObject(graph_obs, 'observed') + #fout.WriteTObject(hist) + fout.Close() + # Next step: open output files + # Fill TGraph2D with CLs, CLs+b + +class HybridNewGrid(CombineToolBase): + description = 'Calculate toy-based limits on parameter grids' + requires_root = True + + def __init__(self): + CombineToolBase.__init__(self) + + def attach_intercept_args(self, group): + CombineToolBase.attach_intercept_args(self, group) + + def attach_args(self, group): + CombineToolBase.attach_args(self, group) + group.add_argument('config', help='json config file') + group.add_argument('--cycles', default=0, type=int, help='Number of job cycles to create per point') + + def GetCombinedHypoTest(self, files): + if len(files) == 0: return None + results = [] + for file in files: + f = ROOT.TFile(file) + ROOT.gDirectory.cd('toys') + for key in ROOT.gDirectory.GetListOfKeys(): + if ROOT.gROOT.GetClass(key.GetClassName()).InheritsFrom(ROOT.RooStats.HypoTestResult.Class()): + results.append(ROOT.gDirectory.Get(key.GetName())) + f.Close() + if (len(results)) > 1: + for r in results[1:]: + results[0].Append(r) + return results[0] + + def ValidateHypoTest(self, result, min_toys=500, max_toys=5000, contours=['obs', 'exp0', 'exp+1', 'exp-1', 'exp+2', 'exp-2'], signif=5.0, cl=0.95): + # 1st test - do we have any HypoTestResult at all? + if result is None: + print '>> No toys completed!' + return False + # 2nd test - have we thrown the minimum number of toys? + ntoys = min(result.GetNullDistribution().GetSize(), result.GetAltDistribution().GetSize()) + if ntoys < min_toys: + print '>> Only %i/%i toys completed!' % (ntoys, min_toys) + return False + # 3rd test - have we thrown the maximum (or more) toys? + if ntoys >= max_toys: + print '>> More than max toys %i/%i toys completed!' % (ntoys, min_toys) + return True + # 3rd test - are we > X sigma away from the exclusion CLs? This must be true for all the + # contours we're interested in + btoys = [x for x in result.GetNullDistribution().GetSamplingDistribution()] + btoys.sort() + crossing = 1 - cl + signif_results = {} + # save the real observed test stat, we'll change it in this + # loop to get the expected but we'll want to restore it at the end + testStatObs = result.GetTestStatisticData() + allGood = True + for contour in contours: + signif_results[contour] = True + if 'exp' in contour: + quantile = ROOT.Math.normal_cdf(float(contour.replace('exp', ''))) + print '>> Checking the %s contour at quantile=%f' % (contour, quantile) + testStat = btoys[int(min(floor(quantile * len(btoys) +0.5), len(btoys)))] + print '>> Test statistic for %f quantile: %f' % (quantile, testStat) + result.SetTestStatisticData(testStat) + result.Print("v") + CLs = result.CLs() + CLsErr = result.CLsError() + if CLsErr == 0.: + print '>> CLsErr is zero' + else: + dist = abs(CLs - crossing) / CLsErr + if dist < signif: + print '>> [%s] Only reached %.1f sigma signifance from chosen CL (target %.1f)' % (contour, dist, signif) + signif_results[contour] = False + result.SetTestStatisticData(testStatObs) + print signif_results + for (key, val) in signif_results.iteritems(): + if val == False: return False + print 'POINT IS OK AFTER %i TOYS' % ntoys + return True + + def PlotTest(self, result, name, one_sided=False, model_desc=''): + plot.ModTDRStyle() + canv = ROOT.TCanvas(name, name) + pad = ROOT.TPad('pad', 'pad', 0., 0., 1., 1.) + pad.Draw() + pad.cd() + null_vals = [x * 2. for x in result.GetNullDistribution().GetSamplingDistribution()] + alt_vals = [x * 2. for x in result.GetAltDistribution().GetSamplingDistribution()] + min_val = min(min(alt_vals), min(null_vals)) + max_val = max(max(alt_vals), max(null_vals)) + min_plot_range = min_val - 0.05 * (max_val - min_val) + if one_sided: + min_plot_range = 0. + pad.SetLogy(True) + max_plot_range = max_val + 0.05 * (max_val - min_val) + hist_null = ROOT.TH1F('null', 'null', 40, min_plot_range, max_plot_range) + hist_alt = ROOT.TH1F('alt', 'alt', 40, min_plot_range, max_plot_range) + for val in null_vals: hist_null.Fill(val) + for val in alt_vals: hist_alt.Fill(val) + hist_alt.SetLineColor(ROOT.TColor.GetColor(4, 4, 255)) + hist_alt.SetFillColorAlpha(ROOT.TColor.GetColor(4, 4, 255), 0.4) + hist_alt.GetXaxis().SetTitle('-2 #times ln(Q_{TEV})') + hist_alt.GetYaxis().SetTitle('Pseudo-experiments') + hist_alt.Draw() + hist_alt.SetMaximum(hist_alt.GetMaximum() * 1.5) + hist_null.SetLineColor(ROOT.TColor.GetColor(252, 86, 11)) + hist_null.SetFillColorAlpha(ROOT.TColor.GetColor(254, 195, 40), 0.4) + hist_null.Draw('SAME') + val_obs = result.GetTestStatisticData() * 2. + obs = ROOT.TArrow(val_obs, 0, val_obs, hist_alt.GetMaximum() * 0.3, 0.05 , '<-|') + obs.SetLineColor(ROOT.kRed) + obs.SetLineWidth(3) + obs.Draw() + leg = plot.PositionedLegend(0.22, 0.2, 3, 0.02) + leg.AddEntry(hist_null, "B", "F") + leg.AddEntry(hist_alt, "S+B", "F") + leg.AddEntry(obs, "Observed", "L") + leg.Draw() + plot.DrawCMSLogo(pad, "CMS", "Internal", 0, 0.15, 0.035, 1.2) + pt_l = ROOT.TPaveText(0.23, 0.75, 0.33, 0.9, 'NDCNB') + if model_desc: pt_l.AddText('Model:') + pt_l.AddText('Toys:') + pt_l.AddText('CLs+b:') + pt_l.AddText('CLb:') + pt_l.AddText('CLs:') + pt_l.SetTextAlign(11) + pt_l.SetTextFont(62) + pt_l.Draw() + pt_r = ROOT.TPaveText(0.33, 0.75, 0.63, 0.9, 'NDCNB') + if model_desc: pt_r.AddText(model_desc) + pt_r.AddText('%i (B) + %i (S+B)' % (result.GetNullDistribution().GetSize(), result.GetAltDistribution().GetSize())) + pt_r.AddText('%.3f #pm %.3f' % (result.CLsplusb(), result.CLsplusbError())) + pt_r.AddText('%.3f #pm %.3f' % (result.CLb(), result.CLbError())) + pt_r.AddText('%.3f #pm %.3f' % (result.CLs(), result.CLsError())) + pt_r.SetTextAlign(11) + pt_r.SetTextFont(42) + pt_r.Draw() + canv.SaveAs('.pdf') + + def run_method(self): + # Step 1 - open the json config file + with open(self.args.config) as json_file: + cfg = json.load(json_file) + # to do - have to handle the case where it doesn't exist + points = []; blacklisted_points = [] + for igrid in cfg['grids']: + assert(len(igrid) == 3) + if igrid[2]=='' : points.extend(itertools.product(utils.split_vals(igrid[0]), utils.split_vals(igrid[1]))) + else : blacklisted_points.extend(itertools.product(utils.split_vals(igrid[0]), utils.split_vals(igrid[1]), utils.split_vals(igrid[2]))) + POIs = cfg['POIs'] + + file_dict = { } + for p in points: + file_dict[p] = [] + + for f in glob.glob('higgsCombine.%s.*.%s.*.HybridNew.mH*.root' % (POIs[0], POIs[1])): + # print f + rgx = re.compile('higgsCombine\.%s\.(?P.*)\.%s\.(?P.*)\.HybridNew\.mH.*\.(?P.*)\.root' % (POIs[0], POIs[1])) + matches = rgx.search(f) + p = (matches.group('p1'), matches.group('p2')) + if p in file_dict: + file_dict[p].append((f, int(matches.group('toy')))) + + for key,val in file_dict.iteritems(): + name = '%s.%s.%s.%s' % (POIs[0], key[0], POIs[1], key[1]) + print '>> Point %s' % name + res = self.GetCombinedHypoTest([x[0] for x in val]) + ok = self.ValidateHypoTest(res) + if res is not None and True: self.PlotTest(res, 'plot_'+name, False, 'm_{H}^{mod+} [m_{A} = %.1f, tan#beta = %.1f]' % (float(key[0]), float(key[1]))) + + if not ok: + print 'Going to generate %i job(s) for point %s' % (self.args.cycles, key) + done_cycles = [x[1] for x in val] + print 'Done cycles: ' + ','.join(str(x) for x in done_cycles) + new_idx = max(done_cycles)+1 if len(done_cycles) > 0 else 1 + new_cycles = range(new_idx, new_idx+self.args.cycles) + print 'New cycles: ' + ','.join(str(x) for x in new_cycles) + point_args = '-n .%s --setPhysicsModelParameters %s=%s,%s=%s --freezeNuisances %s,%s' % (name, POIs[0], key[0], POIs[1], key[1], POIs[0], POIs[1]) + for idx in new_cycles: + cmd = ' '.join(['combine -M HybridNew', cfg['opts'], point_args, '-T %i' % cfg['toys_per_cycle'], '-s %i' % idx]) + self.job_queue.append(cmd) + + # bail_out = len(self.job_queue) > 0 + self.flush_queue() + + # if bail_out: + # print ">> New jobs were created / run in this cycle, run the script again to collect the output" + # sys.exit(0) + + # xvals = [] + # yvals = [] + # zvals_m2s = []; zvals_m1s = []; zvals_exp = []; zvals_p1s = []; zvals_p2s = []; zvals_obs = [] + # for key,val in file_dict.iteritems(): + # for filename in val: + # fin = ROOT.TFile(filename) + # if fin.IsZombie(): continue + # tree = fin.Get('limit') + # for evt in tree: + # if abs(evt.quantileExpected+1)<0.01: + # xvals.append(float(key[0])) + # yvals.append(float(key[1])) + # #print 'At point %s have observed CLs = %f' % (key, evt.limit) + # zvals_obs.append(float(evt.limit)) + # if abs(evt.quantileExpected-0.025)<0.01: + # #print 'At point %s have -2sigma CLs = %f' % (key, evt.limit) + # zvals_m2s.append(float(evt.limit)) + # if abs(evt.quantileExpected-0.16)<0.01: + # #print 'At point %s have -1sigma CLs = %f' % (key, evt.limit) + # zvals_m1s.append(float(evt.limit)) + # if abs(evt.quantileExpected-0.5)<0.01: + # #print 'At point %s have expected CLs = %f' % (key, evt.limit) + # zvals_exp.append(float(evt.limit)) + # if abs(evt.quantileExpected-0.84)<0.01: + # #print 'At point %s have +1sigma CLs = %f' % (key, evt.limit) + # zvals_p1s.append(float(evt.limit)) + # if abs(evt.quantileExpected-0.975)<0.01: + # #print 'At point %s have +2sigma CLs = %f' % (key, evt.limit) + # zvals_p2s.append(float(evt.limit)) + # for POI1, POI2, CLs in blacklisted_points: + # xvals.append(float(POI1)) + # yvals.append(float(POI2)) + # zvals_m2s.append(float(CLs)) + # zvals_m1s.append(float(CLs)) + # zvals_exp.append(float(CLs)) + # zvals_p1s.append(float(CLs)) + # zvals_p2s.append(float(CLs)) + # zvals_obs.append(float(CLs)) + # graph_m2s = ROOT.TGraph2D(len(zvals_m2s), array('d', xvals), array('d', yvals), array('d', zvals_m2s)) + # graph_m1s = ROOT.TGraph2D(len(zvals_m1s), array('d', xvals), array('d', yvals), array('d', zvals_m1s)) + # graph_exp = ROOT.TGraph2D(len(zvals_exp), array('d', xvals), array('d', yvals), array('d', zvals_exp)) + # graph_p1s = ROOT.TGraph2D(len(zvals_p1s), array('d', xvals), array('d', yvals), array('d', zvals_p1s)) + # graph_p2s = ROOT.TGraph2D(len(zvals_p2s), array('d', xvals), array('d', yvals), array('d', zvals_p2s)) + # graph_obs = ROOT.TGraph2D(len(zvals_obs), array('d', xvals), array('d', yvals), array('d', zvals_obs)) + # #h_bins = cfg['hist_binning'] + # #hist = ROOT.TH2F('h_observed', '', h_bins[0], h_bins[1], h_bins[2], h_bins[3], h_bins[4], h_bins[5]) + # #for i in xrange(1, hist.GetNbinsX()+1): + # # for j in xrange(1, hist.GetNbinsY()+1): + # # hist.SetBinContent(i, j, graph.Interpolate(hist.GetXaxis().GetBinCenter(i), hist.GetYaxis().GetBinCenter(j))) + # fout = ROOT.TFile('asymptotic_grid.root', 'RECREATE') + # fout.WriteTObject(graph_m2s, 'minus2sigma') + # fout.WriteTObject(graph_m1s, 'minus1sigma') + # fout.WriteTObject(graph_exp, 'expected') + # fout.WriteTObject(graph_p1s, 'plus1sigma') + # fout.WriteTObject(graph_p2s, 'plus2sigma') + # fout.WriteTObject(graph_obs, 'observed') + # #fout.WriteTObject(hist) + # fout.Close() + # # Next step: open output files + # # Fill TGraph2D with CLs, CLs+b + + +class Limit1D(CombineToolBase): + description = 'Calculate asymptotic limits on parameter grids' + requires_root = True + + def __init__(self): + CombineToolBase.__init__(self) + + def attach_intercept_args(self, group): + CombineToolBase.attach_intercept_args(self, group) + + def attach_args(self, group): + CombineToolBase.attach_args(self, group) + group.add_argument('config', help='json config file') + + def run_method(self): + # Step 1 - open the json config file + with open(self.args.config) as json_file: + cfg = json.load(json_file) + # to do - have to handle the case where it doesn't exist + points = []; blacklisted_points = [] + for igrid in cfg['grids']: + assert(len(igrid) == 1) + points.extend(itertools.product(utils.split_vals(igrid[0]))) + POIs = cfg['POIs'] + + file_dict = { } + for p in points: + file_dict[p] = [] + + for f in glob.glob('higgsCombine.%s.*.Asymptotic.mH*.root' % (POIs[0])): + rgx = re.compile('higgsCombine\.%s\.(?P.*)\.Asymptotic\.mH.*\.root' % (POIs[0])) + matches = rgx.search(f) + p = (matches.group('p1'),) + if p in file_dict: + file_dict[p].append(f) + + for key,val in file_dict.iteritems(): + name = '%s.%s' % (POIs[0], key[0]) + print '>> Point %s' % name + if len(val) == 0: + print 'Going to run limit for point %s' % (key) + r_process = cfg['r'] + point_args = '' + if r_process[0] == "r_ggA" : + point_args = '-n .%s --setPhysicsModelParameters %s=%s --setPhysicsModelParameterRanges %s=0,%s --freezeNuisances %s' % (name, POIs[0], key[0], r_process[0], str(bounds["ggH-bbH",key[0]][0]), POIs[0]) + elif r_process[0] == "r_bbA" : + point_args = '-n .%s --setPhysicsModelParameters %s=%s --setPhysicsModelParameterRanges %s=0,%s --freezeNuisances %s' % (name, POIs[0], key[0], r_process[0], str(bounds["ggH-bbH",key[0]][1]), POIs[0]) + cmd = ' '.join(['combine -M Asymptotic', cfg['opts'], point_args] + self.passthru) + self.job_queue.append(cmd) + + bail_out = len(self.job_queue) > 0 + self.flush_queue() + + if bail_out: + print ">> New jobs were created / run in this cycle, run the script again to collect the output" + sys.exit(0) + + xvals = [] + zvals_m2s = []; zvals_m1s = []; zvals_exp = []; zvals_p1s = []; zvals_p2s = []; zvals_obs = [] + for key,val in file_dict.iteritems(): + for filename in val: + fin = ROOT.TFile(filename) + if fin.IsZombie(): continue + tree = fin.Get('limit') + for evt in tree: + if abs(evt.quantileExpected+1)<0.01: + xvals.append(float(key[0])) + #print 'At point %s have observed CLs = %f' % (key, evt.limit) + zvals_obs.append(float(evt.limit)) + if abs(evt.quantileExpected-0.025)<0.01: + #print 'At point %s have -2sigma CLs = %f' % (key, evt.limit) + zvals_m2s.append(float(evt.limit)) + if abs(evt.quantileExpected-0.16)<0.01: + #print 'At point %s have -1sigma CLs = %f' % (key, evt.limit) + zvals_m1s.append(float(evt.limit)) + if abs(evt.quantileExpected-0.5)<0.01: + #print 'At point %s have expected CLs = %f' % (key, evt.limit) + zvals_exp.append(float(evt.limit)) + if abs(evt.quantileExpected-0.84)<0.01: + #print 'At point %s have +1sigma CLs = %f' % (key, evt.limit) + zvals_p1s.append(float(evt.limit)) + if abs(evt.quantileExpected-0.975)<0.01: + #print 'At point %s have +2sigma CLs = %f' % (key, evt.limit) + zvals_p2s.append(float(evt.limit)) + graph_m2s = ROOT.TGraph(len(zvals_m2s), array('d', xvals), array('d', zvals_m2s)) + graph_m1s = ROOT.TGraph(len(zvals_m1s), array('d', xvals), array('d', zvals_m1s)) + graph_exp = ROOT.TGraph(len(zvals_exp), array('d', xvals), array('d', zvals_exp)) + graph_p1s = ROOT.TGraph(len(zvals_p1s), array('d', xvals), array('d', zvals_p1s)) + graph_p2s = ROOT.TGraph(len(zvals_p2s), array('d', xvals), array('d', zvals_p2s)) + graph_obs = ROOT.TGraph(len(zvals_obs), array('d', xvals), array('d', zvals_obs)) + #h_bins = cfg['hist_binning'] + #hist = ROOT.TH2F('h_observed', '', h_bins[0], h_bins[1], h_bins[2], h_bins[3], h_bins[4], h_bins[5]) + #for i in xrange(1, hist.GetNbinsX()+1): + # for j in xrange(1, hist.GetNbinsY()+1): + # hist.SetBinContent(i, j, graph.Interpolate(hist.GetXaxis().GetBinCenter(i), hist.GetYaxis().GetBinCenter(j))) + fout = ROOT.TFile('asymptotic_1Dlimit.root', 'RECREATE') + fout.WriteTObject(graph_m2s, 'minus2sigma') + fout.WriteTObject(graph_m1s, 'minus1sigma') + fout.WriteTObject(graph_exp, 'expected') + fout.WriteTObject(graph_p1s, 'plus1sigma') + fout.WriteTObject(graph_p2s, 'plus2sigma') + fout.WriteTObject(graph_obs, 'observed') + #fout.WriteTObject(hist) fout.Close() # Next step: open output files # Fill TGraph2D with CLs, CLs+b diff --git a/CombineTools/python/combine/Output.py b/CombineTools/python/combine/Output.py index 7bd21189397..53a3d0ce2ab 100755 --- a/CombineTools/python/combine/Output.py +++ b/CombineTools/python/combine/Output.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import ROOT +import json import CombineHarvester.CombineTools.combine.utils as utils from CombineHarvester.CombineTools.combine.opts import OPTS @@ -71,5 +72,5 @@ def run_method(self): js_out, sort_keys=True, indent=2, separators=(',', ': ')) print jsondata if self.args.output is not None: - with open(args.output, 'w') as out_file: + with open(self.args.output, 'w') as out_file: out_file.write(jsondata) diff --git a/CombineTools/python/combine/crab.py b/CombineTools/python/combine/crab.py index d3fd65a0943..30f6f89a506 100755 --- a/CombineTools/python/combine/crab.py +++ b/CombineTools/python/combine/crab.py @@ -11,19 +11,19 @@ config.section_('JobType') config.JobType.pluginName = 'PrivateMC' -config.JobType.psetName = os.environ['CMSSW_BASE']+'/src/HiggsAnalysis/HiggsToTauTau/data/do_nothing_cfg.py' +config.JobType.psetName = os.environ['CMSSW_BASE']+'/src/CombineHarvester/CombineTools/scripts/do_nothing_cfg.py' config.JobType.scriptExe = '' -config.JobType.inputFiles = [os.environ['CMSSW_BASE']+'/src/HiggsAnalysis/HiggsToTauTau/data/FrameworkJobReport.xml', os.environ['CMSSW_BASE']+'/bin/'+os.environ['SCRAM_ARCH']+'/combine'] +config.JobType.inputFiles = [os.environ['CMSSW_BASE']+'/src/CombineHarvester/CombineTools/scripts/FrameworkJobReport.xml', os.environ['CMSSW_BASE']+'/bin/'+os.environ['SCRAM_ARCH']+'/combine'] config.JobType.outputFiles = ['combine_output.tar'] # config.JobType.maxMemoryMB = args.maxMemory config.section_('Data') -config.Data.primaryDataset = 'Combine' +config.Data.outputPrimaryDataset = 'Combine' config.Data.splitting = 'EventBased' config.Data.unitsPerJob = 1 config.Data.totalUnits = 1 config.Data.publication = False -config.Data.publishDataName = '' +config.Data.outputDatasetTag = '' config.section_('User') diff --git a/CombineTools/python/maketable.py b/CombineTools/python/maketable.py new file mode 100644 index 00000000000..a2f102c2eb8 --- /dev/null +++ b/CombineTools/python/maketable.py @@ -0,0 +1,90 @@ +import CombineHarvester.CombineTools.plotting as plot +import ROOT as R +from array import array +import json + +def Tablefrom1DGraph(rootfile, filename): + graph = [R.TGraph() for i in range(6)] + + # Get and sort graphs + fin = R.TFile(rootfile, 'r') + names = ["mass", "minus2sigma", "minus1sigma", "expected", "plus1sigma", "plus2sigma", "observed"] + for k in range(6): + graph[k] = plot.SortGraph(fin.Get(names[k+1])) + fin.Close() + + # Prepare writing values into table + f = open(filename, 'w') + for name in names: + f.write("%-20s" % name) + f.write("\n") + for name in names: + f.write("--------------------") + f.write("\n") + + # Get values and write them + value=array('d', [0]*7) + for i in range(graph[1].GetN()) : + value[0] = graph[1].GetX()[i] + for k in range(6): + value[k+1]=graph[k].GetY()[i] + for k in range(7): + f.write("%-20s" % str(value[k])) + f.write("\n") + f.close() + + +def TablefromJson(jsonfile, filename): + with open(jsonfile) as json_file: + js = json.load(json_file) + x = [] + jsonnames = ["-2", "-1", "expected", "+1", "+2", "observed"] + names = ["mass", "minus2sigma", "minus1sigma", "expected", "plus1sigma", "plus2sigma", "observed"] + + # Get list of masses + maxpoints = [0 for i in range(7)] + for key in js: + x.append(float(key)) + maxpoints[0]+=1 + + # Sort list of masses (Bubblesort-Algorithm. Not very fast when there are many points to be sorted.) + i=0 + while (i < maxpoints[0]-1): + if (x[i+1]0): + i-=2 + i+=1 + + # Get values for the masses + xfory = [[0.0 for i in range(6)] for j in range(maxpoints[0])] # This is incase the json-file is incomplete (values for -2,-1,exp,+1,+2,obs are missing). If everything is fine, all xfory[i] are the same as x, as well as all maxpoints. + y = [[0.0 for i in range(6)] for j in range(maxpoints[0])] + for i in range(maxpoints[0]): + for k in range(6): + if jsonnames[k] not in js[str(x[i])]: + continue + y[maxpoints[k+1]][k]=js[str(x[i])][jsonnames[k]] + xfory[maxpoints[k+1]][k]=x[i] + maxpoints[k+1]+=1 + + # Prepare writing values into table + f = open(filename, 'w') + for name in names: + f.write("%-20s" % name) + f.write("\n") + for name in names: + f.write("--------------------") + f.write("\n") + + # Write table + i=0 + for mass in x : + for k in range(6): + if k==0: + f.write("%-20s" % str(mass)) + f.write("%-20s" % str(y[i][k])) + f.write("\n") + i+=1 + f.close() diff --git a/CombineTools/python/mssm_multidim_fit_boundaries.py b/CombineTools/python/mssm_multidim_fit_boundaries.py new file mode 100644 index 00000000000..91158300c54 --- /dev/null +++ b/CombineTools/python/mssm_multidim_fit_boundaries.py @@ -0,0 +1,137 @@ +mssm_multidim_fit_boundaries = { + ## key=mass first value=ggH; second value=bbH + ("ggH-bbH", "90") : (80.00, 25.00), + ("ggH-bbH", "100") : (35.00, 20.00), + #("ggH-bbH", "102") : (40.00, 8.00), + #("ggH-bbH", "104") : (40.00, 8.00), + #("ggH-bbH", "106") : (40.00, 8.00), + #("ggH-bbH", "108") : (40.00, 8.00), + ("ggH-bbH", "110") : (18.00, 15.00), + #("ggH-bbH", "112") : (12.00, 7.00), + #("ggH-bbH", "114") : (12.00, 7.00), + #("ggH-bbH", "116") : (12.00, 7.00), + #("ggH-bbH", "118") : (12.00, 7.00), + ("ggH-bbH", "120") : (18.00, 12.00), + #("ggH-bbH", "122") : (10.00, 3.50), + #("ggH-bbH", "124") : (10.00, 3.50), + #("ggH-bbH", "125") : (10.00, 3.50), + #("ggH-bbH", "126") : (10.00, 3.50), + #("ggH-bbH", "128") : (10.00, 3.50), + ("ggH-bbH", "130") : (12.00, 10.00), + ("ggH-bbH", "140") : ( 8.00, 7.00), + ("ggH-bbH", "150") : ( 2.00, 2.00), + ("ggH-bbH", "160") : ( 1.80, 1.85), + ("ggH-bbH", "170") : ( 1.20, 1.50), + ("ggH-bbH", "180") : ( 1.00, 1.25), + ("ggH-bbH", "190") : ( 1.00, 1.00), + ("ggH-bbH", "200") : ( 1.00, 1.00), + ("ggH-bbH", "210") : ( 0.80, 0.80), + ("ggH-bbH", "220") : ( 0.80, 0.70), + ("ggH-bbH", "230") : ( 0.80, 0.60), + ("ggH-bbH", "240") : ( 0.60, 0.60), + ("ggH-bbH", "250") : ( 0.60, 0.60), + ("ggH-bbH", "260") : ( 0.60, 0.60), + ("ggH-bbH", "270") : ( 0.60, 0.60), + ("ggH-bbH", "280") : ( 0.60, 0.60), + ("ggH-bbH", "290") : ( 0.60, 0.60), + ("ggH-bbH", "300") : ( 0.60, 0.60), + ("ggH-bbH", "310") : ( 0.50, 0.50), + ("ggH-bbH", "320") : ( 0.50, 0.50), + ("ggH-bbH", "330") : ( 0.50, 0.50), + ("ggH-bbH", "340") : ( 0.50, 0.50), + ("ggH-bbH", "350") : ( 0.50, 0.50), + ("ggH-bbH", "360") : ( 0.50, 0.50), + ("ggH-bbH", "370") : ( 0.50, 0.50), + ("ggH-bbH", "380") : ( 0.50, 0.50), + ("ggH-bbH", "390") : ( 0.50, 0.50), + ("ggH-bbH", "400") : ( 0.40, 0.40), + ("ggH-bbH", "410") : ( 0.40, 0.40), + ("ggH-bbH", "420") : ( 0.40, 0.40), + ("ggH-bbH", "430") : ( 0.40, 0.40), + ("ggH-bbH", "440") : ( 0.40, 0.40), + ("ggH-bbH", "450") : ( 0.40, 0.40), + ("ggH-bbH", "460") : ( 0.40, 0.40), + ("ggH-bbH", "470") : ( 0.40, 0.40), + ("ggH-bbH", "480") : ( 0.40, 0.40), + ("ggH-bbH", "490") : ( 0.40, 0.40), + ("ggH-bbH", "500") : ( 0.40, 0.40), + ("ggH-bbH", "510") : ( 0.40, 0.40), + ("ggH-bbH", "520") : ( 0.40, 0.40), + ("ggH-bbH", "530") : ( 0.40, 0.40), + ("ggH-bbH", "540") : ( 0.40, 0.40), + ("ggH-bbH", "550") : ( 0.40, 0.40), + ("ggH-bbH", "560") : ( 0.40, 0.40), + ("ggH-bbH", "570") : ( 0.40, 0.40), + ("ggH-bbH", "580") : ( 0.40, 0.40), + ("ggH-bbH", "590") : ( 0.40, 0.40), + ("ggH-bbH", "600") : ( 0.40, 0.40), + ("ggH-bbH", "610") : ( 0.40, 0.40), + ("ggH-bbH", "620") : ( 0.40, 0.40), + ("ggH-bbH", "630") : ( 0.40, 0.40), + ("ggH-bbH", "640") : ( 0.40, 0.40), + ("ggH-bbH", "650") : ( 0.40, 0.40), + ("ggH-bbH", "660") : ( 0.40, 0.40), + ("ggH-bbH", "670") : ( 0.40, 0.40), + ("ggH-bbH", "680") : ( 0.40, 0.40), + ("ggH-bbH", "690") : ( 0.40, 0.40), + ("ggH-bbH", "700") : ( 0.30, 0.30), + ("ggH-bbH", "710") : ( 0.30, 0.30), + ("ggH-bbH", "720") : ( 0.30, 0.30), + ("ggH-bbH", "730") : ( 0.30, 0.30), + ("ggH-bbH", "740") : ( 0.30, 0.30), + ("ggH-bbH", "750") : ( 0.30, 0.30), + ("ggH-bbH", "760") : ( 0.30, 0.30), + ("ggH-bbH", "770") : ( 0.30, 0.30), + ("ggH-bbH", "780") : ( 0.30, 0.30), + ("ggH-bbH", "790") : ( 0.30, 0.30), + ("ggH-bbH", "800") : ( 0.30, 0.30), + ("ggH-bbH", "810") : ( 0.30, 0.30), + ("ggH-bbH", "820") : ( 0.30, 0.30), + ("ggH-bbH", "830") : ( 0.30, 0.30), + ("ggH-bbH", "840") : ( 0.30, 0.30), + ("ggH-bbH", "850") : ( 0.30, 0.30), + ("ggH-bbH", "860") : ( 0.30, 0.30), + ("ggH-bbH", "870") : ( 0.30, 0.30), + ("ggH-bbH", "880") : ( 0.30, 0.30), + ("ggH-bbH", "890") : ( 0.30, 0.30), + ("ggH-bbH", "900") : ( 0.20, 0.20), + ("ggH-bbH", "910") : ( 0.20, 0.20), + ("ggH-bbH", "920") : ( 0.20, 0.20), + ("ggH-bbH", "930") : ( 0.20, 0.20), + ("ggH-bbH", "940") : ( 0.20, 0.20), + ("ggH-bbH", "950") : ( 0.20, 0.20), + ("ggH-bbH", "960") : ( 0.20, 0.20), + ("ggH-bbH", "970") : ( 0.20, 0.20), + ("ggH-bbH", "980") : ( 0.20, 0.20), + ("ggH-bbH", "990") : ( 0.20, 0.20), + ("ggH-bbH","1000") : ( 0.20, 0.20), + +### only makes since 90-140 GeV atm since SM ggH loop contributions are only available for those masses + ("cb-ctau", "90") : (3.00, 3.00), + ("cb-ctau", "100") : (3.00, 3.00), + ("cb-ctau", "120") : (3.00, 3.00), + ("cb-ctau", "130") : (3.00, 3.00), + ("cb-ctau", "140") : (3.00, 3.00), + +### only makes since 90-140 GeV for model normalized to SM atm since SM ggH loop contributions are only available for those masses +### no assumption on total widht could be used for all mases + ("cl-cq", "90") : (3.00, 3.00), + ("cl-cq", "100") : (3.00, 3.00), + ("cl-cq", "120") : (3.00, 3.00), + ("cl-cq", "130") : (3.00, 3.00), + ("cl-cq", "140") : (3.00, 3.00), + ("cl-cq", "160") : (3.00, 3.00), + ("cl-cq", "180") : (3.00, 3.00), + ("cl-cq", "200") : (3.00, 3.00), + ("cl-cq", "250") : (3.00, 3.00), + ("cl-cq", "300") : (3.00, 3.00), + ("cl-cq", "350") : (3.00, 3.00), + ("cl-cq", "400") : (3.00, 3.00), + ("cl-cq", "450") : (3.00, 3.00), + ("cl-cq", "500") : (3.00, 3.00), + ("cl-cq", "600") : (3.00, 3.00), + ("cl-cq", "700") : (3.00, 3.00), + ("cl-cq", "800") : (3.00, 3.00), + ("cl-cq", "900") : (3.00, 3.00), + ("cl-cq","1000") : (3.00, 3.00), + } diff --git a/CombineTools/python/plotting.py b/CombineTools/python/plotting.py index bcb2f08f93b..9eb2952e9c0 100644 --- a/CombineTools/python/plotting.py +++ b/CombineTools/python/plotting.py @@ -1,9 +1,10 @@ +import CombineHarvester.CombineTools.ch as ch import ROOT as R import os from functools import partial from array import array import re - +import collections def SetTDRStyle(): # For the canvas: @@ -242,7 +243,7 @@ def DrawCMSLogo(pad, cmsText, extraText, iPosX, relPosX, relPosY, relExtraDY): if (iPosX / 10 == 1): alignX_ = 1 if (iPosX / 10 == 2): alignX_ = 2 if (iPosX / 10 == 3): alignX_ = 3 - if (iPosX == 0): relPosX = 0.14 + # if (iPosX == 0): relPosX = 0.14 align_ = 10 * alignX_ + alignY_ l = pad.GetLeftMargin() @@ -453,7 +454,7 @@ def CreateTransparentColor(color, alpha): def TFileIsGood(filename): if not os.path.exists(filename): return False fin = R.TFile.Open(filename) - R.TFile.Close(fin) + if fin: R.TFile.Close(fin) if not fin: return False return True @@ -514,6 +515,32 @@ def OnePad(): result = [pad] return result +def TwoPadSplit(split_point, gap_low, gap_high) : + upper = R.TPad('upper', 'upper', 0., 0., 1., 1.) + upper.SetBottomMargin(split_point + gap_high) + upper.SetFillStyle(4000) + upper.Draw() + lower = R.TPad('lower', 'lower', 0., 0., 1., 1.) + lower.SetTopMargin(1 - split_point + gap_low) + lower.SetFillStyle(4000) + lower.Draw() + upper.cd() + result = [upper,lower] + return result + +def TwoPadSplitColumns(split_point, gap_left, gap_right) : + left = R.TPad('left', 'left', 0., 0., 1., 1.) + left.SetRightMargin(1 - split_point + gap_right) + left.SetFillStyle(4000) + left.Draw() + right = R.TPad('right', 'right', 0., 0., 1., 1.) + right.SetLeftMargin(split_point + gap_left) + right.SetFillStyle(4000) + right.Draw() + left.cd() + result = [left,right] + return result + def ImproveMinimum(graph, func): fit_x = 0. fit_y = 0. @@ -768,3 +795,115 @@ def frameTH2D(hist, threshold, mult = 1.0): def FixOverlay(): R.gPad.GetFrame().Draw() R.gPad.RedrawAxis() + +def makeHist1D(name, xbins, graph): + len_x = graph.GetX()[graph.GetN()-1] - graph.GetX()[0] + binw_x = (len_x * 0.5 / (float(xbins) - 1.)) - 1E-5 + hist = R.TH1F(name, '', xbins, graph.GetX()[0], graph.GetX()[graph.GetN()-1]+binw_x) + return hist + +def makeHist2D(name, xbins, ybins, graph2d): + len_x = graph2d.GetXmax() - graph2d.GetXmin() + binw_x = (len_x * 0.5 / (float(xbins) - 1.)) - 1E-5 + len_y = graph2d.GetYmax() - graph2d.GetYmin() + binw_y = (len_y * 0.5 / (float(ybins) - 1.)) - 1E-5 + hist = R.TH2F(name, '', xbins, graph2d.GetXmin()-binw_x, graph2d.GetXmax()+binw_x, ybins, graph2d.GetYmin()-binw_y, graph2d.GetYmax()+binw_y) + return hist + +def makeVarBinHist2D(name, xbins, ybins): + #create new arrays in which bin low edge is adjusted to make measured points at the bin centres + xbins_new=[None]*(len(xbins)+1) + for i in xrange(len(xbins)-1): + if i == 0 or i == 1: xbins_new[i] = xbins[i] - ((xbins[i+1]-xbins[i])/2) + 1E-5 + else: xbins_new[i] = xbins[i] - ((xbins[i+1]-xbins[i])/2) + xbins_new[len(xbins)-1] = xbins[len(xbins)-2] + ((xbins[len(xbins)-2]-xbins[len(xbins)-3])/2) + xbins_new[len(xbins)] = xbins[len(xbins)-1] + ((xbins[len(xbins)-1]-xbins[len(xbins)-2])/2) - 1E-5 + + ybins_new=[None]*(len(ybins)+1) + for i in xrange(len(ybins)-1): + if i == 0 or i == 1: ybins_new[i] = ybins[i] - ((ybins[i+1]-ybins[i])/2) + 1E-5 + else: ybins_new[i] = ybins[i] - ((ybins[i+1]-ybins[i])/2) + ybins_new[len(ybins)-1] = ybins[len(ybins)-2] + ((ybins[len(ybins)-2]-ybins[len(ybins)-3])/2) + ybins_new[len(ybins)] = ybins[len(ybins)-1] + ((ybins[len(ybins)-1]-ybins[len(ybins)-2])/2) - 1E-5 + hist = R.TH2F(name, '', len(xbins_new)-1, array('d',xbins_new), len(ybins_new)-1, array('d',ybins_new)) + return hist + +def fillTH2(hist2d, graph): + for x in xrange(1, hist2d.GetNbinsX()+1): + for y in xrange(1, hist2d.GetNbinsY()+1): + xc = hist2d.GetXaxis().GetBinCenter(x) + yc = hist2d.GetYaxis().GetBinCenter(y) + val = graph.Interpolate(xc, yc) + hist2d.SetBinContent(x, y, val) + +def higgsConstraint(model, higgstype) : + higgsBand=R.TGraph2D() + masslow = 150 + masshigh = 500 + massstep = 10 + n=0 + for mass in range (masslow, masshigh, massstep): + myfile = open("../../HiggsAnalysis/HiggsToTauTau/data/Higgs125/"+model+"/higgs_"+str(mass)+".dat", 'r') + for line in myfile: + tanb = (line.split())[0] + mh = float((line.split())[1]) + mH = float((line.split())[3]) + if higgstype=="h" : + higgsBand.SetPoint(n, mass, float(tanb), mh) + elif higgstype=="H" : + higgsBand.SetPoint(n, mass, float(tanb), mH) + n=n+1 + myfile.close() + return higgsBand + +def MakeErrorBand(LowerGraph, UpperGraph) : + errorBand = R.TGraphAsymmErrors() + lower_list=[]; upper_list=[] + for i in range(LowerGraph.GetN()): + lower_list.append((float(LowerGraph.GetX()[i]), float(LowerGraph.GetY()[i]))) + upper_list.append((float(UpperGraph.GetX()[i]), float(UpperGraph.GetY()[i]))) + lower_list=sorted(set(lower_list)) + upper_list=sorted(set(upper_list)) + for i in range(LowerGraph.GetN()): + errorBand.SetPoint(i, lower_list[i][0], lower_list[i][1]) + errorBand.SetPointEYlow (i, lower_list[i][1]-lower_list[i][1]) + errorBand.SetPointEYhigh(i, upper_list[i][1]-lower_list[i][1]) + return errorBand + +def SortGraph(Graph) : + sortedGraph = R.TGraph() + graph_list=[] + for i in range(Graph.GetN()): + graph_list.append((float(Graph.GetX()[i]), float(Graph.GetY()[i]))) + graph_list=sorted(set(graph_list)) + for i in range(Graph.GetN()): + sortedGraph.SetPoint(i, graph_list[i][0], graph_list[i][1]) + return sortedGraph + +def LimitTGraphFromJSON(js, label): + xvals = [] + yvals = [] + for key in js: + xvals.append(float(key)) + yvals.append(js[key][label]) + graph = R.TGraph(len(xvals), array('d', xvals), array('d', yvals)) + graph.Sort() + return graph + +def LimitBandTGraphFromJSON(js, central, lo, hi): + xvals = [] + yvals = [] + yvals_lo = [] + yvals_hi = [] + for key in js: + xvals.append(float(key)) + yvals.append(js[key][central]) + yvals_lo.append(js[key][central] - js[key][lo]) + yvals_hi.append(js[key][hi] - js[key][central]) + graph = R.TGraphAsymmErrors(len(xvals), array('d', xvals), array('d', yvals), array('d', [0]), array('d', [0]), array('d', yvals_lo), array('d', yvals_hi)) + graph.Sort() + return graph + +def Set(obj, **kwargs): + for key, value in kwargs.iteritems(): + getattr(obj, 'Set'+key)(value) diff --git a/CombineTools/scripts/FrameworkJobReport.xml b/CombineTools/scripts/FrameworkJobReport.xml new file mode 100644 index 00000000000..1f409e55f7c --- /dev/null +++ b/CombineTools/scripts/FrameworkJobReport.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/CombineTools/scripts/HybridNewGrid.py b/CombineTools/scripts/HybridNewGrid.py deleted file mode 100755 index e6d792fca4c..00000000000 --- a/CombineTools/scripts/HybridNewGrid.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env python - -import argparse -import os -import re -import sys -import json -import math -import itertools -import stat -import ROOT -import glob -import plotting - -DRY_RUN = False - -def split_vals(vals): - res = set() - first = vals.split(',') - for f in first: - second = re.split('[:|]', f) - print second - if len(second) == 1: res.add(second[0]) - if len(second) == 3: - x1 = float(second[0]) - ndigs = '0' - split_step = second[2].split('.') - if len(split_step) == 2: - ndigs = len(split_step[1]) - fmt = '%.'+str(ndigs)+'f' - while x1 < float(second[1]) + 0.001: - res.add(fmt % x1) - x1 += float(second[2]) - return sorted([x for x in res], key = lambda x : float(x)) - -def run(command): - print command - if not DRY_RUN: return os.system(command) - -def GetCombinedHypoTest(files): - if len(files) == 0: return None - results = [] - for file in files: - f = ROOT.TFile(file) - ROOT.gDirectory.cd('toys') - for key in ROOT.gDirectory.GetListOfKeys(): - if ROOT.gROOT.GetClass(key.GetClassName()).InheritsFrom(ROOT.RooStats.HypoTestResult.Class()): - results.append(ROOT.gDirectory.Get(key.GetName())) - f.Close() - if (len(results)) > 1: - for r in results[1:]: - results[0].Append(r) - return results[0] - - -def ValidateHypoTest(result, min_toys, contours, contour_min_signif, conf_level): - # 1st test - do we have any HypoTestResult at all? - if result is None: - print 'No toys completed!' - return False - # 2nd test - have we thrown the minimum number of toys? - ntoys = min(result.GetNullDistribution().GetSize(), result.GetAltDistribution().GetSize()) - if ntoys < min_toys: - print 'Only %i/%i toys completed!' - return False - # 3rd test - are we > X sigma away from the exclusion CLs? This must be true for all the - # contours we're interested in - # But first, if the CLs error is zero: - # result.Print("v") - CLs = result.CLs() - CLsErr = result.CLsError() - if CLsErr == 0.: - print 'CLsErr is zero, no further toys required' - return True - crossing = 1 - conf_level - dist = abs(CLs - crossing) / CLsErr - if dist < contour_min_signif: - print 'Only reached %.1f sigma signifance from chosen CL (target %.1f)' % (dist, contour_min_signif) - result.Print("v") - return False - print 'POINT IS OK AFTER %i TOYS' % ntoys - return True - - -def PlotTest(result, name, one_sided=False, model_desc=''): - plotting.ModTDRStyle() - canv = ROOT.TCanvas(name, name) - pad = ROOT.TPad('pad', 'pad', 0., 0., 1., 1.) - pad.Draw() - pad.cd() - - - null_vals = [x * 2. for x in result.GetNullDistribution().GetSamplingDistribution()] - alt_vals = [x * 2. for x in result.GetAltDistribution().GetSamplingDistribution()] - - min_val = min(min(alt_vals), min(null_vals)) - max_val = max(max(alt_vals), max(null_vals)) - min_plot_range = min_val - 0.05 * (max_val - min_val) - if one_sided: - min_plot_range = 0. - pad.SetLogy(True) - max_plot_range = max_val + 0.05 * (max_val - min_val) - hist_null = ROOT.TH1F('null', 'null', 40, min_plot_range, max_plot_range) - hist_alt = ROOT.TH1F('alt', 'alt', 40, min_plot_range, max_plot_range) - for val in null_vals: hist_null.Fill(val) - for val in alt_vals: hist_alt.Fill(val) - hist_alt.SetLineColor(ROOT.TColor.GetColor(4, 4, 255)) - hist_alt.SetFillColorAlpha(ROOT.TColor.GetColor(4, 4, 255), 0.4) - hist_alt.GetXaxis().SetTitle('-2 #times ln(Q_{LHC})') - hist_alt.GetYaxis().SetTitle('Pseudo-experiments') - hist_alt.Draw() - hist_null.SetLineColor(ROOT.TColor.GetColor(252, 86, 11)) - hist_null.SetFillColorAlpha(ROOT.TColor.GetColor(254, 195, 40), 0.4) - hist_null.Draw('SAME') - val_obs = result.GetTestStatisticData() * 2. - obs = ROOT.TArrow(val_obs, 0, val_obs, hist_alt.GetMaximum() / 50., 0.05 , '<-|') - obs.SetLineColor(ROOT.kRed) - obs.SetLineWidth(3) - obs.Draw() - leg = plotting.PositionedLegend(0.22, 0.2, 3, 0.02) - leg.AddEntry(hist_null, "B", "F") - leg.AddEntry(hist_alt, "S+B", "F") - leg.AddEntry(obs, "Observed", "L") - leg.Draw() - plotting.DrawCMSLogo(pad, "CMS", "Internal", 0, 0.045, 0.035, 1.2) - pt_l = ROOT.TPaveText(0.23, 0.75, 0.33, 0.9, 'NDCNB') - if model_desc: pt_l.AddText('Model:') - pt_l.AddText('Toys:') - pt_l.AddText('CLs+b:') - pt_l.AddText('CLb:') - pt_l.AddText('CLs:') - pt_l.SetTextAlign(11) - pt_l.SetTextFont(62) - pt_l.Draw() - pt_r = ROOT.TPaveText(0.33, 0.75, 0.63, 0.9, 'NDCNB') - if model_desc: pt_r.AddText(model_desc) - pt_r.AddText('%i (B) + %i (S+B)' % (result.GetNullDistribution().GetSize(), result.GetAltDistribution().GetSize())) - pt_r.AddText('%.3f #pm %.3f' % (result.CLsplusb(), result.CLsplusbError())) - pt_r.AddText('%.3f #pm %.3f' % (result.CLb(), result.CLbError())) - pt_r.AddText('%.3f #pm %.3f' % (result.CLs(), result.CLsError())) - pt_r.SetTextAlign(11) - pt_r.SetTextFont(42) - pt_r.Draw() - canv.SaveAs('.pdf') - - -ROOT.PyConfig.IgnoreCommandLineOptions = True -ROOT.gROOT.SetBatch(ROOT.kTRUE) -ROOT.gSystem.Load('libHiggsAnalysisCombinedLimit') - -parser = argparse.ArgumentParser(prog='HybridNewGrid.py') -parser.add_argument('config', help="configuration json file") -parser.add_argument('--dry-run', action='store_true', help='Commands are echoed to the screen but not run') -parser.add_argument('--plots', action='store_true', help='Create plots of test statistic distributions') -parser.add_argument('--cycles', default=0, type=int, help='Number of job cycles to create per point') -(args, unknown) = parser.parse_known_args() - -DRY_RUN = args.dry_run - -with open(args.config) as json_file: - cfg = json.load(json_file) - -points = [] - -for igrid in cfg['grids']: - assert(len(igrid) == 2) - points.extend(itertools.product(split_vals(igrid[0]), split_vals(igrid[1]))) - -POIs = cfg['POIs'] - -file_dict = { } - -for p in points: - file_dict[p] = [] - -for f in glob.glob('higgsCombine.%s.*.%s.*.HybridNew.mH*.*.root' % (POIs[0], POIs[1])): - print f - rgx = re.compile('higgsCombine\.%s\.(?P.*)\.%s\.(?P.*)\.HybridNew\.mH.*\.(?P.*)\.root' % (POIs[0], POIs[1])) - matches = rgx.search(f) - p = (matches.group('p1'), matches.group('p2')) - if p in file_dict: - file_dict[p].append((f, int(matches.group('toy')))) - -for key,val in file_dict.iteritems(): - name = '%s.%s.%s.%s' % (POIs[0], key[0], POIs[1], key[1]) - print '>> Point %s' % name - res = GetCombinedHypoTest([x[0] for x in val]) - ok = ValidateHypoTest(res, 500, ['obs'], 5, 0.95) - if res is not None and args.plots: PlotTest(res, 'plot_'+name, True, 'm_{H}^{max} [m_{A} = %.1f, tan#beta = %.1f]' % (float(key[0]), float(key[1]))) - if not ok: - print 'Going to generate %i jobs for point %s' % (args.cycles, key) - done_cycles = [x[1] for x in val] - print 'Done cycles: ' + ','.join(str(x) for x in done_cycles) - new_idx = max(done_cycles)+1 if len(done_cycles) > 0 else 1 - new_cycles = range(new_idx, new_idx+args.cycles) - print 'New cycles: ' + ','.join(str(x) for x in new_cycles) - point_args = '-n .%s --setPhysicsModelParameters %s=%s,%s=%s --freezeNuisances %s,%s' % (name, POIs[0], key[0], POIs[1], key[1], POIs[0], POIs[1]) - for idx in new_cycles: - cmd = ' '.join(['combine -M HybridNew', cfg['opts'], point_args, '-T %i' % cfg['toys_per_cycle'], '-s %i' % idx]) - run(cmd) - - - - diff --git a/CombineTools/scripts/MSSMtanbPlot.py b/CombineTools/scripts/MSSMtanbPlot.py index 9d3d5c1cb6b..7d8d586d178 100644 --- a/CombineTools/scripts/MSSMtanbPlot.py +++ b/CombineTools/scripts/MSSMtanbPlot.py @@ -1,46 +1,8 @@ -import plotting as plot +import CombineHarvester.CombineTools.plotting as plot import ROOT import math import argparse -def fillTH2(hist2d, graph): - for x in xrange(1, hist2d.GetNbinsX()+1): - for y in xrange(1, hist2d.GetNbinsY()+1): - xc = hist2d.GetXaxis().GetBinCenter(x) - yc = hist2d.GetYaxis().GetBinCenter(y) - val = graph.Interpolate(xc, yc) - hist2d.SetBinContent(x, y, val) - -def makeHist(name, xbins, ybins, graph2d): - len_x = graph2d.GetXmax() - graph2d.GetXmin() - binw_x = (len_x * 0.5 / (float(xbins) - 1.)) - 1E-5 - len_y = graph2d.GetYmax() - graph2d.GetYmin() - binw_y = (len_y * 0.5 / (float(ybins) - 1.)) - 1E-5 - hist = ROOT.TH2F(name, '', xbins, graph2d.GetXmin()-binw_x, graph2d.GetXmax()+binw_x, ybins, graph2d.GetYmin()-binw_y, graph2d.GetYmax()+binw_y) - return hist - -def higgsConstraint(model, higgstype) : - if model=="low-tb-high": - massstep=10 - masslow=150 - masshigh=500 - nmass=int(((masshigh-masslow)/massstep-1)) - tanblow=0.5 - tanbhigh=9.5 - ntanb=int(((tanbhigh-tanblow)*10-1)) - - higgsBand=ROOT.TH2D("higgsBand", "higgsBand", nmass, masslow, masshigh, ntanb, tanblow, tanbhigh) - for mass in range (masslow, masshigh+1, massstep): - myfile = open("../../data/Higgs125/"+model+"/higgs_"+str(mass)+".dat", 'r') - for line in myfile: - tanb = (line.split())[0] - mh = float((line.split())[1]) - mH = float((line.split())[3]) - if higgstype=="h" : - higgsBand.SetBinContent(higgsBand.GetXaxis().FindBin(mass), higgsBand.GetYaxis().FindBin(tanb), mh); - elif higgstype=="H" : - higgsBand.SetBinContent(higgsBand.GetXaxis().FindBin(mass), higgsBand.GetYaxis().FindBin(tanb), mH); - return higgsBand col_store = [] def CreateTransparentColor(color, alpha): @@ -50,162 +12,149 @@ def CreateTransparentColor(color, alpha): col_store.append(trans) trans.SetName('userColor%i' % new_idx) return new_idx - + ROOT.gROOT.SetBatch(ROOT.kTRUE) parser = argparse.ArgumentParser() -parser.add_argument('--files', '-f', help='named input files') -parser.add_argument('--verbosity', '-v', help='verbosity') +parser.add_argument('--file', '-f', help='named input file') parser.add_argument('--scenario', '-s', help='scenario for plot label e.g. [mhmax,mhmodp,mhmodm,low-tb-high]') +parser.add_argument('--custom_y_range', help='Fix y axis range', default=False) +parser.add_argument('--y_axis_min', help='Fix y axis minimum', default=0.0) +parser.add_argument('--y_axis_max', help='Fix y axis maximum', default=60.0) +parser.add_argument('--custom_x_range', help='Fix x axis range', default=False) +parser.add_argument('--x_axis_min', help='Fix x axis minimum', default=90.0) +parser.add_argument('--x_axis_max', help='Fix x axis maximum', default=1000.0) +parser.add_argument('--verbosity', '-v', help='verbosity', default=0) args = parser.parse_args() -myfile = open(args.files, 'r') -filestring = '' -for line in myfile: - filestring += line - -infiles = filestring.split(',') -ROOT.gROOT.ProcessLine( - "struct staff_t {\ - Float_t quantileExpected;\ - Float_t mh;\ - Double_t limit;\ - }" ) - -n=0 -graph_exp = ROOT.TGraph2D() -graph_minus2sigma = ROOT.TGraph2D() -graph_minus1sigma = ROOT.TGraph2D() -graph_plus2sigma = ROOT.TGraph2D() -graph_plus1sigma = ROOT.TGraph2D() -graph_obs = ROOT.TGraph2D() -for f in infiles: - if plot.TFileIsGood(f) : - #First find the mA and tanb value indicated by the filename - mA = plot.ParamFromFilename(f, "mA") - tanb = plot.ParamFromFilename(f, "tanb") - # Extract the relevant values from the file. This implementation is lifted from HiggsAnalysis/HiggsToTauTau/scripts/extractSignificanceStats.py - file = ROOT.TFile(f, 'r') - tree = file.Get("limit") - staff = ROOT.staff_t() - tree.SetBranchAddress("quantileExpected",ROOT.AddressOf(staff,"quantileExpected")) - tree.SetBranchAddress("mh",ROOT.AddressOf(staff,"mh")) - tree.SetBranchAddress("limit",ROOT.AddressOf(staff,"limit")) - for i in range(tree.GetEntries()) : - tree.GetEntry(i); - if abs(staff.quantileExpected-0.025) < 0.01 : - minus2sigma=staff.limit - if abs(staff.quantileExpected-0.160) < 0.01 : - minus1sigma=staff.limit - if abs(staff.quantileExpected-0.500) < 0.01 : - exp=staff.limit - if abs(staff.quantileExpected-0.840) < 0.01 : - plus1sigma=staff.limit - if abs(staff.quantileExpected-0.975) < 0.01 : - plus2sigma=staff.limit - if abs(staff.quantileExpected+1.000) < 0.01 : - obs=staff.limit - if int(args.verbosity) > 0 : - print "Tested points: ", mA, tanb, minus2sigma, minus1sigma, exp, plus1sigma, plus2sigma, obs - ROOT.TFile.Close(file) - #Fill TGraphs with the values of each CLs - graph_exp.SetPoint(n, mA, tanb, exp) - graph_minus2sigma.SetPoint(n, mA, tanb, minus2sigma) - graph_minus1sigma.SetPoint(n, mA, tanb, minus1sigma) - graph_plus2sigma.SetPoint(n, mA, tanb, plus2sigma) - graph_plus1sigma.SetPoint(n, mA, tanb, plus1sigma) - graph_obs.SetPoint(n, mA, tanb, obs) - n=n+1 +#Store the mA and tanb list being used for the interpolation +file = ROOT.TFile(args.file, 'r') +print args.file +graph_obs = file.Get("observed") +graph_minus2sigma = file.Get("minus2sigma") +graph_minus1sigma = file.Get("minus1sigma") +graph_exp = file.Get("expected") +graph_plus1sigma = file.Get("plus1sigma") +graph_plus2sigma = file.Get("plus2sigma") + +mA_list=[] +tanb_list=[] +for i in range(graph_exp.GetN()) : + mA_list.append(float(graph_exp.GetX()[i])) + tanb_list.append(float(graph_exp.GetY()[i])) +tanb_list = sorted(set(tanb_list)) +mA_list = sorted(set(mA_list)) +tanb_bins=len(tanb_list) +mA_bins=len(mA_list) +if int(args.verbosity) > 0 : + print "mA_list: ", mA_list, "Total number: ", mA_bins + print "tanb_list: ", tanb_list, "Total number: ", tanb_bins #Create canvas and TH2D for each component -#Note the binning of the TH2D for the interpolation should match the initial input grid -plot.ModTDRStyle(width=800, l=0.13) -#plot.SetTDRStyle() +plot.ModTDRStyle(width=600, l=0.12) +#Slightly thicker frame to ensure contour edges dont overlay the axis +ROOT.gStyle.SetFrameLineWidth(2) c1=ROOT.TCanvas() -axis = makeHist('hist2d', 16, 45, graph_exp) +#axis = plot.makeHist2D('hist2d', mA_bins, tanb_bins, graph_exp) +axis = plot.makeVarBinHist2D('hist2d', mA_list, tanb_list) axis.GetYaxis().SetTitle("tan#beta") -axis.GetXaxis().SetTitle("m_{A}") -pads = plot.OnePad() -pads[0].Draw() -h_exp = makeHist("h_exp", 16, 45, graph_exp) -h_obs = makeHist("h_obs", 16, 45, graph_obs) -h_minus1sigma = makeHist("h_minus1sigma", 16, 45, graph_minus1sigma) -h_plus1sigma = makeHist("h_plus1sigma", 16, 45, graph_plus1sigma) -h_minus2sigma = makeHist("h_minus2sigma", 16, 45, graph_minus2sigma) -h_plus2sigma = makeHist("h_plus2sigma", 16, 45, graph_plus2sigma) -fillTH2(h_exp, graph_exp) -fillTH2(h_obs, graph_obs) -fillTH2(h_minus1sigma, graph_minus1sigma) -fillTH2(h_plus1sigma, graph_plus1sigma) -fillTH2(h_minus2sigma, graph_minus2sigma) -fillTH2(h_plus2sigma, graph_plus2sigma) +axis.GetXaxis().SetTitle("m_{A} (GeV)") +#Create two pads, one is just for the Legend +pad1 = ROOT.TPad("pad1","pad1",0,0.82,1,1) +pad1.SetFillStyle(4000) +pad1.Draw() +pad2 = ROOT.TPad("pad2","pad2",0,0,1,0.82) +pad2.SetFillStyle(4000) +pad2.Draw() +pads=[pad1,pad2] +pads[1].cd() + +#Note the binning of the TH2D for the interpolation should ~ match the initial input grid +#Could in future implement variable binning here +h_exp = plot.makeVarBinHist2D("h_exp", mA_list, tanb_list) +h_obs = plot.makeVarBinHist2D("h_obs", mA_list, tanb_list) +h_minus1sigma = plot.makeVarBinHist2D("h_minus1sigma", mA_list, tanb_list) +h_plus1sigma = plot.makeVarBinHist2D("h_plus1sigma", mA_list, tanb_list) +h_minus2sigma = plot.makeVarBinHist2D("h_minus2sigma", mA_list, tanb_list) +h_plus2sigma = plot.makeVarBinHist2D("h_plus2sigma", mA_list, tanb_list) +plot.fillTH2(h_exp, graph_exp) +plot.fillTH2(h_obs, graph_obs) +plot.fillTH2(h_minus1sigma, graph_minus1sigma) +plot.fillTH2(h_plus1sigma, graph_plus1sigma) +plot.fillTH2(h_minus2sigma, graph_minus2sigma) +plot.fillTH2(h_plus2sigma, graph_plus2sigma) axis.Draw() #Possibility to draw CLs heat map, would be a useful option, using e.g. -#h_obs.Draw("colzsame") - - -#Extract exclusion contours from the TH2Ds -cont_exp = plot.contourFromTH2(h_exp, 1.0, 20) -cont_obs = plot.contourFromTH2(h_obs, 1.0, 5) -cont_minus1sigma = plot.contourFromTH2(h_minus1sigma, 1.0, 20) -cont_plus1sigma = plot.contourFromTH2(h_plus1sigma, 1.0, 20) -cont_minus2sigma = plot.contourFromTH2(h_minus2sigma, 1.0, 20) -cont_plus2sigma = plot.contourFromTH2(h_plus2sigma, 1.0, 20) - -if args.scenario == "low-tb-high": - plane_higgsHBand = higgsConstraint(args.scenario, "H") -# plane_higgsBands.push_back(plane_higgsHBand); -# //lower edge entry 2 - cont_higgsHlow = plot.contourFromTH2(plane_higgsHBand, 260, 20) -# STestFunctor higgsHband0 = std::for_each( iter_higgsHlow.Begin(), TIter::End(), STestFunctor() ); -# for(int i=0; iAt(i));} -# gr_higgsBands.push_back(gr_higgsHlow); -# //upper edge entry 3 - cont_higgsHhigh = plot.contourFromTH2(plane_higgsHBand, 350, 20) -# STestFunctor higgsHband1 = std::for_each( iter_higgsHhigh.Begin(), TIter::End(), STestFunctor() ); -# for(int i=0; iAt(i));} -# gr_higgsBands.push_back(gr_higgsHhigh); -#} - -for p in cont_minus2sigma : +#h_exp.Draw("colzsame") + +#Extract exclusion contours from the TH2Ds, use threshold 1.0 for limit and 0.05 for CLs +threshold=0.05 +#threshold=1 +cont_exp = plot.contourFromTH2(h_exp, threshold, 20) +cont_obs = plot.contourFromTH2(h_obs, threshold, 5) +cont_minus1sigma = plot.contourFromTH2(h_minus1sigma, threshold, 20) +cont_plus1sigma = plot.contourFromTH2(h_plus1sigma, threshold, 20) +cont_minus2sigma = plot.contourFromTH2(h_minus2sigma, threshold, 20) +cont_plus2sigma = plot.contourFromTH2(h_plus2sigma, threshold, 20) + +#if args.scenario != "hMSSM" and "2HDM" not in args.scenario : +# graph_higgshBand = plot.higgsConstraint(args.scenario, "h") +# plane_higgshBand = plot.makeHist2D('plane_higgshBand', 36, 91, graph_higgshBand) +# plot.fillTH2(plane_higgshBand, graph_higgshBand) +# cont_higgshlow = plot.contourFromTH2(plane_higgshBand, 122, 5) +# cont_higgshhigh = plot.contourFromTH2(plane_higgshBand, 128, 5) +# cont_higgsh = plot.contourFromTH2(plane_higgshBand, 125, 5) + +if int(args.verbosity) > 0 : outf = ROOT.TFile('plotting_debug.root', 'RECREATE') +if int(args.verbosity) > 0 : outf.WriteTObject(h_minus2sigma, 'h_minus2sigma') +for i, p in enumerate(cont_minus2sigma): p.SetLineColor(0) p.SetFillColor(ROOT.kGray+1) p.SetFillStyle(1001) - pads[0].cd() + pads[1].cd() p.Draw("F SAME") + if int(args.verbosity) > 0 : outf.WriteTObject(p, 'graph_minus2sigma_%i'%i) -for p in cont_minus1sigma : +if int(args.verbosity) > 0 : outf.WriteTObject(h_minus1sigma, 'h_minus1sigma') +for i, p in enumerate(cont_minus1sigma): p.SetLineColor(0) p.SetFillColor(ROOT.kGray+2) p.SetFillStyle(1001) - pads[0].cd() + pads[1].cd() p.Draw("F SAME") + if int(args.verbosity) > 0 : outf.WriteTObject(p, 'graph_minus1sigma_%i'%i) -for p in cont_plus1sigma : +if int(args.verbosity) > 0 : outf.WriteTObject(h_plus1sigma, 'h_plus1sigma') +for i, p in enumerate(cont_plus1sigma): p.SetLineColor(0) p.SetFillColor(ROOT.kGray+1) p.SetFillStyle(1001) - pads[0].cd() + pads[1].cd() p.Draw("F SAME") + if int(args.verbosity) > 0 : outf.WriteTObject(p, 'graph_plus1sigma_%i'%i) -for p in cont_plus2sigma : +if int(args.verbosity) > 0 : outf.WriteTObject(h_plus2sigma, 'h_plus2sigma') +for i, p in enumerate(cont_plus2sigma): p.SetLineColor(0) p.SetFillColor(ROOT.kWhite) p.SetFillStyle(1001) - pads[0].cd() + pads[1].cd() p.Draw("F SAME") + if int(args.verbosity) > 0 : outf.WriteTObject(p, 'graph_plus2sigma_%i'%i) -for p in cont_exp : +if int(args.verbosity) > 0 : outf.WriteTObject(h_exp, 'h_exp') +for i, p in enumerate(cont_exp): p.SetLineColor(ROOT.kBlack) p.SetLineWidth(2) p.SetLineStyle(2) p.SetFillColor(100) - pads[0].cd() + pads[1].cd() p.Draw("L SAME") + if int(args.verbosity) > 0 : outf.WriteTObject(p, 'graph_exp_%i'%i) -for p in cont_obs : - print "cont_obs" +if int(args.verbosity) > 0 : outf.WriteTObject(h_obs, 'h_obs') +for i,p in enumerate(cont_obs): p.SetLineColor(ROOT.kBlack) p.SetLineWidth(2) p.SetMarkerStyle(20) @@ -213,32 +162,43 @@ def CreateTransparentColor(color, alpha): p.SetMarkerColor(ROOT.kBlack) p.SetFillStyle(1001) p.SetFillColor(CreateTransparentColor(ROOT.kAzure+6,0.5)) - pads[0].cd() + pads[1].cd() p.Draw("F SAME") p.Draw("L SAME") + if int(args.verbosity) > 0 : outf.WriteTObject(p, 'graph_obs_%i'%i) + +if int(args.verbosity) > 0 : outf.Close() + +#if args.scenario != "hMSSM" and "2HDM" not in args.scenario : +# if cont_higgshhigh : +# for p in cont_higgshhigh: +# p.SetLineWidth(-402) +# p.SetFillStyle(3005) +# p.SetFillColor(ROOT.kRed) +# p.SetLineColor(ROOT.kRed) +# pads[1].cd() +# p.Draw("L SAME") + +# if cont_higgshlow : +# for p in cont_higgshlow: +# p.SetLineWidth(402) +# p.SetFillStyle(3005) +# p.SetFillColor(ROOT.kRed) +# p.SetLineColor(ROOT.kRed) +# pads[1].cd() +# p.Draw("L SAME") + +# if cont_higgsh : +# for p in cont_higgsh: +# p.SetLineWidth(2) +# p.SetLineColor(ROOT.kRed) +# p.SetLineStyle(7) +# pads[1].cd() +# p.Draw("L SAME") -for p in cont_higgsHlow: - print "higgsHlow" - p.SetLineWidth(9902) - p.SetFillStyle(1001) - p.SetFillColor(ROOT.kGreen) - p.SetLineColor(ROOT.kGreen+3) - p.SetLineStyle(3) - #p.Draw("L SAME") - #p.Draw("F SAME") - -for p in cont_higgsHhigh: - print "higgsHhigh" - p.SetLineWidth(-9902) - p.SetFillStyle(1001) - p.SetFillColor(ROOT.kGreen) - p.SetLineColor(ROOT.kGreen+3) - p.SetLineStyle(3) - #p.Draw("L SAME") - #p.Draw("F SAME") #Set some common scenario labels -scenario_label="" +scenario_label=args.scenario if args.scenario == "mhmax": scenario_label="m_{h}^{max} scenario" if args.scenario == "mhmodp": @@ -247,9 +207,39 @@ def CreateTransparentColor(color, alpha): scenario_label="m_{h}^{mod-} scenario" if args.scenario == "low-tb-high": scenario_label="low tan#beta scenario" - - -plot.DrawCMSLogo(pads[0], 'Combine Harvester', scenario_label, 11, 0.045, 0.035, 1.2) +if args.scenario == "hMSSM": + scenario_label="hMSSM scenario" + + +pads[0].cd() +legend = plot.PositionedLegend(0.5,0.9,2,0.03) +legend.SetNColumns(2) +legend.SetFillStyle(1001) +legend.SetTextSize(0.15) +legend.SetTextFont(62) +legend.SetHeader("95% CL Excluded:") +# Stupid hack to get legend entry looking correct +if cont_obs[0] : + alt_cont_obs = cont_obs[0].Clone() + alt_cont_obs.SetLineColor(ROOT.kWhite) + legend.AddEntry(alt_cont_obs,"Observed", "F") +if cont_minus1sigma[0] : legend.AddEntry(cont_minus1sigma[0], "#pm 1#sigma Expected", "F") +if cont_exp[0] : legend.AddEntry(cont_exp[0],"Expected", "L") +if cont_minus2sigma[0] : legend.AddEntry(cont_minus2sigma[0], "#pm 2#sigma Expected", "F") +legend.Draw("same") + +# ROOT is just the worst - ARC requested the observed symbol in the legend be changed to this +if(cont_obs[0]) : + legline = ROOT.TLine(605, 13, 680, 13) + legline.SetLineWidth(3) + legline.SetLineColor(ROOT.kBlack) + legline.DrawLineNDC(legend.GetX1()+0.0106, legend.GetY2()-0.36, legend.GetX1()+0.0516, legend.GetY2()-0.36) + +if args.custom_y_range : axis.GetYaxis().SetRangeUser(float(args.y_axis_min), float(args.y_axis_max)) +axis.GetXaxis().SetTitle("m_{A} (GeV)") +if args.custom_x_range : axis.GetXaxis().SetRangeUser(float(args.x_axis_min), float(args.x_axis_max)) +plot.DrawCMSLogo(pads[1], 'Combine Harvester', scenario_label, 11, 0.045, 0.035, 1.2) +plot.DrawTitle(pads[1], '19.7 fb^{-1} (8 TeV)', 3); plot.FixOverlay() c1.SaveAs("mssm_"+args.scenario+".pdf") c1.SaveAs("mssm_"+args.scenario+".png") diff --git a/CombineTools/scripts/SMLegacyExample.py b/CombineTools/scripts/SMLegacyExample.py index 08f1756601a..bf48589b46a 100755 --- a/CombineTools/scripts/SMLegacyExample.py +++ b/CombineTools/scripts/SMLegacyExample.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import CombineHarvester.CombineTools.combineharvester as ch +import CombineHarvester.CombineTools.ch as ch import CombineHarvester.CombineTools.systematics.SMLegacy as SMLegacySysts import ROOT as R import glob diff --git a/CombineTools/scripts/combineTool.py b/CombineTools/scripts/combineTool.py index 020e93c13f5..de87956c17c 100755 --- a/CombineTools/scripts/combineTool.py +++ b/CombineTools/scripts/combineTool.py @@ -7,7 +7,7 @@ from CombineHarvester.CombineTools.combine.Impacts import Impacts from CombineHarvester.CombineTools.combine.Workspace import PrintWorkspace, ModifyDataSet from CombineHarvester.CombineTools.combine.CovMatrix import CovMatrix -from CombineHarvester.CombineTools.combine.LimitGrids import AsymptoticGrid +from CombineHarvester.CombineTools.combine.LimitGrids import AsymptoticGrid, HybridNewGrid, Limit1D from CombineHarvester.CombineTools.combine.Output import PrintSingles, CollectLimits ROOT.PyConfig.IgnoreCommandLineOptions = True @@ -34,6 +34,8 @@ def register_method(parser, method_dict, method_class): register_method(parser, methods, CovMatrix) register_method(parser, methods, PrintSingles) register_method(parser, methods, AsymptoticGrid) +register_method(parser, methods, HybridNewGrid) +register_method(parser, methods, Limit1D) parser.add_argument('-M', '--method') diff --git a/CombineTools/scripts/do_nothing_cfg.py b/CombineTools/scripts/do_nothing_cfg.py new file mode 100644 index 00000000000..8955f446708 --- /dev/null +++ b/CombineTools/scripts/do_nothing_cfg.py @@ -0,0 +1,5 @@ +import FWCore.ParameterSet.Config as cms +process = cms.Process("MAIN") + +process.source = cms.Source("PoolSource", fileNames = cms.untracked.vstring()) + diff --git a/CombineTools/scripts/plotBSMxsBRLimit.py b/CombineTools/scripts/plotBSMxsBRLimit.py new file mode 100644 index 00000000000..9cbaa75243c --- /dev/null +++ b/CombineTools/scripts/plotBSMxsBRLimit.py @@ -0,0 +1,145 @@ +import CombineHarvester.CombineTools.plotting as plot +import CombineHarvester.CombineTools.maketable as maketable +import ROOT +import math +import argparse +import json + +ROOT.gROOT.SetBatch(ROOT.kTRUE) +parser = argparse.ArgumentParser() +parser.add_argument('--file', '-f', help='named input file') +parser.add_argument('--process', help='The process on which a limit has been calculated. [gg#phi, bb#phi]', default="gg#phi") +parser.add_argument('--custom_y_range', help='Fix y axis range', default=False) +parser.add_argument('--y_axis_min', help='Fix y axis minimum', default=0.001) +parser.add_argument('--y_axis_max', help='Fix y axis maximum', default=100.0) +parser.add_argument('--custom_x_range', help='Fix x axis range', default=False) +parser.add_argument('--x_axis_min', help='Fix x axis minimum', default=90.0) +parser.add_argument('--x_axis_max', help='Fix x axis maximum', default=1000.0) +parser.add_argument('--verbosity', '-v', help='verbosity', default=0) +parser.add_argument('--log', help='Set log range for x and y axis', default=False) +#parser.add_argument('--table_vals', help='Amount of values to be written in a table for different masses', default=10) +args = parser.parse_args() + + +#Store the mass list convert from json file or directly via tgraphs +graph_obs = ROOT.TGraph() +graph_minus2sigma = ROOT.TGraph() +graph_minus1sigma = ROOT.TGraph() +graph_exp = ROOT.TGraph() +graph_plus1sigma = ROOT.TGraph() +graph_plus2sigma = ROOT.TGraph() + +if ".root" in args.file : + file = ROOT.TFile(args.file, 'r') + graph_obs = plot.SortGraph(file.Get("observed")) + graph_minus2sigma = plot.SortGraph(file.Get("minus2sigma")) + graph_minus1sigma = plot.SortGraph(file.Get("minus1sigma")) + graph_exp = plot.SortGraph(file.Get("expected")) + graph_plus1sigma = plot.SortGraph(file.Get("plus1sigma")) + graph_plus2sigma = plot.SortGraph(file.Get("plus2sigma")) + maketable.Tablefrom1DGraph(args.file, "mssm_limit_table.txt") +else : + data = {} + with open(args.file) as jsonfile: + data = json.load(jsonfile) + graph_obs = plot.LimitTGraphFromJSON(data, 'observed') + graph_minus2sigma = plot.LimitTGraphFromJSON(data, '-2') + graph_minus1sigma = plot.LimitTGraphFromJSON(data, '-1') + graph_exp = plot.LimitTGraphFromJSON(data, 'expected') + graph_plus1sigma = plot.LimitTGraphFromJSON(data, '+1') + graph_plus2sigma = plot.LimitTGraphFromJSON(data, '+2') + maketable.TablefromJson(args.file, "mssm_limit_table.txt") + +process_label=args.process + +mass_list=[] +for i in range(graph_exp.GetN()) : + mass_list.append(float(graph_exp.GetX()[i])) +mass_list = sorted(set(mass_list)) +mass_bins=len(mass_list) +if int(args.verbosity) > 0 : + print "mass_list: ", mass_list, "Total number: ", mass_bins + +#Create canvas and TH1D +plot.ModTDRStyle(width=600, l=0.12) +ROOT.gStyle.SetFrameLineWidth(2) +c1=ROOT.TCanvas() +axis = plot.makeHist1D('hist1d', mass_bins, graph_exp) +if process_label == "gg#phi" : + axis.GetYaxis().SetTitle("95% CL limit on #sigma#font[42]{(gg#phi)}#upoint#font[52]{B}#font[42]{(#phi#rightarrow#tau#tau)} [pb]") +elif process_label == "bb#phi" : + axis.GetYaxis().SetTitle("95% CL limit on #sigma#font[42]{(bb#phi)}#upoint#font[52]{B}#font[42]{(#phi#rightarrow#tau#tau)} [pb]") +else: + exit("Currently process is not supported") +if args.custom_y_range : axis.GetYaxis().SetRangeUser(float(args.y_axis_min), float(args.y_axis_max)) +axis.GetXaxis().SetTitle("m_{#phi} [GeV]") +if args.custom_x_range : axis.GetXaxis().SetRangeUser(float(args.x_axis_min), float(args.x_axis_max)) +#Create two pads, one is just for the Legend +pad_leg = ROOT.TPad("pad_leg","pad_leg",0,0.82,1,1) +pad_leg.SetFillStyle(4000) +pad_leg.Draw() +pad_plot = ROOT.TPad("pad_plot","pad_plot",0,0,1,0.82) +pad_plot.SetFillStyle(4000) +pad_plot.Draw() +pads=[pad_leg,pad_plot] +pads[1].cd() +if args.log : + pad_plot.SetLogx(1); + pad_plot.SetLogy(1); + axis.SetNdivisions(50005, "X"); + axis.GetXaxis().SetMoreLogLabels(); + axis.GetXaxis().SetNoExponent(); + axis.GetXaxis().SetLabelSize(0.040); +axis.Draw() + +innerBand=plot.MakeErrorBand(graph_minus1sigma, graph_plus1sigma) +outerBand=plot.MakeErrorBand(graph_minus2sigma, graph_plus2sigma) + +outerBand.SetLineWidth(1) +outerBand.SetLineColor(ROOT.kBlack); +# if(injected) outerBand->SetFillColor(kAzure-9); +# else if(BG_Higgs) outerBand->SetFillColor(kSpring+5); +outerBand.SetFillColor(ROOT.TColor.GetColor(252,241,15)) +outerBand.Draw("3") + +innerBand.SetLineWidth(1); +innerBand.SetLineColor(ROOT.kBlack); +# if(injected) innerBand->SetFillColor(kAzure-4); +# else if(BG_Higgs) innerBand->SetFillColor(kGreen+2); +innerBand.SetFillColor(ROOT.kGreen); +innerBand.Draw("3same"); + +graph_exp.SetLineColor(ROOT.kRed); +graph_exp.SetLineWidth(3); +graph_exp.SetLineStyle(1); +# if(mssm_log){ +# expected->SetLineColor(kBlack); +# expected->SetLineStyle(2); +# } +graph_exp.Draw("L"); + +graph_obs.SetMarkerColor(ROOT.kBlack); +graph_obs.SetMarkerSize(1.0); +graph_obs.SetMarkerStyle(20); +graph_obs.SetLineWidth(3); +graph_obs.Draw("PLsame"); + +pads[0].cd() +legend = plot.PositionedLegend(0.5,0.9,2,0.03) +legend.SetNColumns(2) +legend.SetFillStyle(1001) +legend.SetTextSize(0.15) +legend.SetTextFont(62) +legend.SetHeader("95% CL Excluded:") +legend.AddEntry(graph_obs,"Observed", "L") +legend.AddEntry(innerBand, "#pm 1#sigma Expected", "F") +legend.AddEntry(graph_exp,"Expected", "L") +legend.AddEntry(outerBand, "#pm 2#sigma Expected", "F") +legend.Draw("same") + +plot.DrawCMSLogo(pads[1], '', '', 11, 0.045, 0.035, 1.2) +plot.DrawTitle(pads[1], '19.7 fb^{-1} (8 TeV)', 3); +plot.FixOverlay() +c1.SaveAs("mssm_limit.pdf") +c1.SaveAs("mssm_limit.png") + diff --git a/CombineTools/scripts/plotImpacts.py b/CombineTools/scripts/plotImpacts.py new file mode 100755 index 00000000000..62f216637cd --- /dev/null +++ b/CombineTools/scripts/plotImpacts.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +import ROOT +import math +import json +import argparse +import CombineHarvester.CombineTools.plotting as plot + +ROOT.PyConfig.IgnoreCommandLineOptions = True +ROOT.gROOT.SetBatch(ROOT.kTRUE) +ROOT.TH1.AddDirectory(0) + +parser = argparse.ArgumentParser() +parser.add_argument('--input', '-i', help='input json file') +parser.add_argument('--output', '-o', help='name of the output file to create') +parser.add_argument('--translate', '-t', help='JSON file for remapping of parameter names') +parser.add_argument('--per-page', type=int, default=30, help='Number of parameters to show per page') +parser.add_argument('--cms-label', default='Internal', help='Label next to the CMS logo') +args = parser.parse_args() + + +def Translate(name, ndict): + return ndict[name] if name in ndict else name + +# Dictionary to translate parameter names +translate = {} +if args.translate is not None: + with open(args.translate) as jsonfile: + translate = json.load(jsonfile) + +# Load the json output of combineTool.py -M Impacts +data = {} +with open(args.input) as jsonfile: + data = json.load(jsonfile) + +# Set the global plotting style +plot.ModTDRStyle(l=0.4, b=0.10, width=700) + +# We will assume the first POI is the one to plot +POIs = [ele['name'] for ele in data['POIs']] +POI_fit = data['POIs'][0]['fit'] + +# Sort parameters by largest absolute impact on this POI +data['params'].sort(key=lambda x: abs(x['impact_%s' % POIs[0]]), reverse=True) + +# Set the number of parameters per page (show) and the number of pages (n) +show = args.per_page +n = int(math.ceil(float(len(data['params'])) / float(show))) + +for page in xrange(n): + canv = ROOT.TCanvas(args.output, args.output) + n_params = len(data['params'][show * page:show * (page + 1)]) + pdata = data['params'][show * page:show * (page + 1)] + print '>> Doing page %i, have %i parameters' % (page, n_params) + + boxes = [] + for i in xrange(n_params): + y1 = ROOT.gStyle.GetPadBottomMargin() + y2 = 1. - ROOT.gStyle.GetPadTopMargin() + h = (y2 - y1) / float(n_params) + y1 = y1 + float(i) * h + y2 = y1 + h + box = ROOT.TPaveText(0, y1, 1, y2, 'NDC') + plot.Set(box, TextSize=0.02, BorderSize=0, FillColor=0, TextAlign=12, Margin=0.005) + if i % 2 == 0: + box.SetFillColor(18) + box.AddText('%i' % (n_params - i + page * show)) + box.Draw() + boxes.append(box) + + # Crate and style the pads + pads = plot.TwoPadSplitColumns(0.7, 0., 0.) + pads[0].SetGrid(1, 0) + pads[0].SetTickx(1) + pads[1].SetGrid(1, 0) + pads[1].SetTickx(1) + + h_pulls = ROOT.TH2F("pulls", "pulls", 6, -2.9, 2.9, n_params, 0, n_params) + g_pulls = ROOT.TGraphAsymmErrors(n_params) + g_impacts_hi = ROOT.TGraphAsymmErrors(n_params) + g_impacts_lo = ROOT.TGraphAsymmErrors(n_params) + max_impact = 0. + for p in xrange(n_params): + i = n_params - (p + 1) + pre = pdata[p]['prefit'] + pre_err = (pre[2] - pre[0]) / 2. + fit = pdata[p]['fit'] + imp = pdata[p][POIs[0]] + g_pulls.SetPoint(i, (fit[1] - pre[1]) / pre_err, float(i) + 0.5) + g_pulls.SetPointError( + i, (fit[1] - fit[0]) / pre_err, (fit[2] - fit[1]) / pre_err, 0., 0.) + g_impacts_hi.SetPoint(i, 0, float(i) + 0.5) + g_impacts_lo.SetPoint(i, 0, float(i) + 0.5) + g_impacts_hi.SetPointError(i, 0, imp[2] - imp[1], 0.5, 0.5) + g_impacts_lo.SetPointError(i, imp[1] - imp[0], 0, 0.5, 0.5) + max_impact = max( + max_impact, abs(imp[1] - imp[0]), abs(imp[2] - imp[1])) + h_pulls.GetYaxis().SetBinLabel( + i + 1, (Translate(pdata[p]['name'], translate))) + + # Style and draw the pulls histo + plot.Set(h_pulls.GetXaxis(), TitleSize=0.04, LabelSize=0.03, Title='(#hat{#theta}-#theta_{0})/#Delta#theta') + plot.Set(h_pulls.GetYaxis(), LabelSize=0.022, TickLength=0.0) + h_pulls.GetYaxis().LabelsOption('v') + h_pulls.Draw() + + # Go to the other pad and draw the impacts histo + pads[1].cd() + h_impacts = ROOT.TH2F( + "impacts", "impacts", 6, -max_impact * 1.1, max_impact * 1.1, n_params, 0, n_params) + plot.Set(h_impacts.GetXaxis(), LabelSize=0.03, TitleSize=0.04, Ndivisions=505, Title= + '#Delta#hat{%s}' % (Translate(POIs[0], translate))) + plot.Set(h_impacts.GetYaxis(), LabelSize=0, TickLength=0.0) + h_impacts.Draw() + + # Back to the first pad to graph the pulls graph + pads[0].cd() + plot.Set(g_pulls, MarkerSize=0.8, LineWidth=2) + g_pulls.Draw('PSAME') + + # And back to the second pad to draw the impacts graphs + pads[1].cd() + g_impacts_hi.SetFillColor(46) + g_impacts_hi.Draw('2SAME') + g_impacts_lo.SetFillColor(38) + g_impacts_lo.Draw('2SAME') + pads[1].RedrawAxis() + + legend = ROOT.TLegend(0.02, 0.02, 0.40, 0.06, '', 'NBNDC') + legend.SetNColumns(3) + legend.AddEntry(g_pulls, 'Pull', 'LP') + legend.AddEntry(g_impacts_hi, '+1#sigma Impact', 'F') + legend.AddEntry(g_impacts_lo, '-1#sigma Impact', 'F') + legend.Draw() + + plot.DrawCMSLogo(pads[0], 'CMS', args.cms_label, 0, 0.25, 0.00, 0.00) + plot.DrawTitle(pads[1], '#hat{%s} = %.3g #pm %.3g' % ( + Translate(POIs[0], translate), POI_fit[1], (POI_fit[2] - POI_fit[1]) / 2.), 3) + extra = '' + if page == 0: + extra = '(' + if page == n - 1: + extra = ')' + canv.Print('.pdf%s' % extra) diff --git a/CombineTools/scripts/plotLimits.py b/CombineTools/scripts/plotLimits.py index d01ff037158..d13ff1f6195 100755 --- a/CombineTools/scripts/plotLimits.py +++ b/CombineTools/scripts/plotLimits.py @@ -3,36 +3,17 @@ import ROOT import math # from functools import partial -import Tools.Plotting.plotting as plot +import CombineHarvester.CombineTools.plotting as plot import json -# import argparse +import argparse # import os.path from array import array +import CombineHarvester.CombineTools.maketable as maketable - -def LimitTGraphFromJSON(js, label): - xvals = [] - yvals = [] - for key in js: - xvals.append(float(key)) - yvals.append(js[key][label]) - graph = ROOT.TGraph(len(xvals), array('d', xvals), array('d', yvals)) - graph.Sort() - return graph - -def LimitBandTGraphFromJSON(js, central, lo, hi): - xvals = [] - yvals = [] - yvals_lo = [] - yvals_hi = [] - for key in js: - xvals.append(float(key)) - yvals.append(js[key][central]) - yvals_lo.append(js[key][central] - js[key][lo]) - yvals_hi.append(js[key][hi] - js[key][central]) - graph = ROOT.TGraphAsymmErrors(len(xvals), array('d', xvals), array('d', yvals), array('d', [0]), array('d', [0]), array('d', yvals_lo), array('d', yvals_hi)) - graph.Sort() - return graph +parser = argparse.ArgumentParser() +parser.add_argument('--file', '-f', help='named input file') +parser.add_argument('--table_vals', help='Amount of values to be written in a table for different masses', default=10) +args = parser.parse_args() ROOT.PyConfig.IgnoreCommandLineOptions = True @@ -47,20 +28,20 @@ def LimitBandTGraphFromJSON(js, central, lo, hi): pads = plot.OnePad() data = {} -with open('cmb_limits.json') as jsonfile: +with open(args.file) as jsonfile: data = json.load(jsonfile) -g_obs = LimitTGraphFromJSON(data, 'observed') +g_obs = plot.LimitTGraphFromJSON(data, 'observed') g_obs.SetLineWidth(2) axishist = plot.CreateAxisHist(g_obs, True) -g_exp = LimitTGraphFromJSON(data, 'expected') +g_exp = plot.LimitTGraphFromJSON(data, 'expected') g_exp.SetLineWidth(2) g_exp.SetLineColor(ROOT.kRed) # g_exp.SetLineStyle(9) -g_exp1 = LimitBandTGraphFromJSON(data, 'expected', '-1', '+1') +g_exp1 = plot.LimitBandTGraphFromJSON(data, 'expected', '-1', '+1') g_exp1.SetFillColor(ROOT.kGreen) -g_exp2 = LimitBandTGraphFromJSON(data, 'expected', '-2', '+2') +g_exp2 = plot.LimitBandTGraphFromJSON(data, 'expected', '-2', '+2') g_exp2.SetFillColor(ROOT.kYellow) # print ROOT.gPad @@ -93,7 +74,7 @@ def LimitBandTGraphFromJSON(js, central, lo, hi): legend.Draw() -plot.DrawCMSLogo(pads[0], 'CMS', 'Internal', 11, 0.045, 0.035, 1.0, '', 1.0) +plot.DrawCMSLogo(pads[0], 'CMS', 'Internal', 11, 0.045, 0.035, 1.0) plot.DrawTitle(pads[0], '4.9 fb^{-1} (7 TeV) + 19.7 fb^{-1} (8 TeV)', 3) plot.DrawTitle(pads[0], 'H#rightarrow#tau#tau', 1) @@ -103,3 +84,4 @@ def LimitBandTGraphFromJSON(js, central, lo, hi): canv.Print('.pdf') canv.Print('.png') # canv.Print('.C') +maketable.TablefromJson(args.table_vals, args.file, "TablefromJson.txt") diff --git a/CombineTools/src/AZhSystematics.cc b/CombineTools/src/AZhSystematics.cc new file mode 100644 index 00000000000..01bebf1d5c8 --- /dev/null +++ b/CombineTools/src/AZhSystematics.cc @@ -0,0 +1,201 @@ +#include +#include +#include "CombineHarvester/CombineTools/interface/Systematics.h" +#include "CombineHarvester/CombineTools/interface/Process.h" +#include "CombineHarvester/CombineTools/interface/Utilities.h" +#include "CombineHarvester/CombineTools/interface/HttSystematics.h" + +namespace ch { + +using ch::syst::SystMap; +using ch::syst::SystMapAsymm; +using ch::syst::era; +using ch::syst::channel; +using ch::syst::bin_id; +using ch::syst::process; +using ch::JoinStr; + + + +void AddSystematics_AZh(CombineHarvester & cb, CombineHarvester src) { + src.channel({"et","mt","em","tt"}); + //category 0 = ee, 1 = mm + + auto signal = Set2Vec(cb.cp().signals().process_set()); + + src.cp().process(JoinStr({signal,{"GGToZZ2L2L","ZH_ww125","ZH_ww125","ZZ","TTZ","WWZ","WZZ","ZZZ"}})) + .AddSyst(cb, "lumi_8TeV", "lnN", SystMap<>::init + (1.026)); + + src.cp().process(JoinStr({signal,{"GGToZZ2L2L","ZH_ww125","ZH_ww125","ZZ","TTZ","WWZ","WZZ","ZZZ"}})) + .AddSyst(cb, "CMS_eff_e_8TeV", "lnN", SystMap::init + ({"et"}, {0}, 1.06) + ({"et"}, {1}, 1.02) + ({"em"}, {0}, 1.06) + ({"em"}, {1}, 1.02) + ({"mt"}, {0}, 1.04) + ({"tt"}, {0}, 1.04)); + + src.cp().process(JoinStr({signal,{"GGToZZ2L2L","ZH_ww125","ZH_ww125","ZZ","TTZ","WWZ","WZZ","ZZZ"}})) + .AddSyst(cb, "CMS_eff_m_8TeV", "lnN", SystMap::init + ({"et"}, {1}, 1.04) + ({"em"}, {0}, 1.02) + ({"em"}, {1}, 1.06) + ({"mt"}, {0}, 1.02) + ({"mt"}, {1}, 1.06) + ({"tt"}, {1}, 1.04)); + + src.cp().process(JoinStr({signal,{"GGToZZ2L2L","ZH_ww125","ZH_ww125","ZZ","TTZ","WWZ","WZZ","ZZZ"}})).channel({"et","mt"}) + .AddSyst(cb, "CMS_eff_t_llet_8TeV", "lnN", SystMap<>::init + (1.06)); + + src.cp().process(JoinStr({signal,{"GGToZZ2L2L","ZH_ww125","ZH_ww125","ZZ","TTZ","WWZ","WZZ","ZZZ"}})).channel({"tt"}) + .AddSyst(cb, "CMS_eff_t_lltt_8TeV", "lnN", SystMap<>::init + (1.12)); + + src.cp().process(JoinStr({signal,{"GGToZZ2L2L","ZH_ww125","ZH_ww125","ZZ","TTZ","WWZ","WZZ","ZZZ"}})).bin_id({0}) + .AddSyst(cb, "CMS_trigger_e_8TeV", "lnN", SystMap<>::init + (1.01)); + + src.cp().process(JoinStr({signal,{"GGToZZ2L2L","ZH_ww125","ZH_ww125","ZZ","TTZ","WWZ","WZZ","ZZZ"}})).bin_id({1}) + .AddSyst(cb, "CMS_trigger_m_8TeV", "lnN", SystMap<>::init + (1.01)); + + src.cp().process(JoinStr({signal,{"GGToZZ2L2L","ZH_ww125","ZH_ww125","ZZ","TTZ","WWZ","WZZ","ZZZ"}})) + .AddSyst(cb, "CMS_fake_b_8TeV", "lnN", SystMap<>::init + (1.01)); + + src.cp().process({"ZZ"}) + .AddSyst(cb, "QCDscale_VV", "lnN", SystMap<>::init + (1.06)); + + src.cp().process({"ZZ"}) + .AddSyst(cb, "pdf_qqbar", "lnN", SystMap<>::init + (1.06)); + + src.cp().process({"ZZ_tt125"}) + .AddSyst(cb, "CMS_htt_SM125_mu", "lnN", SystMap<>::init + (1.30)); + + src.cp().process({"ZZ_ww125"}) + .AddSyst(cb, "CMS_hww_SM125_mu", "lnN", SystMap<>::init + (1.20)); + + src.cp().process({"ZZ_tt125,ZZ_ww125"}) + .AddSyst(cb, "QCDscale_VH", "lnN", SystMap<>::init + (1.031)); + + src.cp().process({"ZZ_tt125,ZZ_ww125"}) + .AddSyst(cb, "pdf_qqbar", "lnN", SystMap<>::init + (1.025)); + + src.cp().process({"Zjets"}).channel({"et","mt"}) + .AddSyst(cb, "CMS_zh2l2tau_ZjetBkg_lt_extrap_8TeV", "lnN", SystMap<>::init + (1.20)); + + src.cp().process({"Zjets"}).channel({"em"}) + .AddSyst(cb, "CMS_zh2l2tau_ZjetBkg_emu_extrap_8TeV", "lnN", SystMap<>::init + (1.50)); + + src.cp().process({"Zjets"}).channel({"tt"}) + .AddSyst(cb, "CMS_zh2l2tau_ZjetBkg_tt_extrap_8TeV", "lnN", SystMap<>::init + (1.15)); + + src.cp().process({"Zjets"}).channel({"mt"}) + .AddSyst(cb, "CMS_zh2l2tau_ZjetBkg_mu_extrap_8TeV", "lnN", SystMap<>::init + (1.10)); + + src.cp().process({"Zjets"}).channel({"et"}) + .AddSyst(cb, "CMS_zh2l2tau_ZjetBkg_e_extrap_8TeV", "lnN", SystMap<>::init + (1.10)); + + src.cp().process(JoinStr({signal,{"ZZ","GGToZZ2L2L","TTZ","WWZ","WZZ","ZZZ","ZH_ww125","ZH_tt125"}})).channel({"et"}) + .AddSyst(cb, "CMS_scale_t_llet_8TeV", "shape", SystMap<>::init + (1.00)); + + src.cp().process(JoinStr({signal,{"ZZ","GGToZZ2L2L","TTZ","WWZ","WZZ","ZZZ","ZH_ww125","ZH_tt125"}})).channel({"mt"}) + .AddSyst(cb, "CMS_scale_t_llmt_8TeV", "shape", SystMap<>::init + (1.00)); + + src.cp().process(JoinStr({signal,{"ZZ","GGToZZ2L2L","TTZ","WWZ","WZZ","ZZZ","ZH_ww125","ZH_tt125"}})).channel({"tt"}) + .AddSyst(cb, "CMS_scale_t_lltt_8TeV", "shape", SystMap<>::init + (1.00)); + + src.cp().process({"GGToZZ2L2L"}) + .AddSyst(cb, "CMS_zh2l2tau_GGZZ2L2LBkg_8TeV", "lnN", SystMap<>::init + (1.44)); + + src.cp().process({"TTZ"}) + .AddSyst(cb, "CMS_zh2l2tau_TTZBkg_8TeV", "lnN", SystMap<>::init + (1.50)); + + src.cp().process({"GGToZZ2L2L"}).channel({"em"}) + .AddSyst(cb, "CMS_vhtt_ggZZNorm_em_8TeV", "lnN", SystMap<>::init + (1.05)); + + src.cp().process({"TTZ"}).channel({"em"}) + .AddSyst(cb, "CMS_vhtt_ttZNorm_em_8TeV", "lnN", SystMap<>::init + (1.15)); + + src.cp().process({"ZZ"}).channel({"em"}) + .AddSyst(cb, "CMS_vhtt_zzNorm_em_8TeV", "lnN", SystMap<>::init + (1.05)); + + src.cp().process({"WWZ"}) + .AddSyst(cb, "CMS_vhtt_wwzNorm_8TeV", "lnN", SystMap<>::init + (1.50)); + + src.cp().process({"WZZ"}) + .AddSyst(cb, "CMS_vhtt_wzzNorm_8TeV", "lnN", SystMap<>::init + (1.50)); + + src.cp().process({"ZZZ"}) + .AddSyst(cb, "CMS_vhtt_zzzNorm_8TeV", "lnN", SystMap<>::init + (1.50)); + + src.cp().process({"GGToZZ2L2L"}).channel({"mt"}) + .AddSyst(cb, "CMS_vhtt_ggZZNorm_mt_8TeV", "lnN", SystMap<>::init + (1.05)); + + src.cp().process({"TTZ"}).channel({"mt"}) + .AddSyst(cb, "CMS_vhtt_ttZNorm_mt_8TeV", "lnN", SystMap<>::init + (1.30)); + + src.cp().process({"ZZ"}).channel({"mt"}) + .AddSyst(cb, "CMS_vhtt_zzNorm_mt_8TeV", "lnN", SystMap<>::init + (1.05)); + + src.cp().process({"GGToZZ2L2L"}).channel({"et"}) + .AddSyst(cb, "CMS_vhtt_ggZZNorm_et_8TeV", "lnN", SystMap<>::init + (1.05)); + + src.cp().process({"TTZ"}).channel({"et"}) + .AddSyst(cb, "CMS_vhtt_ttZNorm_et_8TeV", "lnN", SystMap<>::init + (1.30)); + + src.cp().process({"ZZ"}).channel({"et"}) + .AddSyst(cb, "CMS_vhtt_zzNorm_et_8TeV", "lnN", SystMap<>::init + (1.05)); + + src.cp().process({"GGToZZ2L2L"}).channel({"tt"}) + .AddSyst(cb, "CMS_vhtt_ggZZNorm_tt_8TeV", "lnN", SystMap<>::init + (1.05)); + + src.cp().process({"TTZ"}).channel({"tt"}) + .AddSyst(cb, "CMS_vhtt_ttZNorm_tt_8TeV", "lnN", SystMap<>::init + (1.30)); + + src.cp().process({"ZZ"}).channel({"tt"}) + .AddSyst(cb, "CMS_vhtt_zzNorm_tt_8TeV", "lnN", SystMap<>::init + (1.05)); + +} + +void AddSystematics_AZh(CombineHarvester & cb) { + + CombineHarvester src = cb.cp(); + AddSystematics_AZh(cb, src); + +} +} + diff --git a/CombineTools/src/CombineHarvester_Datacards.cc b/CombineTools/src/CombineHarvester_Datacards.cc index f476fc54e61..047e43de07c 100644 --- a/CombineTools/src/CombineHarvester_Datacards.cc +++ b/CombineTools/src/CombineHarvester_Datacards.cc @@ -393,8 +393,22 @@ void CombineHarvester::FillHistMappings(std::vector & mappings) { auto sig_proc_set = ch_signals.SetFromProcs(std::mem_fn(&ch::Process::process)); for (auto sig_proc : sig_proc_set) { - mappings.emplace_back(sig_proc, bin, bin + "/" + sig_proc + "$MASS", - bin + "/" + sig_proc + "$MASS_$SYSTEMATIC"); + // should only add this mapping if the signal process has a numeric mass + // value, otherwise we will write it using the background rule above + auto masses = Set2Vec(ch_signals.cp() + .process({sig_proc}) + .SetFromProcs(std::mem_fn(&ch::Process::mass))); + if (masses.size() != 1) { + throw std::runtime_error(FNERROR("Process " + sig_proc + " in bin " + + bin + + " has multiple entries with multiple " + "mass values, this is not supported")); + + } + if (is_float(masses[0])) { + mappings.emplace_back(sig_proc, bin, bin + "/" + sig_proc + "$MASS", + bin + "/" + sig_proc + "$MASS_$SYSTEMATIC"); + } } } @@ -662,7 +676,7 @@ void CombineHarvester::WriteDatacard(std::string const& name, txt_file << format("%-"+sys_str_long+"s") % "rate"; for (auto const& proc : procs_) { - txt_file << format("%-15.4g ") % proc->no_norm_rate(); + txt_file << format("%-15.6g ") % proc->no_norm_rate(); } txt_file << "\n"; txt_file << dashes << "\n"; diff --git a/CombineTools/src/CombineHarvester_Evaluate.cc b/CombineTools/src/CombineHarvester_Evaluate.cc index d13ae09a1a1..0f03f54b9ee 100644 --- a/CombineTools/src/CombineHarvester_Evaluate.cc +++ b/CombineTools/src/CombineHarvester_Evaluate.cc @@ -234,7 +234,13 @@ TH1F CombineHarvester::GetShapeInternal(ProcSystMap const& lookup, if (procs_[i]->shape() || procs_[i]->data()) { TH1F proc_shape = procs_[i]->ShapeAsTH1F(); for (auto sys_it : lookup[i]) { - double x = params_[sys_it->name()]->val(); + auto param_it = params_.find(sys_it->name()); + if (param_it == params_.end()) { + throw std::runtime_error( + FNERROR("Parameter " + sys_it->name() + + " not found in CombineHarvester instance")); + } + double x = param_it->second->val(); if (sys_it->asymm()) { p_rate *= logKappaForX(x * sys_it->scale(), sys_it->value_d(), sys_it->value_u()); diff --git a/CombineTools/src/HttSystematics_MSSMUpdate.cc b/CombineTools/src/HttSystematics_MSSMUpdate.cc new file mode 100644 index 00000000000..6b9b31e2b67 --- /dev/null +++ b/CombineTools/src/HttSystematics_MSSMUpdate.cc @@ -0,0 +1,734 @@ +#include "CombineHarvester/CombineTools/interface/HttSystematics.h" +#include +#include +#include "CombineHarvester/CombineTools/interface/Systematics.h" +#include "CombineHarvester/CombineTools/interface/Process.h" +#include "CombineHarvester/CombineTools/interface/Utilities.h" + +namespace ch { + +using ch::syst::SystMap; +using ch::syst::SystMapAsymm; +using ch::syst::era; +using ch::syst::channel; +using ch::syst::bin_id; +using ch::syst::process; +using ch::JoinStr; + +void AddMSSMUpdateSystematics_et_mt(CombineHarvester & cb, CombineHarvester src) { + //CombineHarvester src = cb.cp(); + src.channel({"et", "mt"}); + + // This regular expression will match any Higgs signal process. + // It's useful for catching all these processes when we don't know + // which of them will be signal or background + //std::string bb_rgx = "(bb[hHA])*"; + //std::string gg_rgx = "(gg[hHA])*"; + + std::vector ggH = {"ggH", "ggH_Htautau", "ggA_Atautau", "ggh_htautau"}; + std::vector bbH = {"bbH", "bbH_Htautau", "bbA_Atautau", "bbh_htautau"}; + + auto signal = Set2Vec(src.cp().signals().SetFromProcs( + std::mem_fn(&Process::process))); + + src.cp().process(JoinStr({signal,{"ggH_SM125", "qqH_SM125", "VH_SM125"}})) + .AddSyst(cb, "lumi_$ERA", "lnN", SystMap::init + //({"7TeV"}, 1.026) + ({"8TeV"}, 1.026)); + + src.cp().process({"ZTT"}) + .AddSyst(cb, "CMS_eff_m", "lnN", SystMap::init({"mt"}, 1.010)); + src.cp().process({"ZTT"}) + .AddSyst(cb, "CMS_eff_e", "lnN", SystMap::init({"et"}, 1.020)); + + src.cp() + .AddSyst(cb, "CMS_eff_t_$CHANNEL_$ERA", "lnN", SystMap::init + ({10, 11, 12, 13, 14}, JoinStr({signal,{"ZTT", "VV", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1.08) + ({10, 11, 12}, {"TT"}, 1.080)); + + src.cp() + .AddSyst(cb, "CMS_eff_t_mssmHigh_mutau_$ERA", "shape", SystMap::init + ({"mt"}, JoinStr({signal,{"ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1)); + + src.cp() + .AddSyst(cb, "CMS_eff_t_mssmHigh_etau_$ERA", "shape", SystMap::init + ({"et"}, JoinStr({signal,{"ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1)); + +// src.cp().process(JoinStr({signal,{"ggH_SM125", "qqH_SM125", "VH_SM125"}})) +// .AddSyst(cb, "CMS_eff_t_mssmHigh_$CHANNEL_$ERA", "shape", SystMap<>::init(1)); + + src.cp() + .AddSyst(cb, "CMS_scale_t_mutau_$ERA", "shape", SystMap::init + ({"mt"}, JoinStr({signal,{"ZTT", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1)); + + src.cp() + .AddSyst(cb, "CMS_scale_t_etau_$ERA", "shape", SystMap::init + ({"et"}, JoinStr({signal,{"ZTT", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1)); + +// src.cp().process(JoinStr({signal,{"ZTT", "ggH_SM125", "qqH_SM125", "VH_SM125"}})) +// .AddSyst(cb, "CMS_scale_t_$CHANNEL_$ERA", "shape", SystMap::init(1)); + + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift1_muTau_nobtag_low_$ERA_W_fine_binning", "shape", SystMap::init({"mt"}, {10}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift2_muTau_nobtag_low_$ERA_W_fine_binning", "shape", SystMap::init({"mt"}, {10}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_muTau_nobtag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({"mt"}, {10}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_muTau_nobtag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({"mt"}, {10}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift1_muTau_nobtag_medium_$ERA_W_fine_binning", "shape", SystMap::init({"mt"}, {11}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift2_muTau_nobtag_medium_$ERA_W_fine_binning", "shape", SystMap::init({"mt"}, {11}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_muTau_nobtag_medium_$ERA_QCD_fine_binning", "shape", SystMap::init({"mt"}, {11}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_muTau_nobtag_medium_$ERA_QCD_fine_binning", "shape", SystMap::init({"mt"}, {11}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift1_muTau_nobtag_high_$ERA_W_fine_binning", "shape", SystMap::init({"mt"}, {12}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift2_muTau_nobtag_high_$ERA_W_fine_binning", "shape", SystMap::init({"mt"}, {12}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_muTau_nobtag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({"mt"}, {12}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_muTau_nobtag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({"mt"}, {12}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift1_muTau_btag_low_$ERA_W_fine_binning", "shape", SystMap::init({"mt"}, {13}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift2_muTau_btag_low_$ERA_W_fine_binning", "shape", SystMap::init({"mt"}, {13}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_muTau_btag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({"mt"}, {13}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_muTau_btag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({"mt"}, {13}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift1_muTau_btag_high_$ERA_W_fine_binning", "shape", SystMap::init({"mt"}, {14}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift2_muTau_btag_high_$ERA_W_fine_binning", "shape", SystMap::init({"mt"}, {14}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_muTau_btag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({"mt"}, {14}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_muTau_btag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({"mt"}, {14}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift1_eleTau_nobtag_low_$ERA_W_fine_binning", "shape", SystMap::init({"et"}, {10}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift2_eleTau_nobtag_low_$ERA_W_fine_binning", "shape", SystMap::init({"et"}, {10}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_eleTau_nobtag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({"et"}, {10}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_eleTau_nobtag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({"et"}, {10}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift1_eleTau_nobtag_medium_$ERA_W_fine_binning", "shape", SystMap::init({"et"}, {11}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift2_eleTau_nobtag_medium_$ERA_W_fine_binning", "shape", SystMap::init({"et"}, {11}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_eleTau_nobtag_medium_$ERA_QCD_fine_binning", "shape", SystMap::init({"et"}, {11}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_eleTau_nobtag_medium_$ERA_QCD_fine_binning", "shape", SystMap::init({"et"}, {11}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift1_eleTau_nobtag_high_$ERA_W_fine_binning", "shape", SystMap::init({"et"}, {12}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift2_eleTau_nobtag_high_$ERA_W_fine_binning", "shape", SystMap::init({"et"}, {12}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_eleTau_nobtag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({"et"}, {12}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_eleTau_nobtag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({"et"}, {12}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift1_eleTau_btag_low_$ERA_W_fine_binning", "shape", SystMap::init({"et"}, {13}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift2_eleTau_btag_low_$ERA_W_fine_binning", "shape", SystMap::init({"et"}, {13}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_eleTau_btag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({"et"}, {13}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_eleTau_btag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({"et"}, {13}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift1_eleTau_btag_high_$ERA_W_fine_binning", "shape", SystMap::init({"et"}, {14}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_shift2_eleTau_btag_high_$ERA_W_fine_binning", "shape", SystMap::init({"et"}, {14}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_eleTau_btag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({"et"}, {14}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_eleTau_btag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({"et"}, {14}, 1.000)); + + src.cp() + .AddSyst(cb, "CMS_scale_j_$ERA", "lnN", SystMap::init + ({"mt", "et"}, {10, 11, 12}, JoinStr({bbH, {"TT"}}), 0.99) + ({"mt"}, {13, 14}, JoinStr({ggH, {"ggH_SM125"}}), 0.99) + ({"mt"}, {13, 14}, {bbH}, 1.01) + ({"mt"}, {13, 14}, {"ZJ"}, 0.980) + ({"mt"}, {13, 14}, {"ZL"}, 0.980) + ({"mt"}, {13, 14}, {"TT"}, 0.920) + ({"mt"}, {13, 14}, {"VV"}, 0.980) + ({"et"}, {13, 14}, JoinStr({ggH, {"ggH_SM125"}}), 1.03) + ({"et"}, {13, 14}, {bbH}, 1.01) + ({"et"}, {13, 14}, {"ZJ"}, 1.060) + ({"et"}, {13, 14}, {"ZL"}, 0.960) + ({"et"}, {13, 14}, {"TT"}, 0.900) + ({"et"}, {13, 14}, {"VV"}, 0.940)); + + src.cp() + .AddSyst(cb, "CMS_htt_scale_met_$ERA", "lnN", SystMap::init + ({10, 11, 12, 13, 14}, JoinStr({signal,{"ggH_SM125", "qqH_SM125", "VH_SM125"}}), 0.99) + ({10, 11, 12, 13, 14}, {"ZJ", "ZL", "TT"}, 1.010) + ({13, 14}, {"W"}, 1.010)); + + src.cp() + .AddSyst(cb, "CMS_eff_b_$ERA", "lnN", SystMap::init + ({"et", "mt"}, {10, 11, 12}, {bbH}, 0.99) + ({"et", "mt"}, {10, 11, 12}, {"TT"}, 0.950) + ({"mt"}, {10, 11, 12}, {"VV"}, 0.980) + ({"et"}, {10, 11, 12}, {"VV"}, 0.990) + ({"mt"}, {13, 14}, JoinStr({ggH, {"ggH_SM125"}}), 1.01) + ({"mt"}, {13, 14}, {bbH}, 1.03) + ({"mt"}, {13, 14}, {"ZL"}, 1.040) + ({"mt"}, {13, 14}, {"TT"}, 1.020) + ({"mt"}, {13, 14}, {"VV"}, 1.040) + ({"et"}, {13, 14}, JoinStr({ggH, {"ggH_SM125"}}), 1.01) + ({"et"}, {13, 14}, {bbH}, 1.02) + ({"et"}, {13, 14}, {"ZL"}, 1.020) + ({"et"}, {13, 14}, {"ZJ"}, 1.040) + ({"et"}, {13, 14}, {"TT"}, 1.040) + ({"et"}, {13, 14}, {"VV"}, 1.040)); + + src.cp() + .AddSyst(cb, "CMS_fake_b_$ERA", "lnN", SystMap::init + ({"mt", "et"}, {10, 11, 12}, {"TT"}, 0.970) + ({"mt", "et"}, {10, 11, 12}, {"VV"}, 0.990) + ({"mt"}, {13, 14}, JoinStr({ggH, {"ggH_SM125"}}), 1.05) + ({"mt"}, {13, 14}, {"ZL"}, 1.050) + ({"mt"}, {13, 14}, {"ZJ"}, 1.090) + ({"mt"}, {13, 14}, {"TT", "VV"}, 1.010) + ({"et"}, {13, 14}, JoinStr({ggH, {"ggH_SM125"}}), 1.03) + ({"et"}, {13, 14}, {"ZL"}, 1.020) + ({"et"}, {13, 14}, {"ZJ"}, 1.090) + ({"et"}, {13, 14}, {"TT", "VV"}, 1.010)); + + src.cp().process({"ZTT", "ZJ", "ZL"}) + .AddSyst(cb, "CMS_htt_zttNorm_$ERA", "lnN", SystMap<>::init(1.030)); + + src.cp().process({"ZTT"}) + .AddSyst(cb, "CMS_htt_extrap_ztt_$CHANNEL_$BIN_$ERA", "lnN", SystMap::init + ({11, 12, 13, 14}, 1.050)); + + src.cp().process({"TT"}) + .AddSyst(cb, "CMS_htt_ttbarNorm_$ERA", "lnN", SystMap<>::init(1.100)); + + src.cp().process({"TT"}) + .AddSyst(cb, "CMS_htt_ttbarPtReweight_$ERA", "shape", SystMap<>::init(1.000)); + + src.cp().process({"TT"}) + .AddSyst(cb, "CMS_htt_ttbarJetFake_$ERA", "shape", SystMap<>::init(1.000)); + + src.cp().process({"TT"}) + .AddSyst(cb, "CMS_htt_ttbar_emb_$ERA", "lnN", SystMap::init({13, 14}, 1.140)); + + src.cp().process({"VV"}) + .AddSyst(cb, "CMS_htt_DiBosonNorm_$ERA", "lnN", SystMap<>::init(1.150)); + + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WNorm_$CHANNEL_$BIN_$ERA", "lnN", SystMap::init + ({10, 11, 12}, 1.200) + ({13}, 1.300) + ({14}, 1.500)); + + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_htt_QCDSyst_$CHANNEL_$BIN_$ERA", "lnN", SystMap::init + ({10, 11, 12}, 1.100) + ({13, 14}, 1.200)); + + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_htt_QCDfrShape_mutau_$ERA", "shape", SystMap::init({"mt"}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_htt_QCDfrShape_etau_$ERA", "shape", SystMap::init({"et"}, 1.000)); + + src.cp().process({"ZJ"}) + .AddSyst(cb, "CMS_htt_ZJetFakeTau_$CHANNEL_$ERA", "lnN", SystMap<>::init(1.200)); + + src.cp().process({"ZL"}) + .AddSyst(cb, "CMS_htt_ZLeptonFakeTau_$CHANNEL_low_pTtau_$ERA", "lnN", SystMap::init + ({"mt"}, {10, 13}, 1.300) + ({"et"}, {10, 13}, 1.200)); + + src.cp().process({"ZL"}) + .AddSyst(cb, "CMS_htt_ZLeptonFakeTau_$CHANNEL_medium_pTtau_$ERA", "lnN", SystMap::init + ({"mt"}, {11, 14}, 1.300) + ({"et"}, {11, 14}, 1.200)); + + src.cp().process({"ZL"}) + .AddSyst(cb, "CMS_htt_ZLeptonFakeTau_$CHANNEL_high_pTtau_$ERA", "lnN", SystMap::init + ({"mt"}, {12}, 1.300) + ({"et"}, {12}, 1.200)); + + src.cp().process({"ZL"}).channel({"mt"}) + .AddSyst(cb, "CMS_htt_ZLScale_mutau_$ERA", "shape", SystMap<>::init(1.000)); + src.cp().process({"ZL"}).channel({"et"}) + .AddSyst(cb, "CMS_htt_ZLScale_etau_$ERA", "shape", SystMap<>::init(1.000)); + + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_mutau_nobtag_low_$ERA", "shape", SystMap::init({"mt"}, {10}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_mutau_nobtag_medium_$ERA", "shape", SystMap::init({"mt"}, {11}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_mutau_nobtag_high_$ERA", "shape", SystMap::init({"mt"}, {12}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_mutau_btag_low_$ERA", "shape", SystMap::init({"mt"}, {13}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_mutau_btag_high_$ERA", "shape", SystMap::init({"mt"}, {14}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_etau_nobtag_low_$ERA", "shape", SystMap::init({"et"}, {10}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_etau_nobtag_medium_$ERA", "shape", SystMap::init({"et"}, {11}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_etau_nobtag_high_$ERA", "shape", SystMap::init({"et"}, {12}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_etau_btag_low_$ERA", "shape", SystMap::init({"et"}, {13}, 1.000)); + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_etau_btag_high_$ERA", "shape", SystMap::init({"et"}, {14}, 1.000)); + + src.cp().process({"ggH_SM125", "qqH_SM125", "VH_SM125"}) + .AddSyst(cb, "CMS_htt_SM125_mu", "lnN", SystMap<>::init(1.300)); + + src.cp() + .AddSyst(cb, "pdf_qqbar", "lnN", SystMap::init + ({"qqH_SM125"}, 1.036) + ({"VH_SM125"}, 1.040)); + + src.cp().process({"ggH_SM125"}) + .AddSyst(cb, "pdf_gg", "lnN", SystMap<>::init(1.097)); + + src.cp() + .AddSyst(cb, "UEPS", "lnN", SystMap::init + ({10, 11, 12}, {"ggH_SM125"}, 1.013) + ({10, 11, 12}, {"qqH_SM125", "VH_SM125"}, 1.050) + ({13, 14}, {"ggH_SM125"}, 0.946) + ({13, 14}, {"qqH_SM125", "VH_SM125"}, 1.007)); + + src.cp().process({"ggH_SM125"}) + .AddSyst(cb, "QCDscale_ggH", "lnN", SystMap::init + ({"mt", "et"}, {10, 11, 12}, 1.080) + ({"mt"}, {13, 14}, 1.105) + ({"et"}, {13, 14}, 1.125)); + + src.cp().process({"qqH_SM125"}) + .AddSyst(cb, "QCDscale_qqH", "lnN", SystMap::init + ({10, 11, 12}, 1.022) + ({13, 14}, 1.015)); + + src.cp().process({"VH_SM125"}) + .AddSyst(cb, "QCDscale_VH", "lnN", SystMap::init + ({10, 11, 12}, 1.010) + ({13, 14}, 1.040)); + + src.cp().process({ggH}) + .AddSyst(cb, "CMS_htt_higgsPtReweight_$ERA", "shape", SystMap<>::init(1)); + + src.cp().process({ggH}) + .AddSyst(cb, "CMS_htt_higgsPtReweight_scale_$ERA", "shape", SystMap::init + ({"mt", "et"}, {10, 11, 12, 13}, 1) + ({"et"}, {14}, 1)); + + src.cp().process({"ggH_SM125"}) + .AddSyst(cb, "CMS_htt_higgsPtReweightSM_$ERA", "shape", SystMap<>::init(1.000)); +} + +void AddMSSMUpdateSystematics_et_mt(CombineHarvester & cb) { + + CombineHarvester src = cb.cp(); + AddMSSMUpdateSystematics_et_mt(cb, src); + +} + +void AddMSSMUpdateSystematics_em(CombineHarvester & cb, CombineHarvester src) { + //CombineHarvester src = cb.cp(); + src.channel({"em"}); + + std::vector ggH = {"ggH", "ggH_Htautau", "ggA_Atautau", "ggh_htautau"}; + std::vector bbH = {"bbH", "bbH_Htautau", "bbA_Atautau", "bbh_htautau"}; + + auto signal = Set2Vec(src.cp().signals().SetFromProcs( + std::mem_fn(&Process::process))); + + src.cp().process(JoinStr({signal,{"EWK", "ggH_SM125", "qqH_SM125", "VH_SM125"}})) + .AddSyst(cb, "lumi_$ERA", "lnN", SystMap::init({"8TeV"}, 1.026)); + + src.cp().process(JoinStr({signal,{"Ztt", "ttbar", "EWK", "Fakes", "ggH_SM125", "qqH_SM125", "VH_SM125"}})) + .AddSyst(cb, "CMS_eff_e_$ERA", "lnN", SystMap<>::init(1.02)); + + src.cp().process(JoinStr({signal,{"Ztt", "ggH_SM125", "qqH_SM125", "VH_SM125"}})) + .AddSyst(cb, "CMS_scale_e_$ERA", "shape", SystMap<>::init(1)); + + src.cp().process({"EWK"}) + .AddSyst(cb, "CMS_shift1_emu_nobtag_$ERA_EWK_fine_binning", "shape", SystMap::init({8}, 1.000)); + + src.cp().process({"EWK"}) + .AddSyst(cb, "CMS_shift1_emu_btag_$ERA_EWK_fine_binning", "shape", SystMap::init({9}, 1.000)); + + src.cp().process({"EWK"}) + .AddSyst(cb, "CMS_shift2_emu_nobtag_$ERA_EWK_fine_binning", "shape", SystMap::init({8}, 1.000)); + src.cp().process({"EWK"}) + .AddSyst(cb, "CMS_shift2_emu_btag_$ERA_EWK_fine_binning", "shape", SystMap::init({9}, 1.000)); + + src.cp().process(JoinStr({signal,{"Ztt", "ttbar", "EWK", "Fakes", "ggH_SM125", "qqH_SM125", "VH_SM125"}})) + .AddSyst(cb, "CMS_eff_m_$ERA", "lnN", SystMap<>::init(1.02)); + + src.cp() + .AddSyst(cb, "CMS_scale_j_$ERA", "lnN", SystMap::init + ({8}, JoinStr({signal,{"ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1.01) + ({8}, {"ttbar", "EWK"}, 0.990) + ({9}, JoinStr({ggH, {"ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1.04) + ({9}, {bbH}, 1.01) + ({9}, {"ttbar"}, 0.930) + ({9}, {"EWK"}, 0.970)); + + src.cp() + .AddSyst(cb, "CMS_htt_scale_met_$ERA", "lnN", SystMap::init + ({8, 9}, JoinStr({signal,{"ggH_SM125", "qqH_SM125", "VH_SM125"}}), 0.98) + ({8}, {"ttbar"}, 0.990) + ({9}, {"ttbar"}, 1.010)); + + src.cp() + .AddSyst(cb, "CMS_eff_b_$ERA", "lnN", SystMap::init + ({8}, JoinStr({signal,{"EWK", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 0.98) + ({8}, {"ttbar"}, 0.950) + ({9}, JoinStr({ggH, {"ggH_SM125"}}), 1.01) + ({9}, {bbH}, 1.05) + ({9}, {"ttbar"}, 1.020) + ({9}, {"EWK"}, 1.030)); + + src.cp() + .AddSyst(cb, "CMS_fake_b_$ERA", "lnN", SystMap::init + ({8}, JoinStr({signal,{"ttbar", "EWK", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 0.98) + ({9}, JoinStr({ggH, {"ggH_SM125"}}), 1.05) + ({9}, {"EWK"}, 1.030)); + + src.cp().process({"Ztt"}) + .AddSyst(cb, "CMS_htt_zttNorm_$ERA", "lnN", SystMap<>::init(1.030)); + + src.cp().process({"Ztt"}) + .AddSyst(cb, "CMS_htt_extrap_ztt_$CHANNEL_btag_$ERA", "lnN", SystMap::init({9}, 1.010)); + + src.cp().process({"ttbar"}) + .AddSyst(cb, "CMS_htt_ttbarNorm_$ERA", "lnN", SystMap<>::init(1.100)); + + src.cp().process({"EWK"}) + .AddSyst(cb, "CMS_htt_DiBosonNorm_$ERA", "lnN", SystMap<>::init(1.150)); + + src.cp().process({"Fakes"}) + .AddSyst(cb, "CMS_htt_fakes_$CHANNEL_$ERA", "lnN", SystMap<>::init(1.300)); + + src.cp().process({"Fakes"}) + .AddSyst(cb, "CMS_htt_fakes_$CHANNEL_btag_$ERA", "lnN", SystMap::init({9}, 1.090)); + + src.cp().process({"ttbar"}) + .AddSyst(cb, "CMS_htt_ttbar_emb_$ERA", "lnN", SystMap::init({9}, 1.020)); + + src.cp().process({"Fakes"}) + .AddSyst(cb, "CMS_htt_FakeShape_$CHANNEL_nobtag_$ERA", "shape", SystMap::init({8}, 1.000)); + src.cp().process({"Fakes"}) + .AddSyst(cb, "CMS_htt_FakeShape_$CHANNEL_btag_$ERA", "shape", SystMap::init({9}, 1.000)); + + src.cp().process({"ttbar"}) + .AddSyst(cb, "CMS_htt_TTbarShape_$CHANNEL_nobtag_$ERA", "shape", SystMap::init({8}, 1.000)); + src.cp().process({"ttbar"}) + .AddSyst(cb, "CMS_htt_TTbarShape_$CHANNEL_btag_$ERA", "shape", SystMap::init({9}, 1.000)); + + src.cp().process({"ggH_SM125", "qqH_SM125", "VH_SM125"}) + .AddSyst(cb, "CMS_htt_SM125_mu", "lnN", SystMap<>::init(1.300)); + + src.cp() + .AddSyst(cb, "pdf_qqbar", "lnN", SystMap::init + ({8, 9}, {"qqH_SM125"}, 1.036) + ({8}, {"VH_SM125"}, 1.010) + ({9}, {"VH_SM125"}, 1.020)); + + src.cp().process({"ggH_SM125"}) + .AddSyst(cb, "pdf_gg", "lnN", SystMap<>::init(1.097)); + + src.cp().process({"ggH_SM125"}) + .AddSyst(cb, "QCDscale_ggH", "lnN", SystMap::init + ({8}, 1.080) + ({9}, 1.105)); + + src.cp().process({"qqH_SM125"}) + .AddSyst(cb, "QCDscale_qqH", "lnN", SystMap::init + ({8}, 1.034) + ({9}, 1.008)); + + src.cp().process({"VH_SM125"}) + .AddSyst(cb, "QCDscale_VH", "lnN", SystMap<>::init(1.040)); + + src.cp() + .AddSyst(cb, "UEPS", "lnN", SystMap::init + ({8}, {"ggH_SM125"}, 1.035) + ({9}, {"ggH_SM125"}, 0.984) + ({8}, {"qqH_SM125", "VH_SM125"}, 1.089)); +} + +void AddMSSMUpdateSystematics_em(CombineHarvester & cb) { + + CombineHarvester src = cb.cp(); + AddMSSMUpdateSystematics_em(cb, src); + +} + +void AddMSSMUpdateSystematics_mm(CombineHarvester & cb, CombineHarvester src) { + //CombineHarvester src = cb.cp(); + src.channel({"mm"}); + + std::vector ggH = {"ggH", "ggH_Htautau", "ggA_Atautau", "ggh_htautau"}; + std::vector bbH = {"bbH", "bbH_Htautau", "bbA_Atautau", "bbh_htautau"}; + + auto signal = Set2Vec(src.cp().signals().SetFromProcs( + std::mem_fn(&Process::process))); + + src.cp() + .AddSyst(cb, "lumi_$ERA", "lnN", SystMap::init + ({8, 9}, JoinStr({signal,{"Dibosons", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), {"8TeV"}, 1.026) + ({8}, {"WJets"}, {"8TeV"}, 1.026)); + + src.cp() + .AddSyst(cb, "CMS_eff_m_$ERA", "lnN", SystMap::init + ({8, 9}, JoinStr({signal,{"ZTT", "TTJ", "Dibosons", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1.06) + ({8}, {"WJets"}, 1.060)); + + src.cp() + .AddSyst(cb, "CMS_scale_j_$ERA", "lnN", SystMap::init + ({8}, JoinStr({signal,{"ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1.02) + ({8}, {"TTJ", "WJets", "Dibosons"}, 0.990) + ({9}, JoinStr({ggH,{"ggH_SM125"}}), 0.98) + ({9}, {bbH}, 1.01) + ({9}, {"TTJ"}, 0.920) + ({9}, {"Dibosons"}, 0.950)); + + src.cp() + .AddSyst(cb, "CMS_eff_b_$ERA", "lnN", SystMap::init + ({8}, JoinStr({signal,{"ggH_SM125", "qqH_SM125", "VH_SM125"}}), 0.98) + ({8}, {"TTJ"}, 0.940) + ({8}, {"WJets", "Dibosons"}, 0.970) + ({9}, JoinStr({ggH, {"ggH_SM125"}}), 1.01) + ({9}, {bbH}, 1.05) + ({9}, {"TTJ"}, 1.020) + ({9}, {"Dibosons"}, 1.030)); + + src.cp() + .AddSyst(cb, "CMS_fake_b_$ERA", "lnN", SystMap::init + ({8}, JoinStr({signal,{"TqTJ", "WJets", "Dibosons", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 0.98) + ({9}, JoinStr({ggH, {"ggH_SM125"}}), 1.06) + ({9}, {"Dibosons"}, 1.050)); + + src.cp().process({"ZTT"}) + .AddSyst(cb, "CMS_htt_zttNorm_$ERA", "lnN", SystMap<>::init(1.050)); + + src.cp().process({"TTJ"}) + .AddSyst(cb, "CMS_htt_ttbarNorm_$ERA", "lnN", SystMap<>::init(1.100)); + + src.cp().process({"Dibosons"}) + .AddSyst(cb, "CMS_htt_$CHANNEL_DiBosonNorm_$ERA", "lnN", SystMap<>::init(1.300)); + + src.cp().process({"ZMM"}) + .AddSyst(cb, "CMS_htt_$CHANNEL_zmmNorm_$ERA", "lnN", SystMap<>::init(1.050)); + + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_htt_$CHANNEL_QCDNorm_$BIN_$ERA", "lnN", SystMap::init + ({8}, 1.100) + ({9}, 1.250)); + + src.cp().process({"WJets"}) + .AddSyst(cb, "CMS_htt_$CHANNEL_WJetsNorm_nobtag_$ERA", "lnN", SystMap::init({8}, 1.220)); + + src.cp().process({"ZMM"}) + .AddSyst(cb, "CMS_htt_$CHANNEL_res_met_$ERA", "shape", SystMap::init({8}, 1.000)); + + src.cp().process({"ZTT"}) + .AddSyst(cb, "CMS_htt_$CHANNEL_ztt_extrap_btag_$ERA", "lnN", SystMap::init({9}, 1.050)); + + src.cp().process({"ZMM"}) + .AddSyst(cb, "CMS_htt_$CHANNEL_zmm_extrap_btag_$ERA", "lnN", SystMap::init({9}, 1.050)); + + src.cp().process({"ggH_SM125", "qqH_SM125", "VH_SM125"}) + .AddSyst(cb, "CMS_htt_SM125_mu", "lnN", SystMap<>::init(1.300)); + + src.cp().process({"qqH_SM125"}) + .AddSyst(cb, "pdf_qqbar", "lnN", SystMap<>::init(1.036)); + + src.cp().process({"ggH_SM125"}) + .AddSyst(cb, "pdf_gg", "lnN", SystMap<>::init(1.097)); + + src.cp().process({"VH_SM125"}) + .AddSyst(cb, "pdf_vh", "lnN", SystMap<>::init(1.010)); + + src.cp().process({"ggH_SM125"}) + .AddSyst(cb, "QCDscale_ggH", "lnN", SystMap::init + ({8}, 1.103) + ({9}, 1.109)); + + src.cp().process({"qqH_SM125"}) + .AddSyst(cb, "QCDscale_qqH", "lnN", SystMap::init + ({8}, 1.034) + ({9}, 1.008)); + + src.cp().process({"VH_SM125"}) + .AddSyst(cb, "QCDscale_VH", "lnN", SystMap<>::init(1.040)); + + src.cp() + .AddSyst(cb, "UEPS", "lnN", SystMap::init + ({8}, {"ggH_SM125"}, 1.035) + ({8}, {"qqH_SM125", "VH_SM125"}, 1.089) + ({9}, {"ggH_SM125"}, 0.984) + ({9}, {"qqH_SM125", "VH_SM125"}, 1.000)); +} + +void AddMSSMUpdateSystematics_mm(CombineHarvester & cb) { + + CombineHarvester src = cb.cp(); + AddMSSMUpdateSystematics_mm(cb, src); + +} + +void AddMSSMUpdateSystematics_tt(CombineHarvester & cb, CombineHarvester src) { + //CombineHarvester src = cb.cp(); + src.channel({"tt"}); + + std::vector ggH = {"ggH", "ggH_Htautau", "ggA_Atautau", "ggh_htautau"}; + std::vector bbH = {"bbH", "bbH_Htautau", "bbA_Atautau", "bbh_htautau"}; + + auto signal = Set2Vec(src.cp().signals().SetFromProcs( + std::mem_fn(&Process::process))); + + src.cp().process(JoinStr({signal,{"ggH_SM125", "qqH_SM125", "VH_SM125"}})) + .AddSyst(cb, "lumi_$ERA", "lnN", SystMap::init({"8TeV"}, 1.026)); + + src.cp().process(JoinStr({signal,{"ZTT", "TT", "VV", "ggH_SM125", "qqH_SM125", "VH_SM125"}})) + .AddSyst(cb, "CMS_eff_t_tautau_$ERA", "lnN", SystMap<>::init(1.19)); + + src.cp().process(JoinStr({signal})) + .AddSyst(cb, "CMS_eff_t_mssmHigh_tautau_$ERA", "shape", SystMap<>::init(1)); + + src.cp().process(JoinStr({signal,{"ZTT"}})) + .AddSyst(cb, "CMS_scale_t_tautau_$ERA", "shape", SystMap<>::init(1)); + + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_tauTau_nobtag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({10}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_tauTau_nobtag_medium_$ERA_QCD_fine_binning", "shape", SystMap::init({11}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_tauTau_nobtag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({12}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_tauTau_btag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({13}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift1_tauTau_btag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({14}, 1.000)); + + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_tauTau_nobtag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({10}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_tauTau_nobtag_medium_$ERA_QCD_fine_binning", "shape", SystMap::init({11}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_tauTau_nobtag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({12}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_tauTau_btag_low_$ERA_QCD_fine_binning", "shape", SystMap::init({13}, 1.000)); + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_shift2_tauTau_btag_high_$ERA_QCD_fine_binning", "shape", SystMap::init({14}, 1.000)); + + src.cp() + .AddSyst(cb, "CMS_scale_j_$ERA", "lnN", SystMap::init + ({ggH}, 1.05) + ({bbH}, 1.06) + ({"TT"}, 1.010) + ({"VV"}, 1.030) + ({"ggH_SM125"}, 1.050)); + + src.cp() + .AddSyst(cb, "CMS_eff_b_$ERA", "lnN", SystMap::init + ({10, 11, 12}, JoinStr({signal,{"ZJ", "TT", "VV", "ZL", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 0.98) + ({13, 14}, JoinStr({signal,{"TT", "VV", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1.09)); + + src.cp() + .AddSyst(cb, "CMS_fake_b_$ERA", "lnN", SystMap::init + ({10, 11, 12}, JoinStr({signal,{"ZJ", "TT", "VV", "ZL", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 0.98) + ({13, 14}, JoinStr({signal,{"TT", "VV", "ggH_SM125", "qqH_SM125", "VH_SM125"}}), 1.02)); + + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WNorm_$CHANNEL_$BIN_$ERA", "lnN", SystMap::init + ({10, 11, 12, 13}, 1.300) + ({14}, 1.500)); + + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_htt_QCDSyst_$CHANNEL_$BIN_$ERA", "lnN", SystMap::init + ({10, 11, 12}, 1.100) + ({13, 14}, 1.200)); + + src.cp().process({"QCD"}) + .AddSyst(cb, "CMS_htt_QCDfrShape_tautau_$ERA", "shape", SystMap<>::init(1.000)); + + src.cp().process({"ZTT", "ZJ", "ZL"}) + .AddSyst(cb, "CMS_htt_zttNorm_$ERA", "lnN", SystMap<>::init(1.033)); + + src.cp().process({"ZTT"}) + .AddSyst(cb, "CMS_htt_extrap_ztt_$CHANNEL_$BIN_$ERA", "lnN", SystMap::init + ({10, 11, 12}, 1.050) + ({13}, 1.090) + ({14}, 1.180)); + + src.cp().process({"TT"}) + .AddSyst(cb, "CMS_htt_ttbarNorm_$ERA", "lnN", SystMap<>::init(1.100)); + + src.cp().process({"TT"}) + .AddSyst(cb, "CMS_htt_ttbarNorm_$CHANNEL_$bin_$ERA", "lnN", SystMap<>::init(1.050)); + + src.cp().process({"TT"}) + .AddSyst(cb, "CMS_htt_ttbarPtReweight_$ERA", "shape", SystMap<>::init(1.000)); + + src.cp().process({"VV"}) + .AddSyst(cb, "CMS_htt_DiBosonNorm_$ERA", "lnN", SystMap<>::init(1.150)); + + src.cp().process({"VV"}) + .AddSyst(cb, "CMS_htt_DiBosonNorm_$CHANNEL_$BIN_$ERA", "lnN", SystMap<>::init(1.150)); + + src.cp().process({"ZJ"}) + .AddSyst(cb, "CMS_htt_ZJetFakeTau_$CHANNEL_$ERA", "lnN", SystMap<>::init(1.200)); + + src.cp().process({"ZL"}) + .AddSyst(cb, "CMS_htt_ZLeptonFakeTau_$CHANNEL_$ERA", "lnN", SystMap<>::init(1.200)); + + src.cp().process({"W"}) + .AddSyst(cb, "CMS_htt_WShape_tautau_$ERA", "shape", SystMap<>::init(1.000)); + + src.cp().process({"ggH_SM125", "qqH_SM125", "VH_SM125"}) + .AddSyst(cb, "CMS_htt_SM125_mu", "lnN", SystMap<>::init(1.300)); + + src.cp().process({"ggH_SM125"}) + .AddSyst(cb, "pdf_gg", "lnN", SystMap<>::init(1.097)); + + src.cp() + .AddSyst(cb, "pdf_qqbar", "lnN", SystMap::init + ({"qqH_SM125"}, 1.036) + ({"VH_SM125"}, 1.020)); + + src.cp().process({"ggH_SM125"}) + .AddSyst(cb, "QCDscale_ggH", "lnN", SystMap<>::init(1.205)); + + src.cp().process({"qqH_SM125"}) + .AddSyst(cb, "QCDscale_qqH", "lnN", SystMap<>::init(1.012)); + + src.cp().process({"VH_SM125"}) + .AddSyst(cb, "QCDscale_VH", "lnN", SystMap<>::init(1.040)); + + src.cp() + .AddSyst(cb, "UEPS", "lnN", SystMap::init + ({"ggH_SM125"}, 0.975) + ({"qqH_SM125", "VH_SM125"}, 1.025)); + + src.cp().process({ggH}) + .AddSyst(cb, "CMS_htt_higgsPtReweight_$ERA", "shape", SystMap<>::init(1)); + + src.cp().process({"ggH_SM125"}) + .AddSyst(cb, "CMS_htt_higgsPtReweightSM_$ERA", "shape", SystMap<>::init(1.000)); +} + +void AddMSSMUpdateSystematics_tt(CombineHarvester & cb) { + + CombineHarvester src = cb.cp(); + AddMSSMUpdateSystematics_tt(cb, src); + +} + +} diff --git a/CombineTools/src/Utilities.cc b/CombineTools/src/Utilities.cc index 0505f1ed8e2..0af484f5eea 100644 --- a/CombineTools/src/Utilities.cc +++ b/CombineTools/src/Utilities.cc @@ -237,6 +237,14 @@ std::vector ParseFileLines(std::string const& file_name) { return files; } +bool is_float(std::string const& str) { + std::istringstream iss(str); + float f; + iss >> std::noskipws >> f; // noskipws considers leading whitespace invalid + // Check the entire string was consumed and if either failbit or badbit is set + return iss.eof() && !iss.fail(); +} + std::vector MassesFromRange(std::string const& input, std::string const& fmt) { std::set mass_set; diff --git a/docs/ChargedHiggs.md b/docs/ChargedHiggs.md new file mode 100644 index 00000000000..31ae53ecf89 --- /dev/null +++ b/docs/ChargedHiggs.md @@ -0,0 +1,51 @@ +Charged Higgs datacards with RooMorphingPdf +=========================================== + +# Getting the cards + +The cards from the CMS HCG svn need to be copied into the auxilliaries directory: + + cp -r /afs/cern.ch/work/a/agilbert/public/CombineTools/data/HIG-14-020.r6636 $CMSSW_BASE/src/auxiliaries/datacards/ + +We will only be using the "low-mass" cards (m_H+ < m_top), which are named like `combine_datacard_hplushadronic_m[MASS].txt`, where `[MASS]` ranges from 80 to 160 GeV. + +# Build a RooMorphingPdf version + +The program `AdaptChargedHiggs` will: + + * parse these cards with CombineHarvester + * reduce the entries to one set of observations + backgrounds, as these are the same for all mass-points + * rename the signal processes and signal-affecting shape systematics, as these have the mass value hard-coded and BuildRooMorphing won't realise that they are all the same process which should be combined into one. The signal sis also renamed to get them in the PROD_DECAY form we will use for the model building later + * Create the RooMorphingPdfs and write out a new single datacard: `output/mssm_nomodel/hplus_tauhad_mssm.txt` + +# Reproduce limits in HIG-14-020 + +To reproduce the model-independent limits from the charged Higgs PAS we will need to use the same physics model that scales the `tt->H+H-bb` and `tt->H+W-bb` processes by the appropriate function of the `t->H+b` branching ratio POI `BR`. This has been adapted from the original version in the combine repository to account for our change of signal process naming: + + text2workspace.py hplus_tauhad_mssm.txt -o hplus_tauhad_mssm.root -P CombineHarvester.CombinePdfs.MSSM:brChargedHiggs + +Then we can run the asymptotic limits: + + combineTool.py -M Asymptotic -d hplus_tauhad_mssm.root -m 80:160:10 --freezeNuisances MH --setPhysicsModelParameterRanges BR=0,0.2 --cminDefaultMinimizerType Minuit2 --rAbsAcc 0.00001 -n .ChargedHiggs + +A few non-default combine options are used: + +| Option | Reason | +|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--setPhysicsModelParameterRanges BR=0,0.2` | Don't want the range to be too large compared to the typical uncertainty on the limit (can reduce Minuit precision) | +| `--cminDefaultMinimizerType Minuit2` | When doing the fit to get the global observables for the asimov dataset combine uses Minuit not Minuit2 by default - better to always use Minuit2 | +| `--rAbsAcc 0.00001` | The default absolute uncertainty on the POI value when searching for the limit is too large compared to the small values of `BR` we are probing here - we would get inaccurate limits. | + +The output limits are found to be in excellent agreement with the published numbers (dash indicates only available via morphing): + +| Mass (GeV) | Median exp. (HIG-14-020) | Median exp. (CH) | Obs. (HIG-14-020) | Obs. (CH) | +|------------|--------------------------|------------------|-------------------|-----------| +| 80 | 0.0111 | 0.0111 | 0.0121 | 0.0121 | +| 90 | 0.0080 | 0.0079 | 0.0094 | 0.0095 | +| 100 | 0.0061 | 0.0061 | 0.0063 | 0.0063 | +| 110 | - | 0.0046 | - | 0.0043 | +| 120 | 0.0034 | 0.0034 | 0.0029 | 0.0029 | +| 130 | - | 0.0028 | - | 0.0023 | +| 140 | 0.0023 | 0.0023 | 0.0018 | 0.0018 | +| 150 | 0.0021 | 0.0021 | 0.0015 | 0.0015 | +| 160 | 0.0022 | 0.0022 | 0.0016 | 0.0016 |