Browse Source

Adds documentation deployment make target, additional values relating to
leading objects

Caleb Fangmeier 7 years ago
parent
commit
55f8fd35f8

+ 1 - 1
.flake8

@@ -1,3 +1,3 @@
 [flake8]
-ignore = E221, E222, E241, E226
+ignore = E221, E222, E241, E225, E226
 max_line_length=120

+ 1 - 0
.gitignore

@@ -3,6 +3,7 @@
 __pycache__/
 build/
 data/
+*_data/
 
 # C++ Build Files
 *.d

+ 6 - 0
CMakeLists.txt

@@ -62,6 +62,12 @@ IF(DOXYGEN_FOUND)
         COMMENT "Generating API documentation with Doxygen" VERBATIM
         DEPENDS notebook-docs
     )
+    ADD_CUSTOM_TARGET(docs-deploy
+        rsync -r . caleb@fangmeier.tech:/var/www/tttt/docs
+        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs
+        COMMENT "Deploy documentation to tttt.fangmeier.tech" VERBATIM
+        DEPENDS docs
+    )
 ENDIF(DOXYGEN_FOUND)
 
 

+ 29 - 4
analysis/TTTT_Analysis.cpp

@@ -95,10 +95,20 @@ void declare_values(MiniTreeDataSet& mt){
                    ((id2 >=1 && id2 <= 5) || id2 == 21);
         })));
 
-    // Here is the calculation of the Top Reconstructed Mass from Particle
+
+
     auto jets = lookup<std::vector<Particle>>("jets");
+    auto leptons = lookup<std::vector<Particle>>("leptons");
+    auto leading_jet = fv::reduce(leading_particle, jets, "leading_jet");
+    fv::apply(particle_pt, fv::tuple(leading_jet), "leading_jet_pt");
+
+    auto leading_lepton = fv::reduce(leading_particle, leptons);
+    fv::apply(particle_pt, fv::tuple(leading_lepton), "leading_lepton_pt");
+    fv::apply(lepton_relIso, fv::tuple(leading_lepton), "leading_lepton_relIso");
 
     auto b_jets = lookup<std::vector<Particle>>("b_jets");
+
+    // Here is the calculation of the Top Reconstructed Mass from Particle
     auto w_dijets = tup_filter<Particle,Particle>(w_mass_filter, combinations<Particle,2>(jets, "reco_dijets"));
 
     auto top_cands = cart_product<std::tuple<Particle,Particle>, Particle>(w_dijets, b_jets);
@@ -259,8 +269,11 @@ void declare_containers(MiniTreeDataSet& mt){
                                                 10, 0, 10, 10, 0, 10,
                                                 "Generated Leptons","Reconstructed Leptons");
 
+    mt.register_container<ContainerTH1<int>>("jet_count", "B-Jet Multiplicity", lookup<int>("nJet"), 15, 0, 15);
     mt.register_container<ContainerTH1<int>>("b_jet_count", "B-Jet Multiplicity", lookup<int>("b_jet_count"), 10, 0, 10);
 
+    mt.register_container<ContainerTH1<int>>("jet_count_base_selection", "B-Jet Multiplicity", lookup<int>("nJet"), 15, 0, 15)->add_filter(event_selection.base_sel);
+    mt.register_container<ContainerTH1<int>>("b_jet_count_base_selection", "B-Jet Multiplicity", lookup<int>("b_jet_count"), 10, 0, 10)->add_filter(event_selection.base_sel);
 
     mt.register_container<ContainerTH1<int>>("jet_count_os_dilepton", "Jet Multiplicity - OS Dilepton Events",
                                                 lookup<int>("nJet"), 14, 0, 14)->add_filter(lookup_obs_filter("os-dilepton"));
@@ -297,10 +310,22 @@ void declare_containers(MiniTreeDataSet& mt){
     mt.register_container<Vector<std::vector<float>>>("Jet_phi",             lookup<std::vector<float>>("Jet_phi"));
     mt.register_container<Vector<std::vector<float>>>("Jet_mass",            lookup<std::vector<float>>("Jet_mass"));
 
-    mt.register_container<PassCount>("SR4j_count", SR4j);
-    mt.register_container<PassCount>("SR5j_count", SR5j);
-    mt.register_container<PassCount>("SR6j_count", SR6j);
+    mt.register_container<ContainerTH1<float>>("leading_jet_pt", "Leading Jet Pt", lookup<float>("leading_jet_pt"), 100, 0, 500);
+    mt.register_container<ContainerTH1<float>>("leading_lepton_pt", "Leading Lepton Pt", lookup<float>("leading_lepton_pt"), 100, 0, 500);
+    mt.register_container<ContainerTH1<float>>("leading_lepton_relIso", "Leading Lepton Relative Isolation", lookup<float>("leading_lepton_relIso"), 100, 0, 0.05);
+    mt.register_container<Vector<float>>("leading_lepton_relIso_all", lookup<float>("leading_lepton_relIso"));
+
+    mt.register_container<PassCount>("trilepton_count",   event_selection.trilepton);
+    mt.register_container<PassCount>("b_jet3_count",      event_selection.b_jet3);
+    mt.register_container<PassCount>("z_mass_veto_count", event_selection.z_mass_veto);
+
+    mt.register_container<PassCount>("J4_count", event_selection.J4);
+    mt.register_container<PassCount>("J5_count", event_selection.J5);
+    mt.register_container<PassCount>("J6_count", event_selection.J6);
 
+    mt.register_container<PassCount>("SR4j_count", event_selection.SR4j);
+    mt.register_container<PassCount>("SR5j_count", event_selection.SR5j);
+    mt.register_container<PassCount>("SR6j_count", event_selection.SR6j);
 }
 
 

+ 46 - 4
analysis/common/obj_types.hpp

@@ -48,6 +48,7 @@ struct Lepton {
     int pdg_id;
     int mcMatchPdgId;
     int charge;
+    float relIso;
 };
 
 struct GenPart{
@@ -62,6 +63,7 @@ struct Particle{
     enum{JET,
          LEPTON,
          GENPART,
+         VOID,
         }          tag;
     int            idx;
     TLorentzVector v;
@@ -93,11 +95,47 @@ struct Particle{
         p.genpart = genpart;
         return p;
     }
+    
+    static Particle
+    Void(){
+        Particle p;
+        p.tag = Particle::VOID;
+        return p;
+    }
+
 };
 
+Function<Particle(std::vector<Particle>)>& leading_particle = GenFunction::register_function<Particle(std::vector<Particle>)>("leading_particle",
+    FUNC(([](const std::vector<Particle>& particles){
+        if(particles.size() == 0) return Particle::Void();
+
+        Particle leading_particle = particles[0];
+        for (auto particle : particles){
+            if(particle.tag != Particle::VOID && particle.v.Pt() > leading_particle.v.Pt()){
+                leading_particle = particle;
+            }
+        }
+        return leading_particle;
+    })));
+
+Function<float(Particle)>& particle_pt = GenFunction::register_function<float(Particle)>("particle_pt",
+        FUNC(([](const Particle& p){
+            return p.v.Pt();
+        })));
+Function<float(Particle)>& particle_eta = GenFunction::register_function<float(Particle)>("particle_eta",
+        FUNC(([](const Particle& p){
+            return p.v.Eta();
+        })));
+
+
+Function<float(Particle)>& lepton_relIso = GenFunction::register_function<float(Particle)>("lepton_relIso",
+        FUNC(([](const Particle& p){
+            return p.lepton.relIso;
+        })));
 
 decltype(auto)
 construct_jets_value(MiniTreeDataSet& mt){
+
     mt.track_branch<int>("nJet");
     mt.track_branch_vec<float>("nJet", "Jet_pt");
     mt.track_branch_vec<float>("nJet", "Jet_eta");
@@ -153,24 +191,28 @@ construct_leptons_value(MiniTreeDataSet& mt){
     auto leptons = apply(GenFunction::register_function<std::vector<Particle>(std::vector<TLorentzVector>,
                                                                               std::vector<int>,
                                                                               std::vector<int>,
-                                                                              std::vector<int>)>("build_reco_leptons",
+                                                                              std::vector<int>,
+                                                                              std::vector<float>)>("build_reco_leptons",
         FUNC(([](const std::vector<TLorentzVector>& vs,
                  const std::vector<int>& pdgIds,
                  const std::vector<int>& mcMatchPdgIds,
-                 const std::vector<int>& charges
+                 const std::vector<int>& charges,
+                 const std::vector<float>& relIso
                  ){
             std::vector<Particle> leptons;
             for(int i=0; i<vs.size(); i++){
                 Particle p = Particle::Lepton(i, vs[i], {pdgIds[i],
                                                          mcMatchPdgIds[i],
-                                                         charges[i]});
+                                                         charges[i],
+                                                         relIso[i]});
                 leptons.push_back(p);
             }
             return leptons;
         }))), fv::tuple(LepGood_4v,
                         mt.track_branch_vec< int >("nLepGood", "LepGood_pdgId"),
                         mt.track_branch_vec< int >("nLepGood", "LepGood_mcMatchPdgId"),
-                        mt.track_branch_vec< int >("nLepGood", "LepGood_charge")
+                        mt.track_branch_vec< int >("nLepGood", "LepGood_charge"),
+                        mt.track_branch_vec<float>("nLepGood", "LepGood_miniRelIso")
                         ), "leptons");
     return leptons;
 }

+ 28 - 14
analysis/selection.hpp

@@ -41,22 +41,36 @@
 
 #include "analysis/common/obj_types.hpp"
 
-ObsFilter* SR4j;
-ObsFilter* SR5j;
-ObsFilter* SR6j;
+struct EventSelection{
+    ObsFilter* trilepton;
+    ObsFilter* b_jet3;
+    ObsFilter* z_mass_veto;
+
+    ObsFilter* base_sel;
+
+    ObsFilter* J4;
+    ObsFilter* J5;
+    ObsFilter* J6;
+
+    ObsFilter* SR4j;
+    ObsFilter* SR5j;
+    ObsFilter* SR6j;
+};
+
+EventSelection event_selection;
 
 void init_selection(){
     auto leptons = lookup<std::vector<Particle>>("leptons");
     auto jets    = lookup<std::vector<Particle>>("jets");
 
     // Require *exactly* three charged leptons
-    auto trilepton = obs_filter("trilepton",GenFunction::register_function<bool()>("trilepton",
+    event_selection.trilepton = obs_filter("trilepton",GenFunction::register_function<bool()>("trilepton",
         FUNC(([leptons](){
             return leptons->get_value().size() == 3;
         }))));
 
-    // Require at least three charged b-jets
-    auto b_jet = obs_filter("b_jet",GenFunction::register_function<bool()>("b_jet",
+    // Require at least three b-jets
+    event_selection.b_jet3 = obs_filter("b_jet3",GenFunction::register_function<bool()>("b_jet3",
         FUNC(([jets](){
             int n_b_jets = 0;
             for(auto jet : jets->get_value()){
@@ -68,7 +82,7 @@ void init_selection(){
 
     // Require all same-flavour OS dilepton combinations are outside the Z mass
     // window (70,105)
-    auto z_mass_window = obs_filter("z_mass_window",GenFunction::register_function<bool()>("z_mass_window",
+    event_selection.z_mass_veto = obs_filter("z_mass_veto",GenFunction::register_function<bool()>("z_mass_veto",
         FUNC(([leptons](){
             auto& leps = leptons->get_value();
             int n = leps.size();
@@ -87,24 +101,24 @@ void init_selection(){
         }))));
 
 
-    auto J4 = obs_filter("4jet_selection",GenFunction::register_function<bool()>("4jet_selection",
+    event_selection.J4 = obs_filter("4jet_selection",GenFunction::register_function<bool()>("4jet_selection",
         FUNC(([jets](){
             return jets->get_value().size() >= 4;
         }))));
 
-    auto J5 = obs_filter("5jet_selection",GenFunction::register_function<bool()>("5jet_selection",
+    event_selection.J5 = obs_filter("5jet_selection",GenFunction::register_function<bool()>("5jet_selection",
         FUNC(([jets](){
             return jets->get_value().size() >= 5;
         }))));
 
-    auto J6 = obs_filter("6jet_selection",GenFunction::register_function<bool()>("6jet_selection",
+    event_selection.J6 = obs_filter("6jet_selection",GenFunction::register_function<bool()>("6jet_selection",
         FUNC(([jets](){
             return jets->get_value().size() >= 6;
         }))));
 
-    auto base_sel = ObsFilter::conj(z_mass_window, ObsFilter::conj(trilepton, b_jet));
-    SR4j = ObsFilter::conj(base_sel, J4);
-    SR5j = ObsFilter::conj(base_sel, J5);
-    SR6j = ObsFilter::conj(base_sel, J6);
+    event_selection.base_sel = ObsFilter::conj(event_selection.z_mass_veto, ObsFilter::conj(event_selection.trilepton, event_selection.b_jet3));
+    event_selection.SR4j = ObsFilter::conj(event_selection.base_sel, event_selection.J4);
+    event_selection.SR5j = ObsFilter::conj(event_selection.base_sel, event_selection.J5);
+    event_selection.SR6j = ObsFilter::conj(event_selection.base_sel, event_selection.J6);
 }
 #endif // SELECTION_HPP

+ 5 - 4
cmake/convert_nb

@@ -15,8 +15,9 @@ else
     exec_opt=""
 fi
 
-for nb_file in `find . -type f -name "*.ipynb" ! -path "*/.ipynb_checkpoints/*"`
-do
+nb_files=$(find . -type f -name "*.ipynb" ! -path "*/.ipynb_checkpoints/*")
+while read -r nb_file; do
     echo "Converting file: $nb_file"
-    $1 --to html --template full $exec_opt --output-dir $2 $nb_file
-done 
+    echo $1 --to html --template full $exec_opt --output-dir "$2" $nb_file
+    $1 --to html --template full $exec_opt --output-dir "$2" "$nb_file"
+done <<< "$nb_files"

+ 10 - 0
filval/api.hpp

@@ -164,6 +164,16 @@ namespace fv{
         return pair<T1,T2>(lookup<T1>(name1), lookup<T2>(name2), alias);
     }
 
+    template <typename T>
+    decltype(auto)
+    reduce(Function<T(std::vector<T>)>& fn, Value<std::vector<T>>* v, const std::string& alias=""){
+        typedef T type;
+        const std::string& name = Reduce<T>::fmt_name(fn, v);
+        if (check_exists<type>(name))
+            return lookup<type>(name);
+        else
+            return (Value<type>*)new Reduce<T>(fn, v, alias);
+    }
 
     template <typename T>
     decltype(auto)

+ 9 - 5
filval/value.hpp

@@ -950,19 +950,23 @@ class TupFilter : public DerivedValue<std::vector<std::tuple<ArgTypes...>>>{
 template <typename T>
 class Reduce : public DerivedValue<T>{
     private:
-        Function<T(std::vector<T>)>& reduce;
+        Function<T(std::vector<T>)>& reduce_fn;
 
         void update_value(){
-            this->value = reduce(v->get_value());
+            this->value = reduce_fn(v->get_value());
         }
 
     protected:
         Value<std::vector<T> >* v;
 
     public:
-        Reduce(Function<T(std::vector<T>)>& reduce, Value<std::vector<T> >* v, const std::string alias)
-          :DerivedValue<T>("reduceWith("+reduce.get_name()+":"+v->get_name()+")", alias),
-           reduce(reduce), v(v) { }
+        static std::string fmt_name(Function<T(std::vector<T>)>& reduce_fn, Value<std::vector<T>>* v){
+            return "reduce("+reduce_fn.get_name()+":"+v->get_name()+")";
+        }
+
+        Reduce(Function<T(std::vector<T>)>& reduce_fn, Value<std::vector<T> >* v, const std::string alias)
+          :DerivedValue<T>(fmt_name(reduce_fn, v), alias),
+           reduce_fn(reduce_fn), v(v) { }
 };
 
 /**

+ 1 - 0
python/.gitignore

@@ -0,0 +1 @@
+figures/

File diff suppressed because it is too large
+ 236 - 31
python/Plotter Testing.ipynb


+ 25 - 44
python/TTTT_Analysis.ipynb

@@ -67,7 +67,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 3,
    "metadata": {
     "collapsed": false,
     "deletable": true,
@@ -78,28 +78,24 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Running analysis for sample:  TTZ\n",
-      "Writing log data to ../data/TTZToLLNuNu_treeProducerSusyMultilepton_tree_result.log\n",
-      "Running analysis for sample:  TTW\n",
-      "Writing log data to ../data/TTWToLNu_treeProducerSusyMultilepton_tree_result.log\n",
-      "Running analysis for sample:  TTH1\n",
-      "Writing log data to ../data/TTHnobb_mWCutfix_ext1_treeProducerSusyMultilepton_tree_result.log\n",
-      "Running analysis for sample:  TTTT\n",
-      "Writing log data to ../data/TTTT_ext_treeProducerSusyMultilepton_tree_result.log\n"
+      "Loading unchanged result file  ../data/TTZToLLNuNu_treeProducerSusyMultilepton_tree_result.root\n",
+      "Loading unchanged result file  ../data/TTWToLNu_treeProducerSusyMultilepton_tree_result.root\n",
+      "Loading unchanged result file  ../data/TTHnobb_mWCutfix_ext1_treeProducerSusyMultilepton_tree_result.root\n",
+      "Loading unchanged result file  ../data/TTTT_ext_treeProducerSusyMultilepton_tree_result.root\n"
      ]
     }
    ],
    "source": [
     "rs_TTZ  = ResultSet(\"TTZ\",  \"../data/TTZToLLNuNu_treeProducerSusyMultilepton_tree.root\")\n",
     "rs_TTW  = ResultSet(\"TTW\",  \"../data/TTWToLNu_treeProducerSusyMultilepton_tree.root\")\n",
-    "rs_TTH1 = ResultSet(\"TTH1\", \"../data/TTHnobb_mWCutfix_ext1_treeProducerSusyMultilepton_tree.root\")\n",
+    "rs_TTH = ResultSet(\"TTH\", \"../data/TTHnobb_mWCutfix_ext1_treeProducerSusyMultilepton_tree.root\")\n",
     "# rs_TTH2 = ResultSet(\"TTH2\", \"../data/TTHnobb_mWCutfix_ch0_treeProducerSusyMultilepton_tree.root\")\n",
     "rs_TTTT = ResultSet(\"TTTT\", \"../data/TTTT_ext_treeProducerSusyMultilepton_tree.root\")"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 15,
    "metadata": {
     "collapsed": false,
     "deletable": true,
@@ -112,10 +108,10 @@
        "\n",
        "| L=N/A |           SR4j            |            SR5j           |            SR6J           |\n",
        "| ----- | -------------------------:| -------------------------:| -------------------------:|\n",
-       "| tttt  | 818.0 | 779.0 | 687.0 |\n",
-       "| ttZ   | 410.0  | 296.0  | 183.0  |\n",
-       "| ttW   | 183.0  | 123.0  | 62.0  |\n",
-       "| ttH   | 683.0 | 526.0 | 323.0 |\n",
+       "| tttt  | 47.23      | 44.97      | 39.66      |\n",
+       "| ttZ   | 78.04       | 56.34       | 34.83       |\n",
+       "| ttW   | 44.39       | 29.84       | 15.04       |\n",
+       "| tth   | 91.36       | 70.36       | 43.20       |\n",
        "\n"
       ]
      },
@@ -123,20 +119,11 @@
      "output_type": "display_data"
     },
     {
-     "data": {
-      "text/markdown": [
-       "\n",
-       "| L=N/A |           SR4j            |            SR5j           |            SR6J           |\n",
-       "| ----- | -------------------------:| -------------------------:| -------------------------:|\n",
-       "| tttt  | 8.6 | 8.18997555012225 | 7.222738386308069 |\n",
-       "| ttZ   | 4.310513447432763  | 3.1119804400977995  | 1.923960880195599  |\n",
-       "| ttW   | 1.923960880195599  | 1.2931540342298289  | 0.6518337408312959  |\n",
-       "| ttH   | 7.180684596577017 | 5.530073349633252 | 3.395843520782396 |\n",
-       "\n"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "5196.308815515672\n"
+     ]
     }
    ],
    "source": [
@@ -154,25 +141,19 @@
     "# print(rs_TTW.SR6j_count[0])\n",
     "# print(rs_TTH1.SR6j_count[0])\n",
     "# print(rs_TTTT.SR6j_count[0])\n",
-    "x = 12\n",
-    "s = 1.0\n",
-    "display_markdown(f\"\"\"\n",
-    "| L=N/A |           SR4j            |            SR5j           |            SR6J           |\n",
-    "| ----- | -------------------------:| -------------------------:| -------------------------:|\n",
-    "| tttt  | {s*rs_TTTT.SR4j_count[0]} | {s*rs_TTTT.SR5j_count[0]} | {s*rs_TTTT.SR6j_count[0]} |\n",
-    "| ttZ   | {s*rs_TTZ.SR4j_count[0]}  | {s*rs_TTZ.SR5j_count[0]}  | {s*rs_TTZ.SR6j_count[0]}  |\n",
-    "| ttW   | {s*rs_TTW.SR4j_count[0]}  | {s*rs_TTW.SR5j_count[0]}  | {s*rs_TTW.SR6j_count[0]}  |\n",
-    "| ttH   | {s*rs_TTH1.SR4j_count[0]} | {s*rs_TTH1.SR5j_count[0]} | {s*rs_TTH1.SR6j_count[0]} |\n",
+    "print(rs_TTTT.lumi)\n",
+    "scale_TTTT = 300/rs_TTTT.lumi\n",
+    "scale_TTZ = 300/rs_TTZ.lumi\n",
+    "scale_TTW = 300/rs_TTW.lumi\n",
+    "scale_TTH = 300/rs_TTH.lumi\n",
     "\n",
-    "\"\"\", raw=True)\n",
-    "s = 8.6/818\n",
     "display_markdown(f\"\"\"\n",
     "| L=N/A |           SR4j            |            SR5j           |            SR6J           |\n",
     "| ----- | -------------------------:| -------------------------:| -------------------------:|\n",
-    "| tttt  | {s*rs_TTTT.SR4j_count[0]} | {s*rs_TTTT.SR5j_count[0]} | {s*rs_TTTT.SR6j_count[0]} |\n",
-    "| ttZ   | {s*rs_TTZ.SR4j_count[0]}  | {s*rs_TTZ.SR5j_count[0]}  | {s*rs_TTZ.SR6j_count[0]}  |\n",
-    "| ttW   | {s*rs_TTW.SR4j_count[0]}  | {s*rs_TTW.SR5j_count[0]}  | {s*rs_TTW.SR6j_count[0]}  |\n",
-    "| ttH   | {s*rs_TTH1.SR4j_count[0]} | {s*rs_TTH1.SR5j_count[0]} | {s*rs_TTH1.SR6j_count[0]} |\n",
+    "| tttt  | {rs_TTTT.SR4j_count*scale_TTTT:.02f}      | {rs_TTTT.SR5j_count*scale_TTTT:.02f}      | {rs_TTTT.SR6j_count*scale_TTTT:.02f}      |\n",
+    "| ttZ   | {rs_TTZ.SR4j_count*scale_TTZ:.02f}       | {rs_TTZ.SR5j_count*scale_TTZ:.02f}       | {rs_TTZ.SR6j_count*scale_TTZ:.02f}       |\n",
+    "| ttW   | {rs_TTW.SR4j_count*scale_TTW:.02f}       | {rs_TTW.SR5j_count*scale_TTW:.02f}       | {rs_TTW.SR6j_count*scale_TTW:.02f}       |\n",
+    "| tth   | {rs_TTH.SR4j_count*scale_TTH:.02f}       | {rs_TTH.SR5j_count*scale_TTH:.02f}       | {rs_TTH.SR6j_count*scale_TTH:.02f}       |\n",
     "\n",
     "\"\"\", raw=True)"
    ]

+ 138 - 42
python/plotter.py

@@ -1,4 +1,5 @@
 #!/usr/bin/env python3
+import math
 import matplotlib as mpl
 mpl.rc('font', **{'family': 'sans-serif', 'sans-serif': ['Helvetica']})
 mpl.rc('font', **{'family': 'serif', 'serif': ['Palatino']})
@@ -6,6 +7,26 @@ mpl.rc('text', usetex=True)
 mpl.rc('savefig', dpi=120)
 
 
+def add_decorations(axes, luminosity, energy):
+    cms_prelim = r'{\raggedright{}\textsf{\textbf{CMS}}\\ \emph{Preliminary}}'
+    axes.text(0.01, 0.98, cms_prelim,
+              horizontalalignment='left',
+              verticalalignment='top',
+              transform=axes.transAxes)
+
+    lumi = ""
+    energy_str = ""
+    if luminosity is not None:
+        lumi = r'${} \mathrm{{fb}}^{{-1}}$'.format(luminosity)
+    if energy is not None:
+        energy_str = r'({} TeV)'.format(energy)
+
+    axes.text(1, 1, ' '.join([lumi, energy_str]),
+              horizontalalignment='right',
+              verticalalignment='bottom',
+              transform=axes.transAxes)
+
+
 class StackHist:
 
     def __init__(self, title=""):
@@ -22,30 +43,29 @@ class StackHist:
         self.data = None
 
     @staticmethod
-    def to_bin_list(th1, scale=1):
+    def to_bin_list(th1):
         bins = []
         for i in range(th1.GetNbinsX()):
             center = th1.GetBinCenter(i + 1)
             width = th1.GetBinWidth(i + 1)
             content = th1.GetBinContent(i + 1)
-            bins.append((center-width/2, center+width/2, content*scale))
+            bins.append((center-width/2, center+width/2, content))
         return bins
 
-    def add_mc_background(self, th1, label, lumi=None):
-        self.backgrounds.append((label, lumi, self.to_bin_list(th1)))
+    def add_mc_background(self, th1, label, lumi=None, plot_color=''):
+        self.backgrounds.append((label, lumi, self.to_bin_list(th1), plot_color))
 
-    def set_mc_signal(self, th1, label, lumi=None, stack=True, scale=1):
-        if scale != 1:
-            label = r"{}$\times{:02d}$".format(label, scale)
-        self.signal = (label, lumi, self.to_bin_list(th1, scale))
+    def set_mc_signal(self, th1, label, lumi=None, stack=True, scale=1, plot_color=''):
+        self.signal = (label, lumi, self.to_bin_list(th1), plot_color)
         self.signal_stack = stack
+        self.signal_scale = scale
 
-    def set_data(self, th1, lumi=None):
-        self.data = ('data', lumi, self.to_bin_list(th1))
+    def set_data(self, th1, lumi=None, plot_color=''):
+        self.data = ('data', lumi, self.to_bin_list(th1), plot_color)
         self.luminosity = lumi
 
     def _verify_binning_match(self):
-        bins_count = [len(bins) for label, lumi, bins in self.backgrounds]
+        bins_count = [len(bins) for _, _, bins, _ in self.backgrounds]
         if self.signal is not None:
             bins_count.append(len(self.signal[2]))
         if self.data is not None:
@@ -53,37 +73,40 @@ class StackHist:
         n_bins = bins_count[0]
         if any(bin_count != n_bins for bin_count in bins_count):
             raise ValueError("all histograms must have the same number of bins")
-        return n_bins
-
-    def _add_decorations(self, axes):
-        cms_prelim = r'{\raggedright{}\textsf{\textbf{CMS}}\\ \emph{Preliminary}}'
-        axes.text(0.01, 0.99, cms_prelim,
-                  horizontalalignment='left',
-                  verticalalignment='top',
-                  transform=axes.transAxes)
-
-        lumi = ""
-        energy = ""
-        if self.luminosity is not None:
-            lumi = r'${} \mathrm{{fb}}^{{-1}}$'.format(self.luminosity)
-        if self.energy is not None:
-            energy = r'({} TeV)'.format(self.energy)
-
-        axes.text(1, 1, ' '.join([lumi, energy]),
-                  horizontalalignment='right',
-                  verticalalignment='bottom',
-                  transform=axes.transAxes)
-
-    def draw(self, axes):
-        n_bins = self._verify_binning_match()
-        bottoms = [0]*n_bins
+        self.n_bins = n_bins
+
+    def save(self, fname):
+        from matplotlib.transforms import Bbox
+        def full_extent(ax, pad=0.0):
+            """Get the full extent of an axes, including axes labels, tick labels, and
+            titles."""
+            # For text objects, we need to draw the figure first, otherwise the extents
+            # are undefined.
+            ax.figure.canvas.draw()
+            items = ax.get_xticklabels() + ax.get_yticklabels()
+            items += [ax, ax.title, ax.xaxis.label, ax.yaxis.label]
+            # items += [ax, ax.title]
+            bbox = Bbox.union([item.get_window_extent() for item in items])
+
+            return bbox.expanded(1.0 + pad, 1.0 + pad)
+
+        extents = []
+        for axes in self.axeses:
+            extents.append(full_extent(axes).transformed(axes.figure.dpi_scale_trans.inverted()))
+        extent = Bbox.union(extents)
+        axes.figure.savefig('figures/'+fname, bbox_inches=extent)
+
+    def do_draw(self, axes):
+        self.axeses = [axes]
+        self._verify_binning_match()
+        bottoms = [0]*self.n_bins
 
         if self.logx:
             axes.set_xscale('log')
         if self.logy:
             axes.set_yscale('log')
 
-        def draw_bar(label, lumi, bins, stack=True, **kwargs):
+        def draw_bar(label, lumi, bins, plot_color, scale=1, stack=True, **kwargs):
             if stack:
                 lefts = []
                 widths = []
@@ -93,12 +116,14 @@ class StackHist:
                     widths.append(right-left)
                     if lumi is not None:
                         content *= self.luminosity/lumi
+                    content *= scale
                     heights.append(content)
 
-                axes.bar(lefts, heights, widths, bottoms, label=label, **kwargs)
+                axes.bar(lefts, heights, widths, bottoms, label=label, color=plot_color, **kwargs)
                 for i, (_, _, content) in enumerate(bins):
                     if lumi is not None:
                         content *= self.luminosity/lumi
+                    content *= scale
                     bottoms[i] += content
             else:
                 xs = [bins[0][0] - (bins[0][1]-bins[0][0])/2]
@@ -107,31 +132,102 @@ class StackHist:
                     width2 = (right-left)/2
                     if lumi is not None:
                         content *= self.luminosity/lumi
+                    content *= scale
                     xs.append(left-width2)
                     ys.append(content)
                     xs.append(right-width2)
                     ys.append(content)
                 xs.append(bins[-1][0] + (bins[-1][1]-bins[-1][0])/2)
                 ys.append(0)
-                axes.plot(xs, ys, label=label, **kwargs)
+                axes.plot(xs, ys, label=label, color=plot_color, **kwargs)
 
         if self.signal is not None and self.signal_stack:
-            draw_bar(*self.signal, hatch='/')
+            label, lumi, bins, plot_color = self.signal
+            if self.signal_scale != 1:
+                label = r"{}$\times{:d}$".format(label, self.signal_scale)
+            draw_bar(label, lumi, bins, plot_color, scale=self.signal_scale, hatch='/')
 
         for background in self.backgrounds:
             draw_bar(*background)
 
         if self.signal is not None and not self.signal_stack:
-            draw_bar(*self.signal, stack=False, color='k')
+            # draw_bar(*self.signal, stack=False, color='k')
+            label, lumi, bins, plot_color = self.signal
+            if self.signal_scale != 1:
+                label = r"{}$\times{:d}$".format(label, self.signal_scale)
+            draw_bar(label, lumi, bins, plot_color, scale=self.signal_scale, stack=False)
 
         axes.set_title(self.title)
         axes.set_xlabel(self.xlabel)
         axes.set_ylabel(self.ylabel)
         axes.set_xlim(*self.xlim)
         # axes.set_ylim(*self.ylim)
-        axes.set_ylim(None, max(bottoms)*1.2)
+        if self.logy:
+            axes.set_ylim(None, math.exp(math.log(max(bottoms))*1.4))
+        else:
+            axes.set_ylim(None, max(bottoms)*1.2)
         axes.legend(frameon=True, ncol=2)
-        self._add_decorations(axes)
+        add_decorations(axes, self.luminosity, self.energy)
+
+    def draw(self, axes, save=True, **kwargs):
+        self.do_draw(axes, **kwargs)
+        if save:
+            self.save(self.title+".png")
+
+
+class StackHistWithSignificance(StackHist):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+    def do_draw(self, axes, bin_significance=True, low_cut_significance=False, high_cut_significance=False):
+        bottom_box, _, top_box = axes.get_position().splity(0.28, 0.30)
+        axes.set_position(top_box)
+        super().do_draw(axes)
+        axes.set_xticks([])
+        rhs_color = '#cc6600'
+
+        bottom = axes.get_figure().add_axes(bottom_box)
+        bottom_rhs = bottom.twinx()
+        self.axeses = [axes, bottom, bottom_rhs]
+        bgs = [0]*self.n_bins
+        for (_, _, bins, _) in self.backgrounds:
+            for i, (left, right, value) in enumerate(bins):
+                bgs[i] += value
+
+        sigs = [0]*self.n_bins
+        if bin_significance:
+            xs = []
+            for i, (left, right, value) in enumerate(self.signal[2]):
+                sigs[i] += value
+                xs.append(left)
+            xs, ys = zip(*[(x, sig/(sig+bg)) for x, sig, bg in zip(xs, sigs, bgs) if (sig+bg)>0])
+            bottom.plot(xs, ys, '.k')
+
+        if high_cut_significance:
+            # s/(s+b) for events passing a minimum cut requirement
+            min_bg = [sum(bgs[i:]) for i in range(self.n_bins)]
+            min_sig = [sum(sigs[i:]) for i in range(self.n_bins)]
+            min_xs, min_ys = zip(*[(x, sig/math.sqrt(sig+bg)) for x, sig, bg in zip(xs, min_sig, min_bg)
+                                   if (sig+bg) > 0])
+            bottom_rhs.plot(min_xs, min_ys, '->', color=rhs_color)
+
+        if low_cut_significance:
+            # s/(s+b) for events passing a maximum cut requirement
+            max_bg = [sum(bgs[:i]) for i in range(self.n_bins)]
+            max_sig = [sum(sigs[:i]) for i in range(self.n_bins)]
+            max_xs, max_ys = zip(*[(x, sig/math.sqrt(sig+bg)) for x, sig, bg in zip(xs, max_sig, max_bg)
+                                   if (sig+bg) > 0])
+            bottom_rhs.plot(max_xs, max_ys, '-<', color=rhs_color)
+
+        bottom.set_ylabel(r'$S/(S+B)$')
+        bottom.set_xlim(axes.get_xlim())
+        bottom.set_ylim((0, 1.1))
+        if low_cut_significance or high_cut_significance:
+            bottom_rhs.set_ylabel(r'$S/\sqrt{S+B}$')
+            bottom_rhs.yaxis.label.set_color(rhs_color)
+            bottom_rhs.tick_params(axis='y', colors=rhs_color, size=4, width=1.5)
+        # bottom.grid()
 
 
 if __name__ == '__main__':

+ 2 - 0
python/utils.py

@@ -192,6 +192,8 @@ class ResultSet:
                 obj.SetDirectory(0)  # disconnects Object from file
             except AttributeError:
                 pass
+            if 'ROOT.vector<int>' in str(type(obj)) and '_count' in name:
+                obj = obj[0]
             self.map[name] = obj
             setattr(self, name, obj)
         file.Close()