From ed08b141984421196e28512ba935df0d077caa18 Mon Sep 17 00:00:00 2001 From: fbilandz Date: Fri, 3 May 2024 13:28:47 +0200 Subject: [PATCH 1/5] Adjustment for Slurm operation --- inclusion/condor/closure.py | 23 +++++--- inclusion/condor/discriminator.py | 25 +++++--- inclusion/condor/eff_and_sf.py | 65 ++++++++++++++------- inclusion/condor/eff_and_sf_aggr.py | 42 +++++++++----- inclusion/condor/hadd_counts.py | 45 +++++++++------ inclusion/condor/hadd_eff.py | 47 ++++++++++----- inclusion/condor/hadd_histo.py | 49 +++++++++++----- inclusion/condor/job_writer.py | 75 +++++++++++++++++++++--- inclusion/condor/processing.py | 30 +++++++--- inclusion/condor/union_calculator.py | 25 +++++--- inclusion/config/main.py | 83 +++++++++++++++++++++------ inclusion/run.py | 86 +++++++++++++++++++--------- 12 files changed, 429 insertions(+), 166 deletions(-) diff --git a/inclusion/condor/closure.py b/inclusion/condor/closure.py index 00e0c81..138c1b8 100644 --- a/inclusion/condor/closure.py +++ b/inclusion/condor/closure.py @@ -47,7 +47,16 @@ def closure(args) : jw.add_string('echo "{} for channel ${{1}} and single trigger ${{2}} done."'.format(script)) #### Write submission file - jw.write_condor(filename=outs_submit, + if main.machine == "slurm": + jw.write_batch(filename=outs_submit, + real_exec=utils.build_script_path(script), + shell_exec=outs_job, + outfile=outs_check, + logfile=outs_log, + queue=main.queue, + machine=main.machine) + else: + jw.write_condor(filename=outs_submit, real_exec=utils.build_script_path(script), shell_exec=outs_job, outfile=outs_check, @@ -55,10 +64,10 @@ def closure(args) : queue=main.queue, machine=main.machine) - qlines = [] - for chn in args.channels: - for trig in args.closure_single_triggers: - qlines.append(' {},{}'.format(chn,trig)) + qlines = [] + for chn in args.channels: + for trig in args.closure_single_triggers: + qlines.append(' {},{}'.format(chn,trig)) - jw.write_queue( qvars=('channel', 'closure_single_trigger'), - qlines=qlines ) + jw.write_queue( qvars=('channel', 'closure_single_trigger'), + qlines=qlines ) diff --git a/inclusion/condor/discriminator.py b/inclusion/condor/discriminator.py index 5d7a54b..cdb7ae5 100644 --- a/inclusion/condor/discriminator.py +++ b/inclusion/condor/discriminator.py @@ -46,14 +46,23 @@ def discriminator(args): jw.add_string('echo "Script {} with channel {} done."'.format(script, args.channels[i])) #### Write submission file - jw.write_condor(filename=outs_submit[i], - real_exec=utils.build_script_path(script), - shell_exec=outs_job[i], - outfile=outs_check[i], - logfile=outs_log[i], - queue=main.queue, - machine=main.machine) - jw.write_queue() + if main.machine == "slurm": + jw.write_batch(filename=outs_submit[i], + real_exec=utils.build_script_path(script), + shell_exec=outs_job[i], + outfile=outs_check[i], + logfile=outs_log[i], + queue=main.queue, + machine=main.machine) + else: + jw.write_condor(filename=outs_submit[i], + real_exec=utils.build_script_path(script), + shell_exec=outs_job[i], + outfile=outs_check[i], + logfile=outs_log[i], + queue=main.queue, + machine=main.machine) + jw.write_queue() # -- Parse options if __name__ == '__main__': diff --git a/inclusion/condor/eff_and_sf.py b/inclusion/condor/eff_and_sf.py index 287e60d..c3e8309 100644 --- a/inclusion/condor/eff_and_sf.py +++ b/inclusion/condor/eff_and_sf.py @@ -56,26 +56,49 @@ def eff_and_sf(args): jw.add_string('echo "{} done."'.format(script)) #### Write submission file - jw.write_condor(filename=outs_submit, - real_exec=utils.build_script_path(script), - shell_exec=outs_job, - outfile=outs_check, - logfile=outs_log, - queue=main.queue, - machine=main.machine) + if main.machine == "slurm": + cfg = importlib.import_module(args.configuration) + input_params = [] + for chn in args.channels: + if chn == args.channels[0]: + triggercomb = utils.generate_trigger_combinations(chn, cfg.triggers, + cfg.exclusive) + else: + triggercomb += utils.generate_trigger_combinations(chn, cfg.triggers, + cfg.exclusive) + + for tcomb in set(triggercomb): + input_params.append('"{}"'.format( utils.join_name_trigger_intersection(tcomb)) ) - cfg = importlib.import_module(args.configuration) - qlines = [] - for chn in args.channels: - if chn == args.channels[0]: - triggercomb = utils.generate_trigger_combinations(chn, cfg.triggers, - cfg.exclusive) - else: - triggercomb += utils.generate_trigger_combinations(chn, cfg.triggers, - cfg.exclusive) - - for tcomb in set(triggercomb): - qlines.append(' {}'.format( utils.join_name_trigger_intersection(tcomb)) ) + jw.write_batch(filename=outs_submit, + real_exec=utils.build_script_path(script), + shell_exec=outs_job, + outfile=outs_check, + logfile=outs_log, + queue=main.queue, + machine=main.machine, + input_output_params=input_params ) + else: + jw.write_condor(filename=outs_submit, + real_exec=utils.build_script_path(script), + shell_exec=outs_job, + outfile=outs_check, + logfile=outs_log, + queue=main.queue, + machine=main.machine) - jw.write_queue( qvars=('triggercomb',), - qlines=qlines ) + cfg = importlib.import_module(args.configuration) + qlines = [] + for chn in args.channels: + if chn == args.channels[0]: + triggercomb = utils.generate_trigger_combinations(chn, cfg.triggers, + cfg.exclusive) + else: + triggercomb += utils.generate_trigger_combinations(chn, cfg.triggers, + cfg.exclusive) + + for tcomb in set(triggercomb): + qlines.append(' {}'.format( utils.join_name_trigger_intersection(tcomb)) ) + + jw.write_queue( qvars=('triggercomb',), + qlines=qlines ) diff --git a/inclusion/condor/eff_and_sf_aggr.py b/inclusion/condor/eff_and_sf_aggr.py index f822f9e..83f0a3f 100644 --- a/inclusion/condor/eff_and_sf_aggr.py +++ b/inclusion/condor/eff_and_sf_aggr.py @@ -41,17 +41,31 @@ def eff_and_sf_aggr(args): jw.add_string('echo "{} done."'.format(script)) #### Write submission file - jw.write_condor(filename=outs_submit, - real_exec=utils.build_script_path(script), - shell_exec=outs_job, - outfile=outs_check, - logfile=outs_log, - queue=main.queue, - machine=main.machine) - - qlines = [] - for chn in args.channels: - qlines.append(' {}'.format(chn)) - - jw.write_queue( qvars=('channel',), - qlines=qlines ) + if main.machine == "slurm": + input_params = [] + for chn in args.channels: + input_params.append('"{}"'.format(chn)) + + jw.write_batch(filename=outs_submit, + real_exec=utils.build_script_path(script), + shell_exec=outs_job, + outfile=outs_check, + logfile=outs_log, + queue=main.queue, + machine=main.machine, + input_output_params=input_params) + else: + jw.write_condor(filename=outs_submit, + real_exec=utils.build_script_path(script), + shell_exec=outs_job, + outfile=outs_check, + logfile=outs_log, + queue=main.queue, + machine=main.machine) + + qlines = [] + for chn in args.channels: + qlines.append(' {}'.format(chn)) + + jw.write_queue( qvars=('channel',), + qlines=qlines ) diff --git a/inclusion/condor/hadd_counts.py b/inclusion/condor/hadd_counts.py index c59d10c..52097c0 100755 --- a/inclusion/condor/hadd_counts.py +++ b/inclusion/condor/hadd_counts.py @@ -77,29 +77,38 @@ def hadd_counts(args): inputs_join = {} nchannels = len(args.channels) for out1,out2,out3,out4 in zip(outs_job,outs_submit,outs_check,outs_log): - jw.write_condor(filename=out2, + if main.machine == "slurm": + jw.write_batch(filename=out2, real_exec=utils.build_script_path(script), shell_exec=out1, outfile=out3, logfile=out4, queue=main.queue, - machine=main.machine) + machine=main.machine,) + else: + jw.write_condor(filename=out2, + real_exec=utils.build_script_path(script), + shell_exec=out1, + outfile=out3, + logfile=out4, + queue=main.queue, + machine=main.machine) - qvars = None - qlines = [] - if out1 == outs_job[0]: - qvars = ('myoutput', 'channel', 'sample') - for it,t in enumerate(targets[nchannels:]): - smpl = args.samples[ int(it/nchannels) ] - chn = args.channels[ int(it%nchannels) ] - if chn not in inputs_join: - inputs_join[chn] = [] - inputs_join[chn].append(t) - qlines.append(' {}, {}, {}'.format(t,smpl,chn)) + qvars = None + qlines = [] + if out1 == outs_job[0]: + qvars = ('myoutput', 'channel', 'sample') + for it,t in enumerate(targets[nchannels:]): + smpl = args.samples[ int(it/nchannels) ] + chn = args.channels[ int(it%nchannels) ] + if chn not in inputs_join: + inputs_join[chn] = [] + inputs_join[chn].append(t) + qlines.append(' {}, {}, {}'.format(t,smpl,chn)) - elif out1 == outs_job[1]: - qvars = ('channel', 'myinputs') - for ichn,chn in enumerate(args.channels): - qlines.append(" {}, '{}'".format(chn, ' '.join(inputs_join[chn]))) + elif out1 == outs_job[1]: + qvars = ('channel', 'myinputs') + for ichn,chn in enumerate(args.channels): + qlines.append(" {}, '{}'".format(chn, ' '.join(inputs_join[chn]))) - jw.write_queue( qvars=qvars, qlines=qlines ) + jw.write_queue( qvars=qvars, qlines=qlines ) diff --git a/inclusion/condor/hadd_eff.py b/inclusion/condor/hadd_eff.py index 0925e38..91cde55 100755 --- a/inclusion/condor/hadd_eff.py +++ b/inclusion/condor/hadd_eff.py @@ -63,7 +63,28 @@ def hadd_eff(args): #### Write submission file inputs_join = [] for out1,out2,out3,out4 in zip(outs_job,outs_submit,outs_check,outs_log): - jw.write_condor(filename=out2, + if main.machine == "slurm": + input_output_params = [] + if out1 == outs_job[0]: + for t,smpl in zip(targets[1:], args.samples): + inputs = os.path.join(args.indir, smpl, args.outprefix + '*' + args.subtag + '.root ') + inputs_join.append(t) + # join subdatasets (different MC or Data subfolders, ex: TT_fullyHad, TT_semiLep, ...) + jw.add_string('"{} {}"'.format(t, inputs)) + elif out1 == outs_job[1]: + # join MC or Data subdatasets into a single one (ex: TT) + jw.add_string('"{} {}"'.format(targets[0], ' '.join(inputs_join))) + + jw.write_batch(filename=out2, + real_exec='/dev/null', + shell_exec=out1, + outfile=out3, + logfile=out4, + queue=main.queue, + machine=main.machine, + input_output_params=input_output_params) + else: + jw.write_condor(filename=out2, real_exec='/dev/null', shell_exec=out1, outfile=out3, @@ -71,16 +92,16 @@ def hadd_eff(args): queue=main.queue, machine=main.machine) - qlines = [] - if out1 == outs_job[0]: - for t,smpl in zip(targets[1:], args.samples): - inputs = os.path.join(args.indir, smpl, args.outprefix + '*' + args.subtag + '.root ') - inputs_join.append(t) - # join subdatasets (different MC or Data subfolders, ex: TT_fullyHad, TT_semiLep, ...) - jw.add_string(' {}, {}'.format(t, inputs)) - elif out1 == outs_job[1]: - # join MC or Data subdatasets into a single one (ex: TT) - jw.add_string(' {}, {}'.format(targets[0], ' '.join(inputs_join))) + qlines = [] + if out1 == outs_job[0]: + for t,smpl in zip(targets[1:], args.samples): + inputs = os.path.join(args.indir, smpl, args.outprefix + '*' + args.subtag + '.root ') + inputs_join.append(t) + # join subdatasets (different MC or Data subfolders, ex: TT_fullyHad, TT_semiLep, ...) + jw.add_string(' {}, {}'.format(t, inputs)) + elif out1 == outs_job[1]: + # join MC or Data subdatasets into a single one (ex: TT) + jw.add_string(' {}, {}'.format(targets[0], ' '.join(inputs_join))) - jw.write_queue( qvars=('myoutput', 'myinputs'), - qlines=qlines ) + jw.write_queue( qvars=('myoutput', 'myinputs'), + qlines=qlines ) diff --git a/inclusion/condor/hadd_histo.py b/inclusion/condor/hadd_histo.py index ce8468a..79c7320 100755 --- a/inclusion/condor/hadd_histo.py +++ b/inclusion/condor/hadd_histo.py @@ -62,7 +62,28 @@ def hadd_histo(args): #### Write submission file inputs_join = [] for out1,out2,out3,out4 in zip(outs_job,outs_submit,outs_check,outs_log): - jw.write_condor(filename=out2, + if main.machine == "slurm": + input_output_params = [] + if out1 == outs_job[0]: + for t,smpl in zip(targets[1:], args.samples): + inputs = os.path.join(args.indir, smpl, args.tprefix + '*' + args.subtag + '.root') + inputs_join.append(t) + # join subdatasets (different MC or Data subfolders, ex: TT_fullyHad, TT_semiLep, ...) + input_output_params.append('"{} {}"'.format(t, inputs)) + elif out1 == outs_job[1]: + # join MC or Data subdatasets into a single one (ex: TT) + input_output_params.append('"{} {}"'.format(targets[0], ' '.join(inputs_join))) + + jw.write_batch(filename=out2, + real_exec='/dev/null', + shell_exec=out1, + outfile=out3, + logfile=out4, + queue=main.queue, + machine=main.machine, + input_output_params=input_output_params) + else: + jw.write_condor(filename=out2, real_exec='/dev/null', shell_exec=out1, outfile=out3, @@ -70,16 +91,16 @@ def hadd_histo(args): queue=main.queue, machine=main.machine) - qlines = [] - if out1 == outs_job[0]: - for t,smpl in zip(targets[1:], args.samples): - inputs = os.path.join(args.indir, smpl, args.tprefix + '*' + args.subtag + '.root') - inputs_join.append(t) - # join subdatasets (different MC or Data subfolders, ex: TT_fullyHad, TT_semiLep, ...) - qlines.append(' {}, {}'.format(t, inputs)) - elif out1 == outs_job[1]: - # join MC or Data subdatasets into a single one (ex: TT) - qlines.append(' {}, {}'.format(targets[0], ' '.join(inputs_join))) - - jw.write_queue( qvars=('myoutput', 'myinputs'), - qlines=qlines ) + qlines = [] + if out1 == outs_job[0]: + for t,smpl in zip(targets[1:], args.samples): + inputs = os.path.join(args.indir, smpl, args.tprefix + '*' + args.subtag + '.root') + inputs_join.append(t) + # join subdatasets (different MC or Data subfolders, ex: TT_fullyHad, TT_semiLep, ...) + qlines.append(' {}, {}'.format(t, inputs)) + elif out1 == outs_job[1]: + # join MC or Data subdatasets into a single one (ex: TT) + qlines.append(' {}, {}'.format(targets[0], ' '.join(inputs_join))) + + jw.write_queue( qvars=('myoutput', 'myinputs'), + qlines=qlines ) diff --git a/inclusion/condor/job_writer.py b/inclusion/condor/job_writer.py index ac18107..2b166af 100644 --- a/inclusion/condor/job_writer.py +++ b/inclusion/condor/job_writer.py @@ -4,6 +4,7 @@ import os import sys +import re parent_dir = os.path.abspath(__file__ + 3 * '/..') sys.path.insert(0, parent_dir) @@ -25,7 +26,7 @@ def add_string(self, string): self.f.write( string + self.endl ) @staticmethod - def define_output(localdir, data_folders, tag, names=''): + def define_output(localdir, data_folders, tag, names='', workflow='condor'): """ Defines where the shell and condor job files, and the HTCondor outputs will be stored. @@ -65,9 +66,12 @@ def define_output(localdir, data_folders, tag, names=''): for jd, cd, name in zip(job_d,out_d,names): mkdir(cd) job_f.append( os.path.join(jd, 'job{}.sh'.format(name)) ) - subm_f.append( os.path.join(jd, 'job{}.condor'.format(name)) ) - - base_name = 'C$(Cluster)_P$(Process)' + if main.machine == 'slurm': + subm_f.append( os.path.join(jd, 'job{}.bat'.format(name)) ) + base_name = '%x-%j' + else: + subm_f.append( os.path.join(jd, 'job{}.condor'.format(name)) ) + base_name = 'C$(Cluster)_P$(Process)' out_name = '{}.out'.format(base_name) log_name = '{}.log'.format(base_name) out_f.append( os.path.join(cd, out_name) ) @@ -116,7 +120,57 @@ def write_condor(self, filename, shell_exec, real_exec, outfile, logfile, self.f.write(m) os.system('chmod u+rwx '+ filename) - def write_queue(self, qvars=(), qlines=[]): + def write_batch(self, filename, shell_exec, real_exec, outfile, logfile, + queue, machine, input_args=[], additional_args = "", input_output_params = []): + + self.filenames.append(filename) + batch_name = os.path.dirname(shell_exec).split('/')[-1] + + if not filename: + raise ValueError("file_name must not be empty") + if not shell_exec and not real_exec: + raise ValueError("either command or executable must not be empty") + if not shell_exec: + raise ValueError("shell must not be empty") + + filenum_array = [] + arg_array = [] + if len(input_args): + filenum_array = [re.findall('(\d+)(?!.*\d)', input_arg)[0] for input_arg in input_args] + file_root_path = re.split('(\d+)(?!.*\d)', input_args[0]) + additional_args = " " + file_root_path[0] + '$SLURM_ARRAY_TASK_ID' + file_root_path[2] + + if len(input_output_params): + arg_array = ["case $SLURM_ARRAY_TASK_ID in"] + + for i in range(len(input_output_params)): + arg_array.append("\t{}) IN_N_OUT_ARGS={} ;;".format(i, input_output_params[i])) + filenum_array.append(str(i)) + arg_array.append("esac") + + additional_args = " $IN_N_OUT_ARGS" + + m = self.endl.join(('#!/usr/bin/env bash', + '#SBATCH --job-name={}'.format(batch_name), + '#SBATCH --partition=standard', + '#SBATCH --output=/dev/null', + '#SBATCH --chdir=/t3home/fbilandz/Run3', + '#SBATCH --time=00:44:59', + '#SBATCH --nodes=1', + '#SBATCH -o {}'.format(outfile), + '#SBATCH -e {}'.format(outfile.replace('.out', '.err')), + '#SBATCH --array={}'.format(','.join(filenum_array)) if len(filenum_array) else "", + '#SBATCH --cpus-per-task=1', + self.endl, + self.endl.join(arg_array) if len(arg_array) else "", + shell_exec + additional_args, + self.endl)) + + with open(filename, 'w') as self.f: + self.f.write(m) + os.system('chmod u+rwx '+ filename) + + def write_queue(self, qvars=(), qlines=[], machine="condor"): """ Works for any number variables in the queue. It is up to the user to guarantee compatibility between queue variables and lines. @@ -124,7 +178,10 @@ def write_queue(self, qvars=(), qlines=[]): extension = self.filenames[-1].split('.')[-1] if extension != self.exts[1]: self.extension_exception() - + + if machine == "slurm": + return + with open(self.filenames[-1], 'a') as self.f: if len(qvars) > 0: argstr = 'Arguments = "' @@ -164,13 +221,13 @@ def write_shell(self, filename, command, localdir, machine, eos_user='bfontana') os.system('chmod u+rwx '+ filename) def condor_specific_content(self, queue, machine): - if 'llr' in machine: + if 'llr' in machine or machine == 'slurm': assert queue in ('short', 'long') - assert machine in ('llrt3condor', 'llrt3condor7') + assert machine in ('llrt3condor', 'llrt3condor7', 'slurm') m = self.endl + self.endl.join(('T3Queue = {}'.format(queue), 'WNTag=el7', '+SingularityCmd = ""')) - if machine == 'llrt3condor': + if machine == 'llrt3condor' or machine == 'slurm': t3 = "t3" elif machine == 'llrt3condor7': t3 = "t3_tst" diff --git a/inclusion/condor/processing.py b/inclusion/condor/processing.py index d209706..edf9ee6 100755 --- a/inclusion/condor/processing.py +++ b/inclusion/condor/processing.py @@ -110,20 +110,34 @@ def processing(args): jw.add_string('echo "Process {} done in mode {}."'.format(vproc,args.mode)) #### Write submission file - jw.write_condor(filename=outs_submit[i], + if main.machine == "slurm": + input_args = [] + for listname in filelist: + input_args.append('{}'.format( listname.replace('\n','') )) + jw.write_batch(filename=outs_submit[i], real_exec=utils.build_script_path(script), shell_exec=outs_job[i], outfile=outs_check[i], logfile=outs_log[i], queue=main.queue, - machine=main.machine) + machine=main.machine, + input_args=input_args) + + else: + jw.write_condor(filename=outs_submit[i], + real_exec=utils.build_script_path(script), + shell_exec=outs_job[i], + outfile=outs_check[i], + logfile=outs_log[i], + queue=main.queue, + machine=main.machine) - qlines = [] - for listname in filelist: - qlines.append(' {}'.format( listname.replace('\n','') )) - - jw.write_queue( qvars=('filename',), - qlines=qlines ) + qlines = [] + for listname in filelist: + qlines.append(' {}'.format( listname.replace('\n','') )) + + jw.write_queue( qvars=('filename',), + qlines=qlines ) # -- Parse options if __name__ == '__main__': diff --git a/inclusion/condor/union_calculator.py b/inclusion/condor/union_calculator.py index a27bc99..5334df7 100644 --- a/inclusion/condor/union_calculator.py +++ b/inclusion/condor/union_calculator.py @@ -59,7 +59,16 @@ def union_calculator(args): jw.add_string('echo "Process {} done."'.format(proc)) #### Write submission file - jw.write_condor(filename=subs[i], + if main.machine == "slurm": + jw.write_batch(filename=subs[i], + real_exec=utils.build_script_path(script), + shell_exec=jobs[i], + outfile=checks[i], + logfile=logs[i], + queue=main.queue, + machine=main.machine) + else: + jw.write_condor(filename=subs[i], real_exec=utils.build_script_path(script), shell_exec=jobs[i], outfile=checks[i], @@ -67,10 +76,10 @@ def union_calculator(args): queue=main.queue, machine=main.machine) - qlines = [] - for listname in filelist: - for trig in args.closure_single_triggers: - qlines.append(' {},{}'.format( os.path.basename(listname).replace('\n',''), trig )) - - jw.write_queue( qvars=('filename', 'closure_single_trigger'), - qlines=qlines ) + qlines = [] + for listname in filelist: + for trig in args.closure_single_triggers: + qlines.append(' {},{}'.format( os.path.basename(listname).replace('\n',''), trig )) + + jw.write_queue( qvars=('filename', 'closure_single_trigger'), + qlines=qlines ) diff --git a/inclusion/config/main.py b/inclusion/config/main.py index f355254..f3a79c5 100644 --- a/inclusion/config/main.py +++ b/inclusion/config/main.py @@ -2,9 +2,11 @@ email = 'bruno.alves@cern.ch' queue = 'short' -machine = 'llrt3condor' #'lxplus' +machine = 'slurm' #'lxplus' or 'llrt3condor' -storage = {'2018': os.path.join('/data_CMS/cms/', os.environ['USER'], 'TriggerScaleFactors'), +storage = { + '2022': os.path.join('/t3home/', os.environ['USER'], 'TriggerScaleFactors'), + '2018': os.path.join('/data_CMS/cms/', os.environ['USER'], 'TriggerScaleFactors'), '2017': os.path.join('/data_CMS/cms/', os.environ['USER'], 'TriggerScaleFactors'), '2016': os.path.join('/data_CMS/cms/', os.environ['USER'], 'TriggerScaleFactors'), '2016APV': os.path.join('/data_CMS/cms/', os.environ['USER'], 'TriggerScaleFactors')} @@ -16,7 +18,10 @@ 'subm' : 'submission', 'outs' : 'outputs'} -base_folder = {'llrt3condor': +base_folder = { + 'slurm': + os.path.join(os.environ['HOME'], 'HHbbtautau/inclusionRun3'), + 'llrt3condor': os.path.join(os.environ['HOME'], 'CMSSW_14_1_0_pre0', 'src', folders['base']), 'llrt3condor7': os.path.join(os.environ['HOME'], 'CMSSW_14_1_0_pre0', 'src', folders['base']), @@ -48,15 +53,22 @@ 'emu' : {'pairType': ('==', 5),} } # variables considered for calculating and plotting efficiencies -var_eff = ('HT20', 'met_et', 'mht_et', 'metnomu_et', 'mhtnomu_et', - 'dau1_pt', 'dau2_pt', 'dau1_eta', 'dau2_eta') +var_eff = ( + # 'met_et', 'mht_et', 'metnomu_et', 'mhtnomu_et', + 'dau1_pt', 'dau2_pt', 'dau1_eta', + # 'dau2_eta' + ) # variables considered for plotting MC/data comparison distributions -var_dist = ('dau1_pt', 'HH_mass') +var_dist = ('dau1_pt', +# 'HH_mass' +) # joining the two lists above var_join = set(var_eff + var_dist) -var_unionweights = ('dau1_pt', 'dau2_pt', 'dau1_eta', 'dau2_eta') +var_unionweights = ('dau1_pt', 'dau2_pt', 'dau1_eta', +# 'dau2_eta' +) trig_linear = lambda x : {'mc': x, 'data': x} @@ -64,7 +76,23 @@ # It does NOT match the 'pass_triggerbit' leaf, which is a skimmed version of the above that might change more often # One way to ensure the scheme is still correct is by running the script as shown here: # https://github.com/bfonta/useful_scripts/commit/f5e4a0096bc74c89176579a336b0f52b74cb3ed2 -trig_map = {'2018': +filterbit_map = { + '2022': { + 'IsoMu24': {'mc': {13: (1, 3)}, 'data': {13: (1, 3)}}, + 'IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1': {'mc': {13: (2, ), 15: (3, 5, 9)}, 'data': {13: (2, ), 15: (3, 5, 9)}}, + "LooseDeepTauPFTauHPS180_L2NN_eta2p1": {'mc': {}, 'data': {}}, + "PFMETNoMu120_PFMHTNoMu120_IDTight": {'mc': {}, 'data': {}}, + } +} + +trig_map = { + '2022': { + 'IsoMu24': {'mc': 0, 'data': 0}, + 'IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1': {'mc': 2, 'data': 2}, + "LooseDeepTauPFTauHPS180_L2NN_eta2p1": {'mc': 11, 'data': 11}, + "PFMETNoMu120_PFMHTNoMu120_IDTight": {'mc': 12, 'data': 12}, + }, + '2018': {'IsoMu24': {'mc': 0, 'data': 0}, 'Ele32': {'mc': 2, 'data': 2}, 'METNoMu120': {'mc': 40, 'data': 40}, @@ -98,21 +126,25 @@ } trig_map['2016APV'] = trig_map['2016'] -lep_triggers = {'2018': +lep_triggers = { + '2022': { + 'IsoMu24', 'IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1' + }, + '2018': {'Ele32', 'EleIsoTauCustom', 'IsoMu24', 'IsoMuIsoTauCustom', 'IsoDoubleTauCustom'}, '2017': {'Ele32', 'EleIsoTau', 'IsoMu27', 'IsoMuIsoTau', 'IsoDoubleTau'}, '2016': {'Ele25', 'IsoMu24', 'IsoMuIsoTau', 'IsoDoubleTau'} } -for year in ('2016', '2017', '2018'): +for year in ('2016', '2017', '2018', '2022'): assert all(x in trig_map[year].keys() for x in lep_triggers[year]) -cuts_ignored = {'HT20': (), - 'met_et': ('metnomu_et',), - 'mht_et': ('mhtnomu_et',), - 'metnomu_et': ('met_et',), - 'mhtnomu_et': ('mht_et',), +cuts_ignored = {#'HT20': (), + # 'met_et': ('metnomu_et',), + # 'mht_et': ('mhtnomu_et',), + # 'metnomu_et': ('met_et',), + # 'mhtnomu_et': ('mht_et',), 'dau1_pt': (), 'dau2_pt': ()} @@ -123,7 +155,9 @@ 'mumu': {} } ### Data and MC samples -inputs = {'2018': +inputs = { + '2022': ('/t3home/fbilandz/HHbbtautau/Run3/MC/PreprocessOutputs'), + '2018': ('/data_CMS/cms/portales/HHresonant_SKIMS/SKIMS_UL18_OpenCADI_Data/', '/data_CMS/cms/alves/HHresonant_SKIMS/SKIMS_UL18_OpenCADI_MC/'), '2017': @@ -137,7 +171,14 @@ # names of the subfolders under 'inputs' above: # dictionary that maps specific general triggers to datasets -data = {"2018": +data = { + "2022": + {'MET' : ('MET',), + 'EG' : ('EGamma',), + 'Mu' : ('SingleMuon',), + 'Tau' : ('Tau',) + }, + "2018": {'MET' : ('MET',), 'EG' : ('EGamma',), 'Mu' : ('SingleMuon',), @@ -163,7 +204,13 @@ }, } -mc_processes = {"2018": +mc_processes = { + "2022": { + 'TT': ('TTToHadronic', 'TTToFullyLeptonic', + 'TTToSemiLeptonic',), + 'DY': ('DYJetsToLL_M-50') + }, + "2018": {'ggfRadions': (), 'ggfBulkGraviton': (), 'TT': ('TTToHadronic', 'TTTo2L2Nu', 'TTToSemiLeptonic',), 'DY': ('DYJetsToLL_M-50_TuneCP5_13TeV-amc', diff --git a/inclusion/run.py b/inclusion/run.py index 54ab7fc..aa8a990 100644 --- a/inclusion/run.py +++ b/inclusion/run.py @@ -3,6 +3,7 @@ _all_ = [ ] import os +import subprocess import sys parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, parent_dir) @@ -104,7 +105,7 @@ '--year', required=True, type=str, - choices=('2016', '2016APV', '2017', '2018'), + choices=('2016', '2016APV', '2017', '2018', '2022'), help='Data year: impact thresholds and selections.' ) parser.add_argument( @@ -639,6 +640,22 @@ def run(self): jobs, branch=self.branch ) dag_manager.write_all() + dag_manager.cleanup() + + @lutils.WorkflowDebugger(flag=FLAGS.debug_workflow) + def requires(self): + return [Processing(mode='histos'), + Processing(mode='counts'), + HaddHisto(dataset_name=data_name, samples=data_vals), + HaddHisto(dataset_name=mc_name, samples=mc_vals), + HaddCounts(dataset_name=data_name, samples=data_vals), + HaddCounts(dataset_name=mc_name, samples=mc_vals ), + EffAndSF(), + EffAndSFAggr(), + Discriminator(), + UnionCalculator(), + Closure(), + ] class SubmitDAG(lutils.ForceRun): """Submission class.""" @@ -658,20 +675,45 @@ def edit_condor_submission_file(self, out): @lutils.WorkflowDebugger(flag=FLAGS.debug_workflow) def run(self): - outfile = self.input()[-1][0].path - com = 'condor_submit_dag -no_submit -f' - com += ' -notification Always' - com += ' -append "notify_user={}"'.format(main.email) - bname = 'Inclusion_' + FLAGS.year + '_branch' + self.branch.capitalize() - com += ' -batch_name {}'.format(bname) - com += ' -outfile_dir {} {}'.format(os.path.dirname(outfile), outfile) - - os.system(com) - time.sleep(.5) - self.edit_condor_submission_file(outfile + '.condor.sub') - time.sleep(.5) - subm_com = '{}.condor.sub'.format(outfile) - os.system(subm_com) + if main.machine == "slurm": + outfile = self.input()[-1][0].path + job_registry = {} + dependencies_dict = {} + job_ids = {} + with open(outfile, "r", encoding="utf-8") as f: + for line in f: + if "JOB" in line: + job = line.split(" ") + job_registry[job[1]] = job[2].strip() + if "CHILD" in line: + job_names = line.split() + child_name = job_names[-1] + parent_names = job_names[1:job_names.index("CHILD")] + dependencies_dict[child_name] = parent_names + for job in job_registry: + if job not in dependencies_dict or len(dependencies_dict[job]) == 0: + status = subprocess.run(["sbatch", "--parsable", "-p", "short", job_registry[job]], capture_output=True) + job_ids[job] = status.stdout.decode().strip() + else: + dependency_ids = [job_ids[dependency_name] for dependency_name in dependencies_dict[job]] + status = subprocess.run(["sbatch", "--dependency=afterok:{}".format(":".join(dependency_ids)), "--parsable", "-p", "short", job_registry[job]], capture_output=True) + job_ids[job] = status.stdout.decode().strip() + + else: + outfile = self.input()[-1][0].path + com = 'condor_submit_dag -no_submit -f' + com += ' -notification Always' + com += ' -append "notify_user={}"'.format(main.email) + bname = 'Inclusion_' + FLAGS.year + '_branch' + self.branch.capitalize() + com += ' -batch_name {}'.format(bname) + com += ' -outfile_dir {} {}'.format(os.path.dirname(outfile), outfile) + + os.system(com) + time.sleep(.5) + self.edit_condor_submission_file(outfile + '.condor.sub') + time.sleep(.5) + subm_com = '{}.condor.sub'.format(outfile) + os.system(subm_com) @lutils.WorkflowDebugger(flag=FLAGS.debug_workflow) def output(self): @@ -681,19 +723,7 @@ def output(self): @lutils.WorkflowDebugger(flag=FLAGS.debug_workflow) def requires(self): - return [ Processing(mode='histos'), - Processing(mode='counts'), - HaddHisto(dataset_name=data_name, samples=data_vals), - HaddHisto(dataset_name=mc_name, samples=mc_vals), - HaddCounts(dataset_name=data_name, samples=data_vals), - HaddCounts(dataset_name=mc_name, samples=mc_vals ), - EffAndSF(), - EffAndSFAggr(), - Discriminator(), - UnionCalculator(), - Closure(), - Dag(branch=self.branch), - ] + return [Dag(branch=self.branch)] utils.create_single_dir( data_storage ) utils.create_single_dir( targets_folder ) From 9b3f6e9a4c2626643f8b3983141da9ab9c5dbbbc Mon Sep 17 00:00:00 2001 From: Filip Bilandzija Date: Thu, 14 Nov 2024 11:30:19 +0100 Subject: [PATCH 2/5] Updated gain procedure --- inclusion/config/main.py | 8 +- inclusion/selection.py | 114 +++- inclusion/utils/utils.py | 93 +++- tests/compare_ratios.py | 237 -------- tests/setup_regions.py | 218 ++++++++ tests/setup_triggerbits.py | 361 ++++++++++++ tests/test_compare_gains.py | 71 --- tests/test_draw_kin_regions.py | 478 ---------------- tests/test_theory.py | 43 -- tests/test_trigger_bits_number.py | 76 --- tests/test_trigger_contaminations.py | 161 ------ tests/test_trigger_gains.py | 287 ---------- tests/test_trigger_regions.py | 710 ------------------------ tests/test_trigger_stats.py | 796 --------------------------- tests/test_util.py | 10 - tests/trigger_efficiencies_run3.py | 456 +++++++++++++++ tests/trigger_gains_refined_run3.py | 118 ++++ 17 files changed, 1321 insertions(+), 2916 deletions(-) delete mode 100644 tests/compare_ratios.py create mode 100644 tests/setup_regions.py create mode 100644 tests/setup_triggerbits.py delete mode 100644 tests/test_compare_gains.py delete mode 100644 tests/test_draw_kin_regions.py delete mode 100644 tests/test_theory.py delete mode 100644 tests/test_trigger_bits_number.py delete mode 100644 tests/test_trigger_contaminations.py delete mode 100644 tests/test_trigger_gains.py delete mode 100644 tests/test_trigger_regions.py delete mode 100644 tests/test_trigger_stats.py delete mode 100644 tests/test_util.py create mode 100644 tests/trigger_efficiencies_run3.py create mode 100644 tests/trigger_gains_refined_run3.py diff --git a/inclusion/config/main.py b/inclusion/config/main.py index f3a79c5..2aa252c 100644 --- a/inclusion/config/main.py +++ b/inclusion/config/main.py @@ -56,6 +56,8 @@ var_eff = ( # 'met_et', 'mht_et', 'metnomu_et', 'mhtnomu_et', 'dau1_pt', 'dau2_pt', 'dau1_eta', + 'dau1_tauIdVSjet', 'dau2_tauIdVSjet', + 'bjet1_btagDeepFlavB', 'bjet2_btagDeepFlavB' # 'dau2_eta' ) @@ -88,7 +90,7 @@ trig_map = { '2022': { 'IsoMu24': {'mc': 0, 'data': 0}, - 'IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1': {'mc': 2, 'data': 2}, + 'IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1': {'mc': 1, 'data': 1}, "LooseDeepTauPFTauHPS180_L2NN_eta2p1": {'mc': 11, 'data': 11}, "PFMETNoMu120_PFMHTNoMu120_IDTight": {'mc': 12, 'data': 12}, }, @@ -175,7 +177,9 @@ "2022": {'MET' : ('MET',), 'EG' : ('EGamma',), - 'Mu' : ('SingleMuon',), + 'Mu' : ( + # 'SingleMuon', + 'Muon',), 'Tau' : ('Tau',) }, "2018": diff --git a/inclusion/selection.py b/inclusion/selection.py index a98737f..dcce219 100644 --- a/inclusion/selection.py +++ b/inclusion/selection.py @@ -14,12 +14,26 @@ import functools from collections import defaultdict import itertools as it +import numpy as np class EventSelection: def __init__(self, entries, isdata, year='2018', configuration=None, debug=False): self.entries = entries - self.bit = self.entries['triggerbit'] - self.run = self.entries['RunNumber'] + # self.bit = self.entries['triggerbit'] + self.filterbits = self.entries['TrigObj_filterBits'] + self.obj_id = self.entries['TrigObj_id'] + # self.HLT_IsoMu24 = self.entries['HLT_IsoMu24'] + # self.HLT_IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1 = self.entries['HLT_IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1'] + # self.HLT_LooseDeepTauPFTauHPS180_L2NN_eta2p1 = self.entries['HLT_LooseDeepTauPFTauHPS180_L2NN_eta2p1'] + # self.HLT_PFMETNoMu120_PFMHTNoMu120_IDTight = self.entries['HLT_PFMETNoMu120_PFMHTNoMu120_IDTight'] + # self.HLT_Ele30_WPTight_Gsf = self.entries['HLT_Ele30_WPTight_Gsf'] + # self.HLT_Ele24_eta2p1_WPTight_Gsf_LooseDeepTauPFTauHPS30_eta2p1_CrossL1 = self.entries['HLT_Ele24_eta2p1_WPTight_Gsf_LooseDeepTauPFTauHPS30_eta2p1_CrossL1'] + # self.HLT_DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1 = self.entries['HLT_DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1'] + # self.HLT_DoubleMediumDeepTauPFTauHPS30_L2NN_eta2p1_PFJet60 = self.entries['HLT_DoubleMediumDeepTauPFTauHPS30_L2NN_eta2p1_PFJet60'] + # self.HLT_QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65 = self.entries['HLT_QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65'] + self.entries['bjet1_btagDeepFlavB'] = self.entries['Jet_btagDeepFlavB'][self.entries['bjet1_JetIdx']] + self.entries['bjet2_btagDeepFlavB'] = self.entries['Jet_btagDeepFlavB'][self.entries['bjet2_JetIdx']] + self.run = self.entries['run'] self.isdata = isdata self.year = year self.debug = debug @@ -46,6 +60,26 @@ def check_bit(self, bitpos): bitdigit = 1 res = bool(self.bit&(bitdigit< deepJetWP[0] and - self.entries['bjet2_bID_deepFlavor'] > deepJetWP[0]) - btagM = ((self.entries['bjet1_bID_deepFlavor'] > deepJetWP[1] - and self.entries['bjet2_bID_deepFlavor'] < deepJetWP[1]) or - (self.entries['bjet1_bID_deepFlavor'] < deepJetWP[1] - and self.entries['bjet2_bID_deepFlavor'] > deepJetWP[1])) - btagMM = (self.entries['bjet1_bID_deepFlavor'] > deepJetWP[1] and - self.entries['bjet2_bID_deepFlavor'] > deepJetWP[1]) + '2018' : (0.0490, 0.2783), + '2022' : (0.0583, 0.3086)}[self.year] + btagLL = (self.entries['Jet_btagDeepFlavB'][self.entries['bjet1_JetIdx']] > deepJetWP[0] and + self.entries['Jet_btagDeepFlavB'][self.entries['bjet2_JetIdx']] > deepJetWP[0]) + btagM = ((self.entries['Jet_btagDeepFlavB'][self.entries['bjet1_JetIdx']] > deepJetWP[1] + and self.entries['Jet_btagDeepFlavB'][self.entries['bjet2_JetIdx']] < deepJetWP[1]) or + (self.entries['Jet_btagDeepFlavB'][self.entries['bjet1_JetIdx']] < deepJetWP[1] + and self.entries['Jet_btagDeepFlavB'][self.entries['bjet2_JetIdx']] > deepJetWP[1])) + btagMM = (self.entries['Jet_btagDeepFlavB'][self.entries['bjet1_JetIdx']] > deepJetWP[1] and + self.entries['Jet_btagDeepFlavB'][self.entries['bjet2_JetIdx']] > deepJetWP[1]) # common = not (self.entries['bjet1_bID_deepFlavor'] > deepJetWP[1] or # self.entries['bjet2_bID_deepFlavor'] > deepJetWP[1]) @@ -267,8 +326,8 @@ def selection_cuts(self, iso_cuts=dict(), lepton_veto=True, bjets_cut=True, dau1_eleiso = self.entries['dau1_eleMVAiso'] dau1_muiso = self.entries['dau1_iso'] dau2_muiso = self.entries['dau2_iso'] - dau1_tauiso = self.entries['dau1_deepTauVsJet'] - dau2_tauiso = self.entries['dau2_deepTauVsJet'] + dau1_tauiso = self.entries['dau1_tauIdVSjet'] + dau2_tauiso = self.entries['dau2_tauIdVSjet'] # third lepton veto nleps = self.entries['nleps'] @@ -276,9 +335,9 @@ def selection_cuts(self, iso_cuts=dict(), lepton_veto=True, bjets_cut=True, return False # require at least two b jet candidates - nbjetscand = self.entries['nbjetscand'] - if nbjetscand <= 1 and bjets_cut: - return False + # nbjetscand = self.entries['nbjetscand'] + # if nbjetscand <= 1 and bjets_cut: + # return False # Loose / Medium / Tight iso_allowed = { 'dau1_ele': 1., 'dau1_mu': 0.15, 'dau2_mu': 0.15, @@ -305,9 +364,9 @@ def selection_cuts(self, iso_cuts=dict(), lepton_veto=True, bjets_cut=True, dau2_muiso >= iso_cuts['dau2_mu']) if bool0 or bool1 or bool2 or bool3: return False - - tauH_mass = self.entries['tauH_mass'] - bH_mass = self.entries['bH_mass_raw'] + + tauH_mass = self.entries['Htt_mass' if self.isdata else 'Htt_mass_corr_Medium_corr'] + bH_mass = self.entries['Hbb_mass' if self.isdata else 'Hbb_mass_corr_Medium_corr'] mcut = bH_mass > 50 and bH_mass < 270 and tauH_mass > 20 and tauH_mass < 130 mcutinv = bH_mass < 50 or bH_mass > 270 or tauH_mass < 20 or tauH_mass > 130 opt = ('standard', 'inverted') @@ -377,11 +436,12 @@ def trigger_bits(self, trig): else: flag = False bits = self.get_trigger_bit(trig) + # filterbits = self.get_filter_bit(trig) if isinstance(bits, (tuple,list)): for bit in bits: flag = flag or self.check_bit(bit) else: - flag = self.check_bit(bits) + flag = self.check_bit(bits) # and self.check_filterbit(filterbits) return flag def var_cuts(self, trig, variables, nocut_dummy_str): diff --git a/inclusion/utils/utils.py b/inclusion/utils/utils.py index d7e4055..a5d99b4 100644 --- a/inclusion/utils/utils.py +++ b/inclusion/utils/utils.py @@ -8,6 +8,7 @@ import itertools as it import numpy as np import h5py +import json from types import SimpleNamespace import inclusion @@ -94,9 +95,10 @@ def check_inters_correctness(triggers, dchn, dgen, channel, exclusive): d = {'dataset1': (tuple1, tuple2,), 'dataset2': (tuple3, tuple4,), ...} """ chn_inters = generate_trigger_combinations(channel, triggers, exclusive) + print(chn_inters, dchn, dgen) flatten = [ tuple(sorted(w)) for x in dchn for w in dchn[x] ] flatten += [ tuple(sorted(w)) for x in dgen for w in dgen[x] ] - + print(flatten) # type check for x in flatten: if not isinstance(x, tuple): @@ -174,18 +176,56 @@ def define_used_tree_variables(cut): in the user-provided custom cut. Repeated variables are deleted. """ - _entries = ('triggerbit', 'RunNumber', 'HHKin_mass', 'isLeptrigger', 'pairType', 'isOS', - 'MC_weight', 'IdSF_deep_2d', 'PUReweight', 'L1pref_weight', 'trigSF', 'PUjetID_SF', 'bTagweightReshape', - 'dau1_eleMVAiso', 'dau1_iso', 'dau2_iso', 'dau1_deepTauVsJet', 'dau2_deepTauVsJet', - 'nleps', 'nbjetscand', 'tauH_mass', 'bH_mass_raw', - 'bjet1_bID_deepFlavor', 'bjet2_bID_deepFlavor', - 'isVBF', 'VBFjj_mass', 'VBFjj_deltaEta', - 'isTau1real', 'isTau2real') + _entries = ('TrigObj_filterBits', + # 'triggerbit', + 'TrigObj_id', 'run', + # 'HHKinFit_mass', + # 'isTauTauJetTrigger', + 'pairType', + 'isOS', + # 'genWeight', + # 'IdSF_deep_2d', + # 'puWeight', + # 'trigSF', + # 'bTagweightReshape', + 'genHH_mass', + 'MET_pt', + 'HLT_IsoMu24', 'HLT_IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1', + 'HLT_LooseDeepTauPFTauHPS180_L2NN_eta2p1', 'HLT_PFMETNoMu120_PFMHTNoMu120_IDTight', + 'HLT_Ele30_WPTight_Gsf', 'HLT_Ele24_eta2p1_WPTight_Gsf_LooseDeepTauPFTauHPS30_eta2p1_CrossL1', + 'HLT_DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1', 'HLT_DoubleMediumDeepTauPFTauHPS30_L2NN_eta2p1_PFJet60', + # "HLT_PFHT280_QuadPFJet30_PNet2BTagMean0p55", + 'dau1_eleMVAiso', 'dau1_iso', 'dau2_iso', 'dau1_eta', 'dau2_eta', 'dau1_tauIdVSjet', 'dau2_tauIdVSjet', + # 'dau1_mass', 'dau2_mass', 'Jet_mass', + 'Jet_btagPNetB', 'Jet_btagDeepFlavB', 'Jet_pt', 'Jet_eta', 'bjet1_JetIdx', 'bjet2_JetIdx', + # 'dau1_pt', 'dau2_pt', + # 'nleps', 'event', + 'isQuadJetTrigger', + # 'bjet1_filterbits', 'bjet2_filterbits', 'tau1_filterbits', 'tau2_filterbits' + # 'nbjetscand', 'SoftActivityJetHT', + # 'Htt_mass', 'Hbb_mass', + # 'bjet1_btagDeepFlavB', 'bjet2_btagDeepFlavB', + # 'isVBFtrigger', + # 'VBFjj_mass', 'VBFjj_deltaEta', + #'isTau1real', 'isTau2real', + ) + mc_corrections = { + # 'dau1_pt': '_corr_Medium', + # 'dau2_pt': '_corr_Medium', + # 'HHKinFit_mass': '_corr_Medium_corr', + # 'Htt_mass': '_corr_Medium_corr', + # 'Hbb_mass': '_corr_Medium_corr', + # 'VBFjj_mass': '_corr_Medium_corr', + # 'VBFjj_deltaEta': '_corr_Medium_corr', + # 'HH_svfit_mass': '_corr_Medium_corr' + # '_corr_Medium': ('dau1_pt', 'dau2_pt') + } + if cut is not None: _regex = tuple(set(re.findall(r'self\.entries\.(.+?)\s', cut))) else: _regex = () - return tuple(set(_entries + _regex)) + return tuple(set(_entries + _regex)), mc_corrections class dot_dict(dict): """dot.notation access to dictionary attributes""" @@ -347,7 +387,7 @@ def get_root_inputs(proc, indir, include_tree=False): mes = '[' + os.path.basename(__file__) + '] ' mes += ' The input file does not exist: {}'.format(line) raise ValueError(mes) - filelist.append(line + ':HTauTauTree') + filelist.append(line + ':Events') else: if line[:-1] not in main.corrupted_files: #filelist.append("root://eosuser.cern.ch/" + line) @@ -391,7 +431,7 @@ def is_trigger_comb_in_channel(chn, tcomb, triggers, exclusive): return split in possible_trigs def is_nan(num): - return num!= num + return num!= num or num is None def join_name_trigger_intersection(tuple_element): inters = main.inters_str @@ -409,16 +449,21 @@ def load_binning(afile, key, variables, channels): """ binedges, nbins = ({} for _ in range(2)) with h5py.File(afile, 'r') as f: + # print(f.keys(), list(f.keys()), key) + # x = {} + # print(dict(f.items())) try: - group = f[key] + group = dict(f.items())[key] except KeyError: - missing_key_print(afile, key) + missing_key_print(afile, key, f) for var in variables: + print(var) try: - subgroup = group[var] + print(dict(group.items())) + subgroup = dict(group.items())[var] except KeyError: - missing_key_print(afile, key) + missing_key_print(afile, key, f) binedges[var], nbins[var] = ({} for _ in range(2)) for chn in channels: @@ -427,7 +472,7 @@ def load_binning(afile, key, variables, channels): return binedges, nbins -def missing_key_print(afile, key): +def missing_key_print(afile, key, f): print('{} does not have key {}.'.format(afile, key)) print('Available keys: {}'.format(f.keys())) raise @@ -512,6 +557,8 @@ def get_lumi(year): return 16800 elif year == "2016APV": return 19500 + elif year == "2022": + return 38010 else: raise ValueError("Year {} not supported.".format(year)) @@ -525,6 +572,8 @@ def get_ptcuts(channel, year): ptcuts = (26,) elif year == "2017" or year == "2018": ptcuts = (33, 25, 35) + elif year == "2022": + ptcuts = (33, 25, 35) elif channel == "mutau": if "2016" in year: @@ -533,6 +582,8 @@ def get_ptcuts(channel, year): ptcuts = (28, 21, 32) elif year == "2018": ptcuts = (25, 21, 32) + elif year == "2022": + ptcuts = (25, 21, 32) elif channel == "tautau": ptcuts = (40, 40) @@ -598,8 +649,14 @@ def total_sum_weights(f, isdata): xsec_norm = 0. with open(search_str, 'r') as afile: for elem in afile: - ftmp = ROOT.TFile(elem.replace('\n', ''), "READ") - xsec_norm += ftmp.Get('h_eff').GetBinContent(1) + if '.json' in elem: + ftmp = json.load(open(elem.replace('\n', ''))) + xsec_norm += float(ftmp['nweightedevents']) + else: + ftmp = ROOT.TFile(elem.replace('\n', ''), "READ") + # Find a way to connect PreCounter with this + xsec_norm += ftmp.Get('h_eff').GetBinContent(1) + # xsec_norm += 1. return xsec_norm return None diff --git a/tests/compare_ratios.py b/tests/compare_ratios.py deleted file mode 100644 index 35b814c..0000000 --- a/tests/compare_ratios.py +++ /dev/null @@ -1,237 +0,0 @@ -# Coding: utf-8 - -_all_ = [ 'compare_ratios' ] - -import os -import numpy as np -import glob -import argparse -import uproot as up - -import matplotlib -import matplotlib.pyplot as plt -import mplhep as hep -plt.style.use(hep.style.ROOT) - -mu, tau = '\u03BC','\u03C4' -dd = {"mumu": mu+mu, "mutau": mu+tau} - -def build_path(base, channel, variable): - path = os.path.join(base, channel, variable) - return os.path.join(path, "eff_Data_Mu_MC_TT_DY_WJets_" + channel + "_" + variable + "_TRG_METNoMu120_CUTS_*.root") - -def get_paths_and_labels(base, mode, channels, variable, year, var_units): - if mode == "ranges": - labels = ["full", r"$[180;\infty[\:\:{}$".format(var_units), - r"$[160;\infty[\:\:{}$".format(var_units), r"$[150;\infty[\:\:{}$".format(var_units)] - if year == "2018": - labels.append(r"$[140;\infty[\:\:{}$".format(var_units)) - # transfer files with: - # cp /data_CMS/cms/alves/TriggerScaleFactors/OpenCADI_18/Outputs/mutau/metnomu_et/eff_Data_Mu_MC_TT_DY_WJets_mutau_metnomu_et_TRG_METNoMu120_CUTS_mhtnomu_et_L_0p0_default.root full_mumu_fit.root - paths = ["full_mumu_fit_"+year+".root", "180_mumu_fit_"+year+".root", - "160_mumu_fit_"+year+".root", "150_mumu_fit_"+year+".root"] - if year == "2018": - paths.append("140_mumu_fit_"+year+".root") - elif mode == "channels": - labels = (dd["mutau"], dd["mumu"]) - paths = (build_path(base, channels[0], variable), - build_path(base, channels[1], variable),) - elif mode == "datasets": - labels = (dd["mumu"] + ", SingleMuon", dd["mumu"] + ", DoubleMuon", - dd["mutau"] + ", SingleMuon", dd["mutau"] + ", DoubleMuon") - paths = ("full_mumu_fit.root", "full_double_mumu_fit.root", - "full_mutau_fit.root", "full_double_mutau_fit.root") - elif mode == "years": - labels = ("UL17", "UL18") - paths = ("160_mumu_fit_2017.root", "150_mumu_fit_2018.root") - - ret = {} - for p,l in zip(paths,labels): - tmp = glob.glob(p) - if len(tmp) != 1: - print(tmp) - raise RuntimeError('[ERROR] Path {} must have lenght 1.'.format(tmp)) - ret[tmp[0]] = l - - return ret - -def sigmoid(x, params): - """ - Sigmoid function to mimick the TF1 object. - Uproot does not yet support TF1 reading. - """ - return params[2] / (1 + np.exp(-params[0] * (x - params[1]))) - -def compare_ratios(paths, mode, variable, year, var_units): - """ - Compare ratios in two modes. - - Mode 'ranges': compare change in fit from changing the fit range - - Mode 'channels': compare changes from fitting different channels - """ - colors = ("blue", "green", "red", "purple", "darkorange") - var_map = dict(metnomu_et=r"MET-no$\mu$") - - fit_ratios, idx_lims = [], [] - - fig, (ax1, ax2) = plt.subplots(2, sharex=True, gridspec_kw={'height_ratios': [3., 1.]}) - plt.subplots_adjust(wspace=0, hspace=0) - - for ipath, (bpath, blabel) in enumerate(paths.items()): - graph_sf = up.open(bpath + ":SF1D") - - fit_data = up.open(bpath + ":SigmoidFuncData") - fit_mc = up.open(bpath + ":SigmoidFuncMC") - - fit_data_pars = fit_data.member('fFormula').member('fClingParameters')[:] - fit_mc_pars = fit_mc.member('fFormula').member('fClingParameters')[:] - fit_xrange = (fit_data.member('fXmin'), fit_data.member('fXmax')) - assert fit_xrange == (fit_mc.member('fXmin'), fit_mc.member('fXmax')) - - fit_xvals = np.linspace(0, 350, num=5000) - - fit_data_yvals = sigmoid(fit_xvals, fit_data_pars) - fit_mc_yvals = sigmoid(fit_xvals, fit_mc_pars) - - # get fit validity range, otherwise the full function is plotted - idx_lims.append( (np.argmax(fit_xvals > fit_xrange[0]), - np.argmax(fit_xvals > fit_xrange[1])) ) - idx_sel = slice(idx_lims[-1][0],idx_lims[-1][1],1) - - # if mode == "ranges" we only want to plot SFs once (all are equal) - if mode != "ranges" or ipath > 0: - # plot efficiency values and error bars - ax1.errorbar(graph_sf.values(axis="x"), graph_sf.values(axis="y"), - xerr=(graph_sf.errors(axis="x", which="low"), graph_sf.errors(axis="x", which="high")), - yerr=(graph_sf.errors(axis="y", which="low"), graph_sf.errors(axis="y", which="high")), - fmt='o', color="black" if mode == "ranges" else colors[ipath]) - - fit_ratios.append(fit_data_yvals / fit_mc_yvals) - - # plot SF fit (Data/MC) - ax1.plot(fit_xvals[idx_sel], fit_ratios[-1][idx_sel], '--', color=colors[ipath], label=blabel) - - ax1.set_ylabel("Data / MC", fontsize=20) - ax1.legend(loc="lower right") - - line_opt = dict(color="grey", linestyle="--") - met_cuts = [180., 160., 150.] - if year=="2018": - met_cuts.append(140.) - if mode == "ranges": - ax1.set_ylim(-0.05, 1.08) - yticks = np.arange(-.06, .06, .02) - ax2.set_yticks(yticks) - for yval in yticks: - ax2.axhline(y=yval, **line_opt) - ax2.set_ylim(yticks[0]+1E-5, yticks[-1]-1E-5) - ax1.axhline(y=1., **line_opt) - for cut in met_cuts: - ax1.axvline(x=cut, **line_opt) - ax2.axvline(x=cut, **line_opt) - - elif mode == "channels": - ax1.set_ylim(-0.05, 1.08) - ax1.axvline(x=150., **line_opt) - ax2.axvline(x=150., **line_opt) - yticks = np.arange(-.3, .3, .1) - ax2.set_yticks(yticks) - ax1.axhline(y=1., **line_opt) - for yval in yticks: - ax2.axhline(y=yval, **line_opt) - ax2.set_ylim(yticks[0]+1E-5, yticks[-1]-1E-5) - - elif mode == "datasets": - ax1.set_ylim(-0.05, 1.08) - ax1.axvline(x=150., **line_opt) - ax2.axvline(x=150., **line_opt) - yticks = np.arange(-.7, .7, .3) - ax2.set_yticks(yticks) - ax1.axhline(y=1., **line_opt) - for yval in yticks: - ax2.axhline(y=yval, **line_opt) - ax2.set_ylim(yticks[0]+1E-5, yticks[-1]-1E-5) - - elif mode == "years": - ax1.set_ylim(-0.05, 1.08) - ax1.axvline(x=150., **line_opt) - ax2.axvline(x=150., **line_opt) - yticks = (-.1, 0., .1) - ax2.set_yticks(yticks) - ax1.axhline(y=1., **line_opt) - for yval in yticks: - ax2.axhline(y=yval, **line_opt) - ax2.set_ylim(-.19, .19) - - # comparison of ratios using the first partial fit as reference - # the x range is the minimum interval common to both ratios - if mode == "ranges": - for ipath, (bpath,_) in enumerate(paths.items()): - if ipath == 0: - continue - tmp_sel = slice(max(idx_lims[1][0], idx_lims[ipath][0]), - min(idx_lims[1][1], idx_lims[ipath][1]), 1) - ax2.plot(fit_xvals[tmp_sel], (fit_ratios[3][tmp_sel]/fit_ratios[ipath][tmp_sel])-1., '--', color=colors[ipath]) - elif mode == "channels": - ax2.plot(fit_xvals, (fit_ratios[1]/fit_ratios[0])-1., '--', color=colors[ipath]) - elif mode == "datasets": - ax2.plot(fit_xvals, (fit_ratios[1]/fit_ratios[0])-1., '--', color="blue", - label=dd["mumu"] + ": Double/Single") - ax2.plot(fit_xvals, (fit_ratios[3]/fit_ratios[2])-1., '--', color="orange", - label=dd["mutau"] + ": Double/Single") - elif mode == "years": - tmp_sel = slice(max(idx_lims[1][0], idx_lims[0][0]), - min(idx_lims[1][1], idx_lims[0][1]), 1) - ax2.plot(fit_xvals[tmp_sel], (fit_ratios[1][tmp_sel]/fit_ratios[0][tmp_sel])-1., '--', color=colors[ipath]) - - if mode == "ranges": - ax2.set_ylabel(r"$(SF_{{150\:{u}}}/SF_{{X\:{u}}}) - 1$".format(u=var_units), fontsize=20) - elif mode == "channels": - ax2.set_ylabel(r"Fit Ratio", fontsize=20) - elif mode == "datasets": - ax2.set_ylabel(r"Fit Ratio", fontsize=20) - elif mode == "years": - ax2.set_ylabel(r"UL18/UL17 - 1", fontsize=20) - ax2.set_xlabel(var_map[variable] + " [" + var_units + "]", fontsize=21) - - if mode == "datasets": - ax2.legend(loc="lower right", fontsize=15) - - hep_opt = dict(ax=ax1) - lumi = {"2016APV": "19.5", "2016": "16.8", "2017": "41.5", "2018": "59.7"}[year] - hep.cms.text(' Preliminary', fontsize=22, **hep_opt) - if mode == "ranges": - hep.cms.lumitext(dd["mumu"] + " (baseline, {}) | ".format(year) + r"{} $fb^{{-1}}$ (13 TeV)".format(lumi), - fontsize=19, **hep_opt) - elif mode == "years": - hep.cms.lumitext(r"41.7 $fb^{-1}$ [UL17], 59.7 $fb^{-1}$ [UL18] (13 TeV)", fontsize=19, **hep_opt) - else: - hep.cms.lumitext(r"59.7 $fb^{-1}$ (13 TeV)", fontsize=19, **hep_opt) - - baseout = "/eos/home-b/bfontana/www/TriggerScaleFactors/CompareRatios/" - if mode == "years": - output = os.path.join(baseout, os.path.basename(__file__[:-3] + '_' + mode)) - else: - output = os.path.join(baseout, os.path.basename(__file__[:-3] + '_' + mode + '_' + year)) - - for ext in ('.png', '.pdf'): - fig.savefig(output + ext) - print('Plot saved under {}'.format(output + ext)) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Compare efficiency ratios obtained with two different methods.') - parser.add_argument('--tag', required=False, default="", - help='Tag used to produce the graphs. Same used by the inclusion/run.py command.') - parser.add_argument('--mode', default="channel", choices=("ranges", "channels", "datasets", "years"), - help='Which comparison to run.') - parser.add_argument('--year', default="2018", choices=("2016APV", "2016", "2017", "2018"), - help='Which data period to consider.') - - FLAGS = parser.parse_args() - if FLAGS.mode == "channels": - assert FLAGS.tag != "" - - base = os.path.join("/data_CMS/cms/alves/TriggerScaleFactors/", FLAGS.tag, "Outputs") - paths = get_paths_and_labels(base, FLAGS.mode, channels=("mutau", "mumu"), - variable="metnomu_et", year=FLAGS.year, var_units="GeV") - compare_ratios(paths, mode=FLAGS.mode, variable="metnomu_et", year=FLAGS.year, var_units="GeV") diff --git a/tests/setup_regions.py b/tests/setup_regions.py new file mode 100644 index 0000000..cf297b4 --- /dev/null +++ b/tests/setup_regions.py @@ -0,0 +1,218 @@ +# coding: utf-8 + +_all_ = [ 'test_trigger_regions' ] + +import os +import sys +parent_dir = os.path.abspath(__file__ + 2 * '/..') +sys.path.insert(0, parent_dir) +import argparse +import glob +import multiprocessing +import itertools as it +import csv +import numpy as np +import h5py +from collections import defaultdict as dd +import importlib +from array import array + +import inclusion +from inclusion import selection +from inclusion.config import main +from inclusion.utils import utils + +import ROOT +import hist +import pickle +import uproot + +from bokeh.plotting import figure, output_file, save +from bokeh.models import Range1d, Label + +tau = '\u03C4' +mu = '\u03BC' +pm = '\u00B1' +ditau = tau+tau + +def get_outname(channel, bigtau): + utils.create_single_dir('data') + + name = "" + name += '.root' + + s = 'data/regions_{}'.format(name) + return s + + +def trigger_regions(indir, channel, year, outname): + outname = get_outname(outname) + config_module = importlib.import_module(args.configuration) + + if channel == 'etau' or channel == 'mutau': + iso1 = range(0, 8.1, 8/24) + elif channel == 'tautau': + iso1 = range(0, 401, 8) + pNet_dist = [x/12. for x in range(13)] + binning.update({ + 'genHH_mass': ([250, 300, 350, 400, 450, 500, 550, 600, 675, 800, 1000, 1600],), + 'dau1_iso': (iso1,), + 'dau1_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'dau2_iso': (iso1,), + 'dau2_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'dau1_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + 'dau2_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + 'dau1_tauIdVSjet': ([0, 1, 2, 3, 4, 5, 6, 7], ), + 'dau2_tauIdVSjet': ([0, 1, 2, 3, 4, 5, 6, 7], ), + 'bjet1_pNet': (pNet_dist,), + 'bjet2_pNet': (pNet_dist,), + 'bjet1_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'bjet2_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'bjet1_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + 'bjet2_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + # 'triggerbits': (range(31)) + }) + + norphans, ntotal = ({k:0 for k in categories} for _ in range(2)) + + ahistos = rec_dd() + for chn in ['mutau', 'etau', 'tautau']: + for i in binning.keys(): + ahistos[chn][i] = ( + hist.Hist.new.Variable(*binning[i], name=i) + .Weight() + ) + + t_in = ROOT.TChain('Events') + glob_files = glob.glob( os.path.join(indir, 'data_*.root') ) + if len(glob_files) < 1: + raise RuntimeError("No files!") + for f in glob_files: + t_in.Add(f) + t_in.SetBranchStatus('*', 0) + _entries, mc_corrections = utils.define_used_tree_variables(cut=config_module.custom_cut) + + _entries += tuple([ x + mc_corrections[x] for x in mc_corrections]) + for ientry in _entries: + t_in.SetBranchStatus(ientry, 1) + + for entry in t_in: + # this is slow: do it once only + entries = utils.dot_dict({x: getattr(entry, x) for x in _entries}) + + if entries.pairType == -1: + continue + + if (entries.pairType == 0 or entries.pairType == 1) and (entries.isOS != 1 or entries.dau2_tauIdVSjet < 5): + continue + + if entries.pairType == 2 and (entries.isOS != 1 or entries.dau2_tauIdVSjet < 5 or entries.dau1_tauIdVSjet < 5): + continue + + if entries.pairType > 2: + continue + + sel = selection.EventSelection(entries, year=year, isdata=False, configuration=config_module) + # in_legacy, in_met, in_tau = which_region(entries, year, ptcuts, regcuts, channel, sel, + # bigtau=args.bigtau) + + w_mc = entries.genWeight + # w_pure = entries.puWweight + # # w_l1pref = entries.L1pref_weight + # w_trig = 1 # entries.trigSF + # w_idiso = entries.IdSF_deep_2d + # w_jetpu = entries.PUjetID_SF + # w_btag = entries.bTagweightReshape + + if utils.is_nan(w_mc) : w_mc=1 + # if utils.is_nan(w_pure) : w_pure=1 + # # if utils.is_nan(w_l1pref) : w_l1pref=1 + # if utils.is_nan(w_trig) : w_trig=1 + # if utils.is_nan(w_idiso) : w_idiso=1 + # if utils.is_nan(w_jetpu) : w_jetpu=1 + # if utils.is_nan(w_btag) : w_btag=1 + evt_weight = 1 # * w_btag + # if evt_weight < 0.: + # print(w_mc, w_pure, w_trig, w_idiso, w_jetpu) + tau_gen_cut = {"etau": None, "mutau": None, + "tautau": 'self.entries["isTau1real"] == 1 and self.entries["isTau2real"] == 1'} + + if entries.pairType == 0: + chn = 'mutau' + elif entries.pairType == 1: + chn = 'etau' + elif entries.pairType == 2: + chn = 'tautau' + + for i in binning.keys(): + z = 0 + if 'bjet1' in i: + if 'eta' in i: + z = entries['Jet_eta'][entries['bjet1_JetIdx']] + if 'pt' in i: + z = entries['Jet_pt'][entries['bjet1_JetIdx']] + if 'pNet' in i: + z = entries['Jet_btagPNetB'][entries['bjet1_JetIdx']] + elif 'bjet2' in i: + if 'eta' in i: + z = entries['Jet_eta'][entries['bjet2_JetIdx']] + if 'pt' in i: + z = entries['Jet_pt'][entries['bjet2_JetIdx']] + if 'pNet' in i: + z = entries['Jet_btagPNetB'][entries['bjet2_JetIdx']] + else: + z = entries[i] + + ahistos[chn][i].fill(z, weight=evt_weight) + + + # all MC and signal must be rescaled to get the correct number of events + # for key,_ in cuts.items(): + # for reg in regions: + # for cat in categories: + # for cc in ['mutau', 'etau', 'tautau']: + # print("lumi", utils.get_lumi(args.year), utils.total_sum_weights(glob_files[0].replace("PreprocessRDF", "PreCounter").replace("/cat_base_selection", "").replace(".root", ".json"), isdata=False)) + # ahistos[key][reg][cat][cc] *= (utils.get_lumi(args.year) / + # utils.total_sum_weights(glob_files[0].replace("PreprocessRDF", "PreCounter").replace("/cat_base_selection", "").replace(".root", ".json"), isdata=False)) + + file = uproot.recreate(outname) + print('Raw histograms saved in {}.'.format(outname), flush=True) + +if __name__ == '__main__': + extensions = ('png',) #('png', 'pdf') + triggers = {'etau': ("HLT_Ele30_WPTight_Gsf", + "HLT_Ele24_eta2p1_WPTight_Gsf_LooseDeepTauPFTauHPS30_eta2p1_CrossL1"), + 'mutau': ('HLT_IsoMu24', 'HLT_IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1'), + 'tautau': ('HLT_DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1',) + } + + categories = ('baseline',) #('baseline', 's1b1jresolvedMcut', 's2b0jresolvedMcut', 'sboostedLLMcut') + + # Parse input arguments + desc = 'Producer trigger histograms.\n' + desc += "Run example: python tests/test_trigger_regions.py --indir /data_CMS/cms/alves/HHresonant_SKIMS/SKIMS_UL18_EOSv4_Signal/ --masses 400 500 600 700 800 900 1000 1250 1500 --channels ETau --met_turnon 180 --region_cuts 40 40 --copy" + parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument('--indir', required=True, type=str, + help='Full path of ROOT input file') + # parser.add_argument('--masses', required=True, nargs='+', type=str, + # help='Resonance mass') + parser.add_argument('--channel', required=True, type=str, + help='Select the channel over which the workflow will be run.' ) + parser.add_argument('--year', required=True, type=str, choices=('2016', '2016APV', '2017', '2018', '2022'), + help='Select the year over which the workflow will be run.' ) + # parser.add_argument('--spin', required=True, type=int, choices=(0, 2), + # help='Select the spin hypothesis over which the workflow will be run.' ) + parser.add_argument('--configuration', dest='configuration', required=True, + help='Name of the configuration module to use.') + parser.add_argument('--outname', dest='outname', required=True, + help='Output file name.') + args = utils.parse_args(parser) + + #### run main function ### + if not args.plot: + # for sample in args.masses: + trigger_regions(args.indir, args.channel, args.year, args.outname) + + ########################### + diff --git a/tests/setup_triggerbits.py b/tests/setup_triggerbits.py new file mode 100644 index 0000000..5839001 --- /dev/null +++ b/tests/setup_triggerbits.py @@ -0,0 +1,361 @@ +# coding: utf-8 + +_all_ = [ 'test_trigger_regions' ] + +import os +import sys +parent_dir = os.path.abspath(__file__ + 2 * '/..') +sys.path.insert(0, parent_dir) +import argparse +import glob +import multiprocessing +import itertools as it +import csv +import numpy as np +import h5py +from collections import defaultdict as dd +import importlib +from array import array + +import inclusion +from inclusion import selection +from inclusion.config import main +from inclusion.utils import utils + +import ROOT +import hist +import pickle +import uproot + +from bokeh.plotting import figure, output_file, save +from bokeh.models import Range1d, Label + +tau = '\u03C4' +mu = '\u03BC' +pm = '\u00B1' +ditau = tau+tau + +def rec_dd(): + return dd(rec_dd) + +def get_outname(channel, bigtau): + utils.create_single_dir('data') + + name = "" + if bigtau: + name += '_BIGTAU' + name += '_all.root' + + s = 'data/regions_2023_postBPix_15p36-ditaujet{}'.format(name) + return s + + +def trigger_regions(indir, channel, year, deltaR): + outname = get_outname(channel, + args.bigtau) + config_module = importlib.import_module(args.configuration) + + if channel == 'etau' or channel == 'mutau': + iso1 = range(0, 8.1, 8/24) + elif channel == 'tautau': + iso1 = range(0, 401, 8) + pNet_dist = [x/12. for x in range(13)] + binning.update({ + 'genHH_mass': ([250, 300, 350, 400, 450, 500, 550, 600, 675, 800, 1000, 1600],), + 'dau1_iso': (iso1,), + # 'dau1_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'dau2_iso': (iso1,), + # 'dau2_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'dau1_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + 'dau2_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + 'dau1_tauIdVSjet': ([0, 1, 2, 3, 4, 5, 6, 7], ), + 'dau2_tauIdVSjet': ([0, 1, 2, 3, 4, 5, 6, 7], ), + 'bjet1_pNet': (pNet_dist,), + 'bjet2_pNet': (pNet_dist,), + 'bjet1_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'bjet2_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'bjet1_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + 'bjet2_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + # 'bjet1_triggerbits': ([i for i in range(32)],), + # 'bjet2_triggerbits': ([i for i in range(32)],), + # 'tau1_triggerbits': ([i for i in range(32)],), + # 'tau2_triggerbits': ([i for i in range(32)],), + # 'SoftActivityJetHT': ([0, 50, 100, 150, 200, 250, 300, 350, 400, 500, 750, 1000, 1500, 2000],) + }) + + + norphans, ntotal = ({k:0 for k in categories} for _ in range(2)) + + ahistos = rec_dd() + for chn in ['mutau', 'etau', 'tautau']: + for i in binning.keys(): + ahistos[chn][i] = ( + hist.Hist.new.Variable(*binning[i], name=i) + .Weight() + ) + + t_in = ROOT.TChain('Events') + glob_files = glob.glob( os.path.join(indir, 'data_*.root') ) + if len(glob_files) < 1: + raise RuntimeError("No files!") + for f in glob_files: + t_in.Add(f) + t_in.SetBranchStatus('*', 0) + _entries, mc_corrections = utils.define_used_tree_variables(cut=config_module.custom_cut) + + _entries += tuple([ x + mc_corrections[x] for x in mc_corrections]) + for ientry in _entries: + t_in.SetBranchStatus(ientry, 1) + + for entry in t_in: + # this is slow: do it once only + entries = utils.dot_dict({x: getattr(entry, x) for x in _entries}) + + if entries.pairType == -1: + continue + + if (entries.pairType == 0 or entries.pairType == 1) and (entries.isOS != 1 or entries.dau2_tauIdVSjet < 5): + continue + + if entries.pairType == 2 and (entries.isOS != 1 or entries.dau2_tauIdVSjet < 5 or entries.dau1_tauIdVSjet < 5): + continue + + if entries.pairType > 2: + continue + + + sel = selection.EventSelection(entries, year=year, isdata=False, configuration=config_module) + # in_legacy, in_met, in_tau = which_region(entries, year, ptcuts, regcuts, channel, sel, + # bigtau=args.bigtau) + + w_mc = entries.genWeight + # w_pure = entries.puWweight + # # w_l1pref = entries.L1pref_weight + # w_trig = 1 # entries.trigSF + # w_idiso = entries.IdSF_deep_2d + # w_jetpu = entries.PUjetID_SF + # w_btag = entries.bTagweightReshape + + if utils.is_nan(w_mc) : w_mc=1 + # if utils.is_nan(w_pure) : w_pure=1 + # # if utils.is_nan(w_l1pref) : w_l1pref=1 + # if utils.is_nan(w_trig) : w_trig=1 + # if utils.is_nan(w_idiso) : w_idiso=1 + # if utils.is_nan(w_jetpu) : w_jetpu=1 + # if utils.is_nan(w_btag) : w_btag=1 + evt_weight = 1 # * w_btag + # if evt_weight < 0.: + # print(w_mc, w_pure, w_trig, w_idiso, w_jetpu) + tau_gen_cut = {"etau": None, "mutau": None, + "tautau": 'self.entries["isTau1real"] == 1 and self.entries["isTau2real"] == 1'} + + if entries.pairType == 0: + chn = 'mutau' + elif entries.pairType == 1: + chn = 'etau' + elif entries.pairType == 2: + chn = 'tautau' + + + + for i in binning.keys(): + z = 0 + if 'bjet1' in i: + if 'eta' in i: + z = entries['Jet_eta'][entries['bjet1_JetIdx']] + if 'pt' in i: + z = entries['Jet_pt'][entries['bjet1_JetIdx']] + if 'pNet' in i: + z = entries['Jet_btagPNetB'][entries['bjet1_JetIdx']] + elif 'bjet2' in i: + if 'eta' in i: + z = entries['Jet_eta'][entries['bjet2_JetIdx']] + if 'pt' in i: + z = entries['Jet_pt'][entries['bjet2_JetIdx']] + if 'pNet' in i: + z = entries['Jet_btagPNetB'][entries['bjet2_JetIdx']] + else: + if 'triggerbits' in i: + continue + z = entries[i] + + ahistos[chn][i].fill(z, weight=evt_weight) + + # if chn == 'tautau' and entries['isQuadJetTrigger'] and entries['bjet1_filterbits'] >= 0: + # for i in range(31): + # print(i, entries["bjet1_filterbits"], bool(entries["bjet1_filterbits"] & (1 << i))) + # if bool(entries["bjet1_filterbits"] & (1 << i)): + # ahistos[chn]['bjet1_triggerbits'].fill(i) + # if bool(entries["bjet2_filterbits"] & (1 << i)): + # ahistos[chn]['bjet2_triggerbits'].fill(i) + # if bool(entries["tau1_filterbits"] & (1 << i)): + # ahistos[chn]['tau1_triggerbits'].fill(i) + # if bool(entries["tau2_filterbits"] & (1 << i)): + # ahistos[chn]['tau2_triggerbits'].fill(i) + + + file = uproot.recreate(outname) + for chn in ['mutau', 'etau', 'tautau']: + for i in binning.keys(): + file[str(chn) + "_" + str(i)] = ahistos[chn][i] + + print('Raw histograms saved in {}.'.format(outname), flush=True) + +if __name__ == '__main__': + extensions = ('png',) #('png', 'pdf') + triggers = {'etau': ("HLT_Ele30_WPTight_Gsf", + "HLT_Ele24_eta2p1_WPTight_Gsf_LooseDeepTauPFTauHPS30_eta2p1_CrossL1"), + 'mutau': ('HLT_IsoMu24', 'HLT_IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1'), + 'tautau': ('HLT_DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1',) + } + binning = { + # 'metnomu_et': (20, 0, 450), + # 'dau1_pt': (30, 0, 450), + # 'dau1_eta': (20, -2.5, 2.5), + # 'dau2_iso': (20, 0.88, 1.005), + # 'dau2_pt': (30, 0, 400), + # 'dau2_eta': (20, -2.5, 2.5), + # 'ditau_deltaR': (30, 0.3, 1.3), + # 'dib_deltaR': (25, 0, 2.5), + # 'bH_pt': (20, 70, 600), + # 'bH_mass': (30, 0, 280), + # 'tauH_mass': (30, 0, 170), + # 'tauH_pt': (30, 0, 500), + # 'tauH_SVFIT_mass': (30, 0, 250), + # 'tauH_SVFIT_pt': (20, 200, 650), + # 'bjet1_pt': (25, 10, 600), + # 'bjet2_pt': (25, 10, 550), + # 'bjet1_eta': (20, -2.5, 2.5), + # 'bjet2_eta': (20, -2.5, 2.5), + } + variables = tuple(binning.keys()) + ('HHKin_mass', 'dau1_iso') + + categories = ('baseline',) #('baseline', 's1b1jresolvedMcut', 's2b0jresolvedMcut', 'sboostedLLMcut') + + # Parse input arguments + desc = 'Producer trigger histograms.\n' + desc += "Run example: python tests/test_trigger_regions.py --indir /data_CMS/cms/alves/HHresonant_SKIMS/SKIMS_UL18_EOSv4_Signal/ --masses 400 500 600 700 800 900 1000 1250 1500 --channels ETau --met_turnon 180 --region_cuts 40 40 --copy" + parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument('--indir', required=True, type=str, + help='Full path of ROOT input file') + # parser.add_argument('--masses', required=True, nargs='+', type=str, + # help='Resonance mass') + parser.add_argument('--channel', required=True, type=str, + help='Select the channel over which the workflow will be run.' ) + parser.add_argument('--year', required=True, type=str, choices=('2016', '2016APV', '2017', '2018', '2022'), + help='Select the year over which the workflow will be run.' ) + # parser.add_argument('--spin', required=True, type=int, choices=(0, 2), + # help='Select the spin hypothesis over which the workflow will be run.' ) + parser.add_argument('--deltaR', type=float, default=0.5, help='DeltaR between the two leptons.' ) + parser.add_argument('--plot', action='store_true', + help='Reuse previously produced data for quick plot changes.') + parser.add_argument('--copy', action='store_true', + help='Copy the outputs to EOS at the end.') + parser.add_argument('--notext', action='store_true', help='Square diagram without text.') + parser.add_argument('--sequential', action='store_true', + help='Do not use the multiprocess package.') + parser.add_argument('--bigtau', action='store_true', + help='Consider a larger single tau region, reducing the ditau one.') + parser.add_argument('--met_turnon', type=float, default=180, + help='MET trigger turnon cut [GeV].' ) + parser.add_argument('--region_cuts', required=False, type=float, nargs=2, default=(190, 190), + help='High/low regions pT1 and pT2 selection cuts [GeV].' ) + parser.add_argument('--configuration', dest='configuration', required=True, + help='Name of the configuration module to use.') + args = utils.parse_args(parser) + + met_turnon = args.met_turnon + regcuts = args.region_cuts + ptcuts = utils.get_ptcuts(args.channel, args.year) + + main_dir = os.path.join(os.path.join('/t3home/', os.environ['USER'], 'TriggerScaleFactors'), + '_'.join((args.channel, *[str(x) for x in regcuts], + 'DR', str(args.deltaR), 'PT', *[str(x) for x in ptcuts], 'TURNON', + str(met_turnon)))) + if args.bigtau: + main_dir += '_BIGTAU' + + regions = ('legacy', 'met', 'tau') + + #### run main function ### + if not args.plot: + # for sample in args.masses: + trigger_regions(args.indir, args.channel, args.year, args.deltaR) + # if args.sequential: + # pass + # else: + # pool = multiprocessing.Pool(processes=6) + # pool.starmap(trigger_regions, + # zip(it.repeat(args.indir), it.repeat(args.channel), it.repeat(args.year), it.repeat(args.deltaR))) + + ########################### + + sum_stats, err_sum_stats = ([] for _ in range(2)) + contam1, contam2, contam1_errors, contam2_errors = ([] for _ in range(4)) + from_directory = os.path.join(main_dir, args.channel) + outname = get_outname(args.channel, args.bigtau) + with open(outname, "rb") as f: + ahistos = pickle.load(f) + + # write csv header, one per category + out_counts = [] + # plot histograms and fill CSV with histogram integrals + c_legacy_trg, c_met_trg, c_tau_trg = ({} for _ in range(3)) + + acounts = rec_dd() + for reg in regions: + for key,cut in ahistos.items(): + acounts[key][reg] = round(ahistos[key][reg]["baseline"].values().sum(), 2) + + # append to table, one line per region + with open(os.path.join(out_counts[categories.index(cat)], 'table.csv'), 'a') as f: + reader = csv.writer(f, delimiter=',', quotechar='|') + row = [reg] + row.extend([acounts[k][reg] for k in acounts.keys()]) + reader.writerow(row) + + if reg=='legacy': + c_legacy_trg[reg] = acounts["Base"][reg] + c_met_trg[reg] = acounts["NoBaseMET"][reg] + c_tau_trg[reg] = acounts["NoBaseNoMETTau"][reg] + elif reg=='met': + c_legacy_trg[reg] = acounts["BaseNoMET"][reg] + c_met_trg[reg] = acounts["MET"][reg] + c_tau_trg[reg] = acounts["NoBaseNoMETTau"][reg] + elif reg=='tau': + c_legacy_trg[reg] = acounts["BaseNoTau"][reg] + c_met_trg[reg] = acounts["NoBaseMETNoTau"][reg] + c_tau_trg[reg] = acounts["Tau"][reg] + + c1, c2, e1, e2 = sq_res + contam1.append(c1) + contam2.append(c2) + contam1_errors.append(e1) + contam2_errors.append(e2) + + stats_l = [c_legacy_trg['legacy'],c_met_trg['legacy'],c_tau_trg['tau'],c_legacy_trg['tau']] + stats = sum(stats_l) + estats = sum(np.sqrt(stats_l)) + sum_stats.append(stats) + err_sum_stats.append(estats) + + contam1 = [float(x) for x in contam1] + contam2 = [float(x) for x in contam2] + contam1_errors = [float(x) for x in contam1_errors] + contam2_errors = [float(x) for x in contam2_errors] + # masses = [float(x) for x in args.masses] + contamination_save('data', '_'.join([str(x) for x in regcuts]) + '_' + args.channel, + contam1, contam2, contam1_errors, contam2_errors) + stats_save('data', '_'.join([str(x) for x in regcuts]) + '_' + args.channel, + sum_stats, err_sum_stats, mode='a') + + if args.copy: + import subprocess + to_directory = os.path.join('/eos/home-b/bfontana/www/TriggerScaleFactors', main_dir) + to_directory = os.path.join(to_directory, args.channel) + + for sample in args.masses: + sample_from = os.path.join(from_directory, sample) + print('Copying: {}\t\t--->\t{}'.format(sample_from, to_directory), flush=True) + subprocess.run(['rsync', '-ah', sample_from, to_directory]) diff --git a/tests/test_compare_gains.py b/tests/test_compare_gains.py deleted file mode 100644 index 2437db0..0000000 --- a/tests/test_compare_gains.py +++ /dev/null @@ -1,71 +0,0 @@ -import os -import argparse -import json -import numpy as np - -import matplotlib -import matplotlib.pyplot as plt -import mplhep as hep -plt.style.use(hep.style.ROOT) - -def compare_gains(chn, spin): - data = {} - for mode in ('standard', 'bigtau'): - with open('data_' + chn + '_' + mode + '.json') as json_data: - data[mode] = json.load(json_data) - - chn_unicodes = {"etau": r'$bb\: e\tau$', - "mutau": r'$bb\: \mu\tau$', - "tautau": r'$bb\: \tau\tau$', - "mumu": r'$bb\: \mu\mu$'} - spin_map = {'0': "Radion", '2': "BulkGraviton"} - masses = [300, 400, 500, 600, 700, 750, 800, 850, 900, 1000, - 1250, 1500, 1750, 2000, 2500, 3000] - colors = iter(("red", "dodgerblue")) - - fig, (ax1, ax2) = plt.subplots(2, sharex=True, gridspec_kw={'height_ratios': [3., 1.]}) - plt.subplots_adjust(wspace=0, hspace=0) - - ax1.set_ylabel("Number of weighted events", fontsize=20) - for mode in ('standard', 'bigtau'): - err = [e/2. for e in data[mode]['errs'][chn]] - ax1.errorbar(masses, data[mode]['vals'][chn], - yerr=(err, err), fmt='-o', color=next(colors), label=mode) - ax1.legend(loc="upper right") - - ax2.set_ylim(-2, 15) - yticks = [0., 5., 10.] - ax2.set_yticks(yticks) - line_opt = dict(color="grey", linestyle="--") - for yval in yticks: - ax2.axhline(y=yval, **line_opt) - ax2.set_ylabel(r"Ratio - 1 [%]", fontsize=20) - ax2.set_xlabel(r"$m(X)\:\:[GeV]$", fontsize=21) - - erratio = [1/2 * np.sqrt(ea**2/a**2 + eb**2/b**2) * a/b * 100 - for a,b,ea,eb in zip(data['standard']['vals'][chn],data['bigtau']['vals'][chn], - data['standard']['errs'][chn],data['bigtau']['errs'][chn])] - ax2.errorbar(masses, [(x/y-1.)*100 for x, y in zip(data['standard']['vals'][chn],data['bigtau']['vals'][chn])], - yerr=[erratio,erratio], fmt='-o', color='black') - - hep.cms.text(' Preliminary', fontsize=22, ax=ax1) - hep.cms.lumitext(chn_unicodes[chn] + " (baseline) | " + spin_map[spin], - fontsize=21, ax=ax1) - - output = os.path.join("/eos/home-b/bfontana/www/TriggerScaleFactors/CompareRatios/", - chn + "_" + os.path.basename(__file__[:-3])) - for ext in ('.png', '.pdf'): - fig.savefig(output + ext) - print('Plot saved under {}'.format(output + ext)) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Compare two selection regions with and without bigtau flag.') - parser.add_argument('--channel', required=True, choices=('etau', 'mutau', 'tautau'), help='Which analysis channel to consider.') - parser.add_argument('--spin', required=True, choices=('0', '2'), - help='Signal spin hypothesis.') - - FLAGS = parser.parse_args() - - compare_gains(chn=FLAGS.channel, spin=FLAGS.spin) - diff --git a/tests/test_draw_kin_regions.py b/tests/test_draw_kin_regions.py deleted file mode 100644 index 307f2c8..0000000 --- a/tests/test_draw_kin_regions.py +++ /dev/null @@ -1,478 +0,0 @@ -# Coding: utf-8 - -_all_ = [ 'drawCuts' ] - -import os -import argparse -import glob -import uproot as up -import hist -import pickle - -import matplotlib; import matplotlib.pyplot as plt -import matplotlib.colors as colors -import mplhep as hep -plt.style.use(hep.style.ROOT) - -def sel_category(batch, category, year): - """Applies analysis category cuts to an awkward batch.""" - deepJetWP = {'2016' : (0.048, 0.249), - '2016APV' : (0.051, 0.260), - '2017' : (0.0532, 0.3040), - '2018' : (0.0490, 0.2783)}[year] - - if category == "sboosted": - batch = batch[(batch.isBoosted == 1) & - (batch.bjet1_bID_deepFlavor > deepJetWP[0]) & (batch.bjet2_bID_deepFlavor > deepJetWP[0])] - elif category == "s1b1jresolved": - batch = batch[(batch.isBoosted != 1) & - (((batch.bjet1_bID_deepFlavor > deepJetWP[1]) & (batch.bjet2_bID_deepFlavor < deepJetWP[1])) | - ((batch.bjet1_bID_deepFlavor < deepJetWP[1]) & (batch.bjet2_bID_deepFlavor > deepJetWP[1])))] - elif category == "s2b0jresolved": - batch = batch[(batch.isBoosted != 1) & - (batch.bjet1_bID_deepFlavor > deepJetWP[1]) & (batch.bjet2_bID_deepFlavor > deepJetWP[1])] - - return batch - -def sel_cuts(batch, channel): - """Applies analysis selection cuts to an awkward batch.""" - chn_map = {"etau": 1, "mutau": 0, "tautau": 2, "mumu": 3} - batch = batch[batch.pairType == chn_map[channel]] - - # When one only has 0 or 1 bjets the HH mass is not well defined, - # and a value of -1 is assigned. One thus has to remove the cut below - # when considering events with less than 2 b-jets. - # batch = batch[batch.HHKin_mass > 1] - - # third lepton veto - batch = batch[batch.nleps == 0] - - # require at least two b jet candidates - batch = batch[batch.nbjetscand > 1] - - # opposite sign leptons - batch = batch[batch.isOS == 1] - - # Loose / Medium / Tight - iso_allowed = { 'dau1_ele': 1., 'dau1_mu': 0.15, 'dau2_mu': 0.15, - 'dau1_tau': 5., 'dau2_tau': 5. } - - # lepton id and isolation - if channel == "etau": - batch = batch[(batch.dau1_eleMVAiso == 1.) & (batch.dau2_deepTauVsJet >= 5.)] - elif channel == "mutau": - batch = batch[(batch.dau1_iso < 0.15) & (batch.dau2_deepTauVsJet >= 5.)] - elif channel == "tautau": - batch = batch[(batch.dau1_deepTauVsJet >= 5.) & (batch.dau2_deepTauVsJet >= 5.)] - elif channel == "mumu": - batch = batch[(batch.dau1_iso < 0.15) & (batch.dau2_iso < 0.15)] - - return batch - -def getHisto(x, y, inputs, xbins, ybins, - channel, category, year, dtype, savename, save=False, other_vars=None): - avars = (x, y) if other_vars is None else (x, y, *other_vars) - for inp in inputs: - assert inp[-5:] == ".root" - - if save and os.path.isfile(savename): - with open(savename, 'rb') as f: - histogram = pickle.load(f) - return histogram - - histogram = hist.Hist( - hist.axis.Regular(*xbins, name=x), - hist.axis.Regular(*ybins, name=y), - storage=hist.storage.Double() - ) - - for ibatch, batch in enumerate(up.iterate(inputs, step_size="200MB", library='ak', - filter_name=avars)): - print("Batch {}".format(ibatch)) - batch = sel_cuts(batch, channel) - batch = sel_category(batch, category, year) - histogram.fill(getattr(batch, x), getattr(batch, y)) - - with open(savename, 'wb') as f: - pickle.dump(histogram, f) - - return histogram - -class DrawCuts(): - def __init__(self, inputs, sample, channel, category, year, dtype, save): - self.inputs = inputs - self.sample = sample - self.channel = channel - self.category = category - self.year = year - self.dtype = dtype - self.save = save - - self.other_vars = ('HHKin_mass', 'pairType', 'isOS', 'dau1_eleMVAiso', 'dau1_iso', 'dau2_iso', - 'dau1_deepTauVsJet', 'dau2_deepTauVsJet', 'nleps', 'nbjetscand', - 'bjet1_bID_deepFlavor', 'bjet2_bID_deepFlavor', 'isBoosted') - - self.outname = lambda mode: os.path.join('pickles', - '_'.join((mode, sample, channel, dtype, category, year)) + ".pkl") - - self.rect_opt = dict(facecolor='white', edgecolor="black", linewidth=2) - self.leg_opt = dict(loc="upper right" if channel!="mumu" else "upper center", - facecolor="white", edgecolor="white", framealpha=1) - - def triggers(self): - mode = "trigger" - - fig, ax = self.set_figure(16, 16) - - bins = self.get_bins(mode=mode, dtype=self.dtype) - histogram = getHisto(x="dau1_pt", y="dau2_pt", xbins=bins[0], ybins=bins[1], - channel=self.channel, category=self.category, year=self.year, - other_vars=self.other_vars, inputs=self.inputs, dtype=self.dtype, - savename=self.outname(mode), save=self.save) - - xlabel, ylabel = self.set_axis_labels(mode=mode, channel=self.channel) - ax.set_xlabel(xlabel) - ax.set_ylabel(ylabel) - - values = histogram.values() - cbar = hep.hist2dplot(values, histogram.axes[0].edges, histogram.axes[1].edges, - flow=None, norm=colors.LogNorm(vmin=1, vmax=values.max())) - cbar.cbar.ax.set_ylabel("No. Events", rotation=90, labelpad=0.5, loc='top') - - self.set_hep_labels(self.channel) - - self.set_lines(histogram, mode=mode) - - if self.channel == "etau": - rect = matplotlib.patches.Rectangle((76,202), 24, 35, **self.rect_opt) - elif self.channel == "mutau": - rect = matplotlib.patches.Rectangle((76,202), 24, 35, **self.rect_opt) - elif self.channel == "tautau": - rect = matplotlib.patches.Rectangle((194,203), 42, 34, **self.rect_opt) - elif self.channel == "mumu": - rect = matplotlib.patches.Rectangle((76,202), 24, 35, **self.rect_opt) - - ax.add_patch(rect) - - plt.legend(title="Triggers", **self.leg_opt) - self.savefig(mode=mode) - - def mass(self): - mode = "mass" - - fig, ax = self.set_figure(16, 16) - - bins = self.get_bins(mode=mode, dtype=self.dtype) - histogram = getHisto(x="tauH_mass", y="bH_mass", xbins=bins[0], ybins=bins[1], - channel=self.channel, category=self.category, year=self.year, - other_vars=self.other_vars, inputs=self.inputs, dtype=self.dtype, - savename=self.outname(mode), save=self.save) - - xlabel, ylabel = self.set_axis_labels(mode=mode, channel=self.channel) - ax.set_xlabel(xlabel) - ax.set_ylabel(ylabel) - - values = histogram.values() - cbar = hep.hist2dplot(values, histogram.axes[0].edges, histogram.axes[1].edges, - flow=None, norm=colors.LogNorm(vmin=1, vmax=values.max())) - cbar.cbar.ax.set_ylabel("No. Events", rotation=90, labelpad=0.5, loc='top') - - self.set_hep_labels(self.channel) - - self.set_lines(histogram, mode=mode) - - rect = matplotlib.patches.Rectangle((140,332), 57, 13, **self.rect_opt) - ax.add_patch(rect) - - plt.legend(loc="upper right", facecolor="white", edgecolor="white", framealpha=1) - self.savefig(mode) - - def get_bins(self, mode, dtype): - if mode == "trigger": - if self.channel == "etau": - nbinsx = 25 if dtype == "signal" else 35 - nbinsy = 25 if dtype == "signal" else 35 - xbins = (nbinsx, 5, 101) - ybins = (nbinsy, 5, 240) - elif self.channel == "mutau": - nbinsx = 25 if dtype == "signal" else 35 - nbinsy = 25 if dtype == "signal" else 35 - xbins = (nbinsx, 10, 101) - ybins = (nbinsy, 10, 240) - elif self.channel == "tautau": - nbinsx = 25 if dtype == "signal" else 40 - nbinsy = 25 if dtype == "signal" else 40 - xbins = (nbinsx, 12, 240) - ybins = (nbinsy, 12, 240) - elif self.channel == "mumu": - nbinsx = 25 if dtype == "signal" else 35 - nbinsy = 25 if dtype == "signal" else 35 - xbins = (nbinsx, 10, 170) - ybins = (nbinsy, 10, 130) - - elif mode == "mass": - nbinsx = 50 if dtype == "signal" else 100 - nbinsy = 50 if dtype == "signal" else 100 - if self.channel == "etau": - xbins = (nbinsx, 5, 200) - ybins = (nbinsy, 5, 350) - elif self.channel == "mutau": - xbins = (nbinsx, 10, 200) - ybins = (nbinsy, 10, 350) - elif self.channel == "tautau": - xbins = (nbinsx, 15, 200) - ybins = (nbinsy, 15, 350) - elif self.channel == "mumu": - xbins = (nbinsx, 10, 200) - ybins = (nbinsy, 10, 350) - - return xbins, ybins - - def savefig(self, mode): - for ext in ('.png', '.pdf',): - smpl = self.sample.replace(' ', '-').replace('+', '-') - savename = os.path.join("/eos/home-b/bfontana/www/DrawCuts/", mode) - savename = os.path.join(savename, - '_'.join(("draw_" + mode, smpl, - self.channel, self.category, self.year))) - plt.savefig(savename + ext, dpi=600) - print('Stored in {}'.format(savename + ext)) - plt.close('all') - - def set_figure(self, wsize, hsize): - fig = plt.figure(figsize=(wsize, hsize),) - ax = plt.subplot(111) - ax.title.set_size(100) - return fig, ax - - def set_axis_labels(self, mode, channel): - """Set X and Y label.""" - if mode == "trigger": - if channel == "etau": - xlabel = r"$p_T(e)$ [GeV]" - ylabel = r"$p_T(\tau)$ [GeV]" - elif channel == "mutau": - xlabel = r"$p_T(\mu)$ [GeV]" - ylabel = r"$p_T(\tau)$ [GeV]" - elif channel == "tautau": - xlabel = r"$p_T(\tau_1)$ [GeV]" - ylabel = r"$p_T(\tau_2)$ [GeV]" - elif channel == "mumu": - xlabel = r"$p_T(\mu)$ [GeV]" - ylabel = r"$p_T(\mu)$ [GeV]" - elif mode == "mass": - xlabel = r"$m_{{\tau\tau}}^{{vis}}$ [GeV]" - ylabel = r"$m_{{bb}}^{{vis}}$ [GeV]" - return xlabel, ylabel - - def set_hep_labels(self, channel): - hep.cms.text('Preliminary', fontsize=40) - chn_unicodes = {"etau": r'$bb\: e\tau$', - "mutau": r'$bb\: \mu\tau$', - "tautau": r'$bb\: \tau\tau$', - "mumu": r'$bb\: \mu\mu$'} - cat_map = {'baseline': "baseline", 'baseline_boosted': "baseline boosted", - 'boostedL_pnet': "boosted", 'res1b': "res 1b", 'res2b': "res 2b"} - sample_header = self.sample.replace('TT', r"$t\bar{t}$") - hep.cms.lumitext((chn_unicodes[self.channel] + " (" + cat_map[self.category] + ") | " + - sample_header + " (" + self.year + ")"), - fontsize=24) # r"138 $fb^{-1}$ (13 TeV)" - - def set_lines(self, h, mode): - xmin, xmax = h.axes[0].edges[0], h.axes[0].edges[-1] - ymin, ymax = h.axes[1].edges[0], h.axes[1].edges[-1] - - if mode == "trigger": - if self.channel == "etau": - if self.year == "2016": - plt.plot([26.5, 26.5], [ymin, ymax], - c='black', linewidth=10, label=r"single-e + e$\tau$") - plt.plot([xmin, 25.5, 25.5], [191., 191., ymax], - c='black', linewidth=10, label=r"single-$\tau$") - plt.plot([xmin, 25.5, 25.5], [189., 189., ymin], - c='black', linewidth=10, label=r"MET") - elif self.year == "2017" or self.year == "2018": - plt.plot([24.5, 24.5, 32.5, 32.5], [ymax, 36., 36., ymin], - c='black', linewidth=10, label=r"single-e + e$\tau$") - plt.plot([xmin, 23.5, 23.5], [191., 191., ymax], - c='red', linewidth=10, label=r"single-$\tau$") - plt.plot([xmin, 23.5, 23.5, 31.5, 31.5], [189., 189., 34., 34., ymin], - c='deepskyblue', linewidth=10, label="MET") - - elif self.channel == "mutau": - if self.year == "2016": - plt.plot([19.5, 19.5, 24.5, 24.5], [ymax, 24.5, 24.5, ymin], - c='black', linewidth=10, label=r"single-$\mu$ + $\mu\tau$") - plt.plot([xmin, 18.5, 18.5], [191., 191., ymax], - c='red', linewidth=10, label=r"single-$\tau$") - plt.plot([xmin, 18.5, 18.5, 23.5, 23.5], [189., 189., 31., 31., ymin], - c='deepskyblue', linewidth=10, label="MET") - elif self.year == "2017": - plt.plot([20.5, 20.5, 27.5, 27.5], [ymax, 33., 33., ymin], - c='black', linewidth=10, label=r"single-$\mu$ + $\mu\tau$") - plt.plot([xmin, 19.5, 19.5], [191., 191., ymax], - c='red', linewidth=10, label=r"single-$\tau$") - plt.plot([xmin, 19.5, 19.5, 26.5, 26.5], [189., 189., 31., 31., ymin], - c='deepskyblue', linewidth=10, label="MET") - elif self.year == "2018": - plt.plot([20.5, 20.5, 24.5, 24.5], [ymax, 33., 33., ymin], - c='black', linewidth=10, label=r"single-$\mu$ + $\mu\tau$") - plt.plot([xmin, 19.5, 19.5], [191., 191., ymax], - c='red', linewidth=10, label=r"single-$\tau$") - plt.plot([xmin, 19.5, 19.5, 23.5, 23.5], [189., 189., 31., 31., ymin], - c='deepskyblue', linewidth=10, label="MET") - - elif self.channel == "tautau": - plt.plot([42., 42., xmax], [ymax, 42., 42.], c='black', linewidth=10, label=r"$\tau\tau$") - plt.plot([xmin, 39., 39.], [191., 191., ymax], c='red', linewidth=10, label=r"single-$\tau$") - plt.plot([xmax, 191., 191.], [39., 39., ymin], c='red', linewidth=10) - plt.plot([189., 189., 39., 39., xmin], [ymin, 39., 39., 189., 189.], c='deepskyblue', linewidth=10, label="MET") - - elif self.channel == "mumu": - if self.year == "2016": - plt.plot([24.5, 24.5], [ymax, ymin], - c='black', linewidth=10, label=r"single-$\mu$") - elif self.year == "2017": - plt.plot([27.5, 27.5], [ymax, ymin], - c='black', linewidth=10, label=r"single-$\mu$") - elif self.year == "2018": - plt.plot([24.5, 24.5], [ymax, ymin], - c='black', linewidth=10, label=r"single-$\mu$") - - elif mode == "mass": - arrow_h = dict(color='red', width=3, head_width=10, head_length=5, shape="full") - arrow_v = dict(color='red', width=2.6, head_width=7, head_length=6, shape="full") - line_d = dict(c='red', linewidth=10) - if self.channel == "mumu": - if self.dtype == "mc": - # plt.plot([20., 130., 130., 20., 20.], [50., 50., 270., 270., 50.], - # label=r"Mass window cut", **line_d) - # plt.arrow(20., (ymax-ymin)/2, -4, 0, **arrow_h) - # plt.arrow(130., (ymax-ymin)/2, 10, 0, **arrow_h) - # plt.arrow(75., 50, 0, -12, **arrow_v) - # plt.arrow(75., 270, 0, 12, **arrow_v) - - dy_l, dy_r = 86., 95. # DY left and right cuts - tt_d, tt_u = 50., 150. # ttbar down and up cuts - - plt.plot([xmin, dy_l, dy_l], [tt_u, tt_u, ymax], **line_d, label=r"Mass window cut") - plt.plot([dy_r, dy_r, xmax], [ymax, tt_u, tt_u], **line_d) - plt.plot([xmin, dy_l, dy_l], [tt_d, tt_d, ymin], **line_d) - plt.plot([dy_r, dy_r, xmax], [ymin, tt_d, tt_d], **line_d) - - arrow_length_h, arrow_length_v = 8, 10 - # top left corner - plt.arrow(dy_l/2, tt_u, 0, arrow_length_v, **arrow_v) - plt.arrow(dy_l, (ymax-tt_u)/2. + tt_u, -arrow_length_h, 0, **arrow_h) - - # top right cornerd - plt.arrow(dy_r, (ymax-tt_u)/2. + tt_u, arrow_length_h, 0, **arrow_h) - plt.arrow((xmax-dy_r)/2.+dy_r, tt_u, 0, arrow_length_v, **arrow_v) - - # bottom left corner - plt.arrow(dy_l, tt_d/2, -arrow_length_h, 0, **arrow_h) - plt.arrow(dy_l/2., tt_d, 0, -arrow_length_v, **arrow_v) - - # bottom right corner - plt.arrow(dy_r, tt_d/2, arrow_length_h, 0, **arrow_h) - plt.arrow((xmax-dy_r)/2. + dy_r, tt_d, 0, -arrow_length_v, **arrow_v) - - elif self.dtype == "dy": - arrow_opt = dict(color='red', width=2, head_width=8, head_length=4, shape="full") - plt.plot([86., 86.], [ymin, ymax], label=r"Mass window cut", **line_d) - plt.plot([95., 95.], [ymin, ymax], **line_d) - - plt.arrow(86., (ymax-ymin)/5., 2, 0, **arrow_opt) - plt.arrow(95., 2*(ymax-ymin)/5., -2, 0, **arrow_opt) - plt.arrow(86., 3*(ymax-ymin)/5., 2, 0, **arrow_opt) - plt.arrow(95., 4*(ymax-ymin)/5., -2, 0, **arrow_opt) - - elif self.dtype == "tt": - dy_l, dy_r = 86., 95. # DY left and right cuts - tt_d, tt_u = 50., 150. # ttbar down and up cuts - - plt.plot([xmin, dy_l, dy_l], [tt_u, tt_u, ymax], **line_d, label=r"Mass window cut") - plt.plot([dy_r, dy_r, xmax], [ymax, tt_u, tt_u], **line_d) - plt.plot([xmin, dy_l, dy_l], [tt_d, tt_d, ymin], **line_d) - plt.plot([dy_r, dy_r, xmax], [ymin, tt_d, tt_d], **line_d) - - arrow_length_h, arrow_length_v = 8, 10 - # top left corner - plt.arrow(dy_l/2, tt_u, 0, arrow_length_v, **arrow_v) - plt.arrow(dy_l, (ymax-tt_u)/2. + tt_u, -arrow_length_h, 0, **arrow_h) - - # top right corner - plt.arrow(dy_r, (ymax-tt_u)/2. + tt_u, arrow_length_h, 0, **arrow_h) - plt.arrow((xmax-dy_r)/2.+dy_r, tt_u, 0, arrow_length_v, **arrow_v) - - # bottom left corner - plt.arrow(dy_l, tt_d/2, -arrow_length_h, 0, **arrow_h) - plt.arrow(dy_l/2., tt_d, 0, -arrow_length_v, **arrow_v) - - # bottom right corner - plt.arrow(dy_r, tt_d/2, arrow_length_h, 0, **arrow_h) - plt.arrow((xmax-dy_r)/2. + dy_r, tt_d, 0, -arrow_length_v, **arrow_v) - - else: - plt.plot([20., 130., 130., 20., 20.], [50., 50., 270., 270., 50.], - label=r"Mass window cut", **line_d) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description="draw cuts on top of signal or MC distributions", - formatter_class=argparse.RawTextHelpFormatter) - - parser.add_argument('--dtype', choices=('signal', 'mc', 'dy', 'tt'), default='signal', - type=str, help='Data type') - parser.add_argument('--skim_tag', required=True, type=str, help='Tag of input skims.') - parser.add_argument('--signal', choices=('Radion', 'BulkGraviton'), default='Radion', - type=str, help='Signal particle type') - parser.add_argument('--mass', default='1000', - type=str, help='Signal particle type') - parser.add_argument('--channel', required=True, choices=("etau", "mutau", "tautau", "mumu"), - type=str, help='Signal particle type') - parser.add_argument('--category', default="baseline", - choices=("baseline", "baseline_boosted", "boostedL_pnet", "res1b", "res2b"), - type=str, help='Analysis category') - parser.add_argument('--year', required=True, choices=("2016", "2016APV", "2017", "2018"), - type=str, help='Signal particle type') - parser.add_argument('--mode', default="trigger", choices=("trigger", "mass"), - type=str, help='Signal particle type') - parser.add_argument('--save', action="store_false", - help='Wether to save the histograms or to use the ones produced.') - FLAGS = parser.parse_args() - - base = "/data_CMS/cms/alves/HHresonant_SKIMS/" - if FLAGS.dtype == "signal": - base = os.path.join(base, "SKIMS_UL18_{}_Sig/".format(FLAGS.skim_tag)) - name = os.path.join(base, "GluGluTo{}ToHHTo2B2Tau_M-{}_".format(FLAGS.signal, FLAGS.mass)) - infiles = glob.glob(os.path.join(name, "output_*.root")) - elif FLAGS.dtype == "dy": - base = os.path.join(base, "SKIMS_UL18_{}_MC/".format(FLAGS.skim_tag)) - names = ["DYJetsToLL_M-50_TuneCP5_13TeV-amc"] - infiles = [os.path.join(base, name, "hadded.root") for name in names] - elif FLAGS.dtype == "tt": - base = os.path.join(base, "SKIMS_UL18_{}_MC/".format(FLAGS.skim_tag)) - names = ["TTTo2L2Nu", "TTToHadronic", "TTToSemiLeptonic"] - infiles = [os.path.join(base, name, "hadded.root") for name in names] - elif FLAGS.dtype == "mc": - base = os.path.join(base, "SKIMS_UL18_{}_MC/".format(FLAGS.skim_tag)) - names = ["DYJetsToLL_M-50_TuneCP5_13TeV-amc", - "TTTo2L2Nu", "TTToHadronic", "TTToSemiLeptonic"] - infiles = [os.path.join(base, name, "hadded.root") for name in names] - - if FLAGS.dtype == "signal": - sample = FLAGS.signal + " " + FLAGS.mass + " GeV" - elif FLAGS.dtype == "dy": - sample = "DY" - elif FLAGS.dtype == "tt": - sample = "TT" - elif FLAGS.dtype == "mc": - sample = "TT+DY" - - draw = DrawCuts(infiles, channel=FLAGS.channel, year=FLAGS.year, category=FLAGS.category, - sample=sample, dtype=FLAGS.dtype, save=FLAGS.save) - if FLAGS.mode == "trigger": - draw.triggers() - elif FLAGS.mode == "mass": - draw.mass() diff --git a/tests/test_theory.py b/tests/test_theory.py deleted file mode 100644 index 8b744cb..0000000 --- a/tests/test_theory.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np - -nevents = 200000 -ntriggers = 2 -eff_init_data = [0.4, 0.6, 0.2] -eff_init_mc = [0.65, 0.35, 0.15] -assert len(eff_init_data)==ntriggers+1 -assert len(eff_init_mc)==ntriggers+1 -# 0: trigger 1 -# 1: trigger 2 -# 2: trigger 1 AND trigger 2 -choices = [0,1] - -trigger_decisions_data = np.random.choice(choices, size=nevents, p=eff_init_data[:-1]) -trigger_decisions_mc = np.random.choice(choices, size=nevents, p=eff_init_mc[:-1]) -print(trigger_decisions_data) -print(trigger_decisions_mc) -occur_data = np.array([ np.count_nonzero( (trigger_decisions_data==ch)) for ch in choices ]) -occur_mc = np.array([ np.count_nonzero( (trigger_decisions_mc==ch)) for ch in choices ]) -eff_data = occur_data / nevents -eff_mc = occur_mc / nevents -print(eff_data) -print(eff_mc) - -mask_data = np.random.choice(choices, - size=trigger_decisions_data.shape, - p=[1-eff_init_data[-1],eff_init_data[-1]]).astype(bool) -mask_mc = np.random.choice(choices, - size=trigger_decisions_mc.shape, - p=[1-eff_init_mc[-1],eff_init_mc[-1]]).astype(bool) -print(mask_data) -print(mask_mc) -trigger_decisions_data[mask_data] = 2 -trigger_decisions_mc[mask_mc] = 2 -print(trigger_decisions_data) -print(trigger_decisions_mc) - -occur_data = np.array([ np.count_nonzero( np.where((trigger_decisions_data==ch))) for ch in choices ]) -occur_mc = np.array([ np.count_nonzero( np.where((trigger_decisions_mc==ch))) for ch in choices ]) -eff_data = occur_data / nevents -eff_mc = occur_mc / nevents -print(eff_data) -print(eff_mc) diff --git a/tests/test_trigger_bits_number.py b/tests/test_trigger_bits_number.py deleted file mode 100644 index 5ca6271..0000000 --- a/tests/test_trigger_bits_number.py +++ /dev/null @@ -1,76 +0,0 @@ -# coding: utf-8 - -__all__ = ['TriggerBitsNumber'] - -import unittest - -import os -import sys -parent_dir = os.path.abspath(__file__ + 2 * '/..') -sys.path.insert(0, parent_dir) - -import inclusion -from inclusion import selection -from inclusion import config - -import ROOT - -class TriggerBitsNumber(unittest.TestCase): - def setUp(self): - self.isdata = True - self.entry_names = ('triggerbit', 'RunNumber', 'PUReweight', 'lumi', - 'IdAndIsoSF_deep_pt', 'HHKin_mass', 'pairType', - 'dau1_eleMVAiso', 'dau1_iso', 'dau1_deepTauVsJet', - 'dau2_deepTauVsJet', 'nleps', 'nbjetscand', - 'tauH_SVFIT_mass', 'bH_mass_raw',) - self.entry_names += tuple(config.var_eff) - self.dummy_dataset = 'MET' # not used but required by the EventSelection class - - def my_print(self, i, n): - print('\r{} / {}'.format(i, n), flush=True, end='') - - def test_met_bits(self): - infile = '/data_CMS/cms/alves/HHresonant_SKIMS/SKIMS_UL18_Aug15Evening/MET__Run2018A/output_10.root' - f_in = ROOT.TFile(infile) - t_in = f_in.Get('HTauTauTree') - - t_in.SetBranchStatus('*', 0) - for ientry in self.entry_names: - t_in.SetBranchStatus(ientry, 1) - - cmet = 0 - nentries = t_in.GetEntriesFast() - for ientry,entry in enumerate(t_in): - if ientry%1000==0: - self.my_print(ientry, nentries) - - entries = {x: getattr(entry, x) for x in self.entry_names} - sel = selection.EventSelection(entries, self.dummy_dataset, self.isdata) - - if sel.check_bit(sel.get_trigger_bit('METNoMu120')): - cmet += 1 - - return cmet > 50 # some tunable cut - - def test_muon_bits(self): - infile = '/data_CMS/cms/alves/HHresonant_SKIMS/SKIMS_UL18_Aug15Evening/SingleMuon__Run2018A/output_10.root' - f_in = ROOT.TFile(infile) - t_in = f_in.Get('HTauTauTree') - - t_in.SetBranchStatus('*', 0) - for ientry in self.entry_names: - t_in.SetBranchStatus(ientry, 1) - - cmuon = 0 - nentries = t_in.GetEntriesFast() - for ientry,entry in enumerate(t_in): - if ientry%1000==0: - self.my_print(ientry, nentries) - - entries = {x: getattr(entry, x) for x in self.entry_names} - sel = selection.EventSelection(entries, self.dummy_dataset, self.isdata) - - if sel.check_bit(sel.get_trigger_bit('IsoMu24')): - cmuon += 1 - - return cmuon > 50 # some tunable cut diff --git a/tests/test_trigger_contaminations.py b/tests/test_trigger_contaminations.py deleted file mode 100644 index 55d8256..0000000 --- a/tests/test_trigger_contaminations.py +++ /dev/null @@ -1,161 +0,0 @@ -# coding: utf-8 - -_all_ = [ 'test_trigger_contaminations' ] - -import os -import sys -parent_dir = os.path.abspath(__file__ + 2 * '/..') -sys.path.insert(0, parent_dir) - -import inclusion -from inclusion.config import main -from inclusion.utils import utils - -import argparse -import h5py -import numpy as np -from bokeh.plotting import figure, output_file, save -from bokeh.palettes import Set1 as ColorSet -from bokeh.models import Range1d, ColumnDataSource -from bokeh.layouts import layout - -tau = '\u03C4' -mu = '\u03BC' -pm = '\u00B1' -ditau = tau+tau - -def main(args): - output_file( os.path.join(basedir, 'contaminations_' + args.region_vary + '_' + args.channel + '.html') ) - p_opt = dict(width=800, height=400, x_axis_label='x', y_axis_label='y') - title_d = {'dau1_pt': '(varying first lepton pT cut, second set to 190 GeV)', - 'dau2_pt': '(varying second lepton pT cut, first set to 190 GeV)', - 'both': '(varying both lepton pT cuts)'} - title1 = 'Contaminations in the SingleTau regions ' - p1 = figure(title=title1+title_d[args.region_vary], - tools='save,box_zoom,reset', **p_opt) - p1.xaxis.axis_label = 'm(HH) [GeV]' - p1.yaxis.axis_label = 'Contamination [%]' - p1.x_range = Range1d(0, 14.5) - p1.y_range = Range1d(-0.5, 20.) - - title2 = 'Statistics in the Single ('+tau+'+'+ditau+') and DiTau ('+ditau+'+MET) regions ' - p2 = figure(title=title2+title_d[args.region_vary], tools='save,box_zoom,reset', **p_opt) - p2.xaxis.axis_label = 'm(HH) [GeV]' - p2.yaxis.axis_label = '#Events' - - title3 = 'Normalized statistics in the Single ('+tau+'+'+ditau+') and DiTau ('+ditau+'+MET) regions ' - p3 = figure(title=title3+title_d[args.region_vary], tools='save,box_zoom,reset', **p_opt) - p3.xaxis.axis_label = 'm(HH) [GeV]' - p3.yaxis.axis_label = 'Normalized #Events' - - #linearize x axis - linear_x = [k for k in range(1,len(args.masses)+1)] - xticks = linear_x[:] - for p in (p1,p2,p3): - p.toolbar.logo = None - p.xaxis[0].ticker = xticks - p.xgrid[0].ticker = xticks - p.xgrid.grid_line_alpha = 0.2 - p.xgrid.grid_line_color = 'black' - - nshifts = len(args.region_cuts) - ns2 = int(nshifts/2) - diff = 0.10 if ns2%2==0 else 0.05 - shifts = [round((-ns2+x)*diff,2) for x in range(nshifts)] - colors = ColorSet[9] - for icut, cut in enumerate(args.region_cuts): - if args.region_vary == 'dau1_pt': - cut1, cut2 = cut, '190' - elif args.region_vary == 'dau2_pt': - cut1, cut2 = '190', cut - elif args.region_vary == 'both': - cut1, cut2 = cut, cut - label = '_'.join((str(cut1),str(cut2),args.channel)) - label_stats = label + '_stats' - - with h5py.File(os.path.join('data', label + '.hdf5'), 'r') as f: - masses = f[label][0] - x_str = [str(int(k)) for k in masses] - contam1 = f[label][1] - contam2 = f[label][2] - econtam1 = f[label][3] - econtam2 = f[label][4] - np.all(f[label_stats][1]==masses) - - stats1 = f[label_stats][1] - estats1 = f[label_stats][2] - if icut==0: - stats1_norm = stats1 - stats1_ratio = stats1 / stats1_norm - estats1_ratio = estats1 / stats1_norm - - source = ColumnDataSource({'x': [x+shifts[icut] for x in linear_x], - 'contam1': contam1, - 'contam2': contam2, - 'stats1': stats1, - 'stats1_ratio': stats1_ratio,}) - opt = dict(color=colors[icut], source=source) - - leg0 = (label[:3] if args.region_vary=='dau1_pt' else label[4:7]) + 'GeV ' - leg1 = leg0 + ditau - leg2 = leg1 + '+MET' - p1.line('x', 'contam1', line_width=1, legend_label=leg1, **opt) - p1.line('x', 'contam2', line_width=1, legend_label=leg2, line_dash='4 4', **opt) - p1.multi_line([(x+shifts[icut],x+shifts[icut]) for x in linear_x], - [(max(0,x-y/2),min(100,x+y/2)) for x,y in zip(contam1,econtam1)], - color=colors[icut], line_width=2, legend_label=leg1) - p1.multi_line([(x+shifts[icut],x+shifts[icut]) for x in linear_x], - [(max(0,x-y/2),min(100,x+y/2)) for x,y in zip(contam2,econtam2)], - color=colors[icut], line_width=2, legend_label=leg2) - ga = p1.circle('x', 'contam1', size=6, legend_label=leg1, **opt) - gb = p1.triangle('x', 'contam2', size=8, legend_label=leg2, **opt) - ga = ga.glyph - gb = gb.glyph - ga.line_color = 'black' - gb.line_color = 'black' - p1.xaxis.major_label_overrides = dict(zip(linear_x,x_str)) - - p2.multi_line([(x+shifts[icut],x+shifts[icut]) for x in linear_x], - [(max(0,x-y/2),x+y/2) for x,y in zip(stats1,estats1)], - color=colors[icut], line_width=2, legend_label=leg0) - ga = p2.circle('x', 'stats1', size=6, legend_label=leg0, **opt) - ga = ga.glyph - ga.line_color = 'black' - p2.xaxis.major_label_overrides = dict(zip(linear_x,x_str)) - - p3.multi_line([(x+shifts[icut],x+shifts[icut]) for x in linear_x], - [(max(0,x-y/2),x+y/2) for x,y in zip(stats1_ratio,estats1_ratio)], - color=colors[icut], line_width=2, legend_label=leg0) - ga = p3.circle('x', 'stats1_ratio', size=6, legend_label=leg0, **opt) - ga = ga.glyph - ga.line_color = 'black' - p3.xaxis.major_label_overrides = dict(zip(linear_x,x_str)) - p3.legend.location = 'bottom_left' - - for p in (p1,p2,p3): - p.legend.glyph_height = 15 - p.legend.glyph_width = 15 - p.legend.label_height = 9 - p.legend.label_width = 15 - p.legend.label_text_font_size = '7pt' - p.legend.click_policy = 'hide' - p.output_backend = 'svg' - - save(layout([[p1],[p2,p3]])) - - -if __name__ == '__main__': - basedir = '/eos/user/b/bfontana/www/TriggerScaleFactors/' - - parser = argparse.ArgumentParser(description='Produce plots of trigger gain VS resonance mass.') - parser.add_argument('--masses', required=True, nargs='+', type=str, - help='Resonance mass') - parser.add_argument('--region_cuts', required=True, nargs='+', type=str, - help='Region SingleTau pT cuts') - parser.add_argument('--region_vary', required=True, type=str, choices=('dau1_pt', 'dau2_pt', 'both'), - help='SingleTau region to vary') - parser.add_argument('--channel', required=True, type=str, - help='Select the channel over which the workflow will be run.' ) - args = utils.parse_args(parser) - - main(args) diff --git a/tests/test_trigger_gains.py b/tests/test_trigger_gains.py deleted file mode 100644 index 971b056..0000000 --- a/tests/test_trigger_gains.py +++ /dev/null @@ -1,287 +0,0 @@ -# coding: utf-8 - -_all_ = [ 'test_trigger_gains' ] - -import os -import sys -parent_dir = os.path.abspath(__file__ + 2 * '/..') -sys.path.insert(0, parent_dir) - -import json -import argparse -from inclusion.utils import utils -import numpy as np -from collections import defaultdict as dd -import hist -from hist.intervals import clopper_pearson_interval as clop -import pickle - -import bokeh -from bokeh.plotting import figure, output_file, save -from bokeh.models import Whisker -from bokeh.layouts import gridplot -#from bokeh.io import export_svg - -tau = '\u03C4' -mu = '\u03BC' -pm = '\u00B1' -ditau = tau+tau - -def get_outname(sample, channel, regcuts, ptcuts, met_turnon, bigtau): - utils.create_single_dir('data') - - name = sample + '_' + channel + '_' - name += '_'.join((*regcuts, 'ptcuts', *[str(x) for x in ptcuts], 'turnon', str(met_turnon))) - if bigtau: - name += '_BIGTAU' - name += '.pkl' - - s = 'data/regions_{}'.format(name) - return s - -def pp(chn): - if chn == "tautau": - return ditau - elif chn == "etau": - return "e" + tau - elif chn == "mutau": - return mu + tau - -def rec_dd(): - return dd(rec_dd) - -def set_fig(fig, legend=True): - fig.output_backend = 'svg' - fig.toolbar.logo = None - # if legend: - # fig.legend.click_policy='hide' - # fig.legend.location = 'top_left' - # fig.legend.label_text_font_size = '8pt' - fig.min_border_bottom = 5 - fig.xaxis.visible = True - fig.title.align = "left" - fig.title.text_font_size = "15px" - fig.xaxis.axis_label_text_font_style = "bold" - fig.yaxis.axis_label_text_font_style = "bold" - fig.xaxis.axis_label_text_font_size = "13px" - fig.yaxis.axis_label_text_font_size = "13px" - -def main(args): - channels = args.channels - linear_x = [k for k in range(1,len(args.masses)+1)] - edges_x = [k-0.5 for k in range(1,len(args.masses)+1)] + [len(args.masses)+0.5] - ptcuts = {chn: utils.get_ptcuts(chn, args.year) for chn in args.channels} - - nevents, errors = dd(lambda: dd(dict)), dd(lambda: dd(dict)) - ratios, eratios = dd(lambda: dd(dict)), dd(lambda: dd(dict)) - - for adir in main_dir: - dRstr = str(args.deltaR).replace('.', 'p') - if len(args.channels) == 1: - output_name = os.path.join(base_dir, 'trigger_gains_{}_{}_DR{}'.format(args.channels[0], - args.year, dRstr)) - elif len(args.channels) == 2: - output_name = os.path.join(base_dir, 'trigger_gains_{}_{}_{}_DR{}'.format(*args.channels[:2], - args.year, dRstr)) - elif len(args.channels) == 3: - output_name = os.path.join(base_dir, 'trigger_gains_all_{}_DR{}'.format(args.year, dRstr)) - if args.bigtau: - output_name += "_BIGTAU" - output_name += ".html" - output_file(output_name) - print('Saving file {}.'.format(output_name)) - - for chn in channels: - md = adir[chn] - in_base = os.path.join(base_dir, md) - - nevents[md][chn]['base'], nevents[md][chn]['met'], nevents[md][chn]['tau'] = [], [], [] - ratios[md][chn]['two'], ratios[md][chn]['met'], ratios[md][chn]['tau'] = [], [], [] - eratios[md][chn]['two'], eratios[md][chn]['met'], eratios[md][chn]['tau'] = [], [], [] - errors[md][chn] = [] - - for mass in args.masses: - outname = get_outname(mass, chn, [str(x) for x in args.region_cuts], - [str(x) for x in ptcuts[chn]], str(args.met_turnon), - args.bigtau) - - with open(outname, "rb") as f: - ahistos = pickle.load(f) - - # all regions summed - sum_base_tot = round(ahistos["Base"]["legacy"]["baseline"].values().sum() + - ahistos["Base"]["tau"]["baseline"].values().sum() + - ahistos["Base"]["met"]["baseline"].values().sum()) - - # legacy region - l1 = lambda x : round(x["legacy"]["baseline"].values().sum(), 2) - sum_base = l1(ahistos["Base"]) - sum_vbf = l1(ahistos["VBF"]) - sum_met = l1(ahistos["NoBaseMET"]) - sum_only_tau = l1(ahistos["NoBaseNoMETTau"]) - sum_tau = l1(ahistos["NoBaseTau"]) - sum_basekin = l1(ahistos["LegacyKin"]) - w2_basekin = ahistos["METKin"]["legacy"]["baseline"].variances().sum() - - # MET region - l2 = lambda x : round(x["met"]["baseline"].values().sum(), 2) - sum_metkin = l2(ahistos["METKin"]) - w2_metkin = ahistos["METKin"]["met"]["baseline"].variances().sum() - - # Single Tau region - l3 = lambda x : round(x["tau"]["baseline"].values().sum(), 2) - sum_taukin = l3(ahistos["TauKin"]) - w2_taukin = ahistos["TauKin"]["tau"]["baseline"].variances().sum() - - # hypothetical VBF region - sum_vbfkin = l2(ahistos["VBFKin"]) + l3(ahistos["VBFKin"]) - - nevents[md][chn]['base'].append(sum_basekin) - nevents[md][chn]['met'].append(sum_basekin + sum_metkin) - nevents[md][chn]['tau'].append(sum_basekin + sum_metkin + sum_taukin) - - rat_met_num = sum_basekin + sum_metkin - rat_met_all = rat_met_num / sum_base_tot - - rat_tau_num = sum_basekin + sum_taukin - rat_tau_all = rat_tau_num / sum_base_tot - - rat_all_num = sum_basekin + sum_taukin + sum_metkin - rat_all = rat_all_num / sum_base_tot - - ratios[md][chn]['met'].append(rat_met_all) - ratios[md][chn]['tau'].append(rat_tau_all) - ratios[md][chn]['two'].append(rat_all) - - e_metkin = np.sqrt(w2_metkin) - e_taukin = np.sqrt(w2_taukin) - e_basekin = np.sqrt(w2_basekin) - - e_tau_num = np.sqrt(w2_taukin + w2_basekin) - e_met_num = np.sqrt(w2_metkin + w2_basekin) - e_all_num = np.sqrt(w2_metkin + w2_taukin + w2_basekin) - - eratios[md][chn]['tau'].append(rat_tau_all * np.sqrt(e_tau_num**2/rat_tau_num**2 + 1/sum_base_tot)) - eratios[md][chn]['met'].append(rat_met_all * np.sqrt(e_met_num**2/rat_met_num**2 + 1/sum_base_tot)) - eratios[md][chn]['two'].append(rat_all * np.sqrt(e_all_num**2/rat_all_num**2 + 1/sum_base_tot)) - errors[md][chn].append(e_all_num) - - json_name = 'data_' + chn + '_' - json_name += ('bigtau' if args.bigtau else 'standard') + '.json' - with open(json_name, 'w', encoding='utf-8') as json_obj: - json_data = {"vals": {chn: nevents[adir[chn]][chn]['tau'] for chn in channels}} - json_data.update({"errs": {chn: errors[adir[chn]][chn] for chn in channels}}) - json.dump(json_data, json_obj, ensure_ascii=False, indent=4) - - opt_points = dict(size=8) - opt_line = dict(width=1.5) - colors = ('green', 'blue', 'red', 'brown') - styles = ('solid', 'dashed', 'dotdash') - legends = {'base': 'Legacy', - 'met': 'MET', 'tau': 'Single Tau', - 'two': 'MET + Single Tau', 'vbf': 'VBF'} - - x_str = [str(k) for k in args.masses] - xticks = linear_x[:] - yticks = [x for x in range(0,110,5)] - shift_one = {'met': [-0.15, 0., 0.15], 'tau': [-0.20, -0.05, 0.1], - 'vbf': [-0.10, 0.05, 0.20]} - shift_both = {'met': [-0.15, 0., 0.15], 'two': [-0.20, -0.05, 0.1]} - shift_kin = {'met': [-0.09, 0., 0.15], 'tau': [0.03, -0.05, 0.1], - 'two': [-0.03, 0.05, 0.20], 'vbf': [0.09, 0.1, 0.25]} - - for adir in main_dir: - p_opt = dict(width=800, height=400, x_axis_label='x', y_axis_label='y') - p1 = figure(title='Event number (' + pp(channels[0]) + ')', y_axis_type="linear", **p_opt) - p2 = figure(title='Acceptance Gain (' + pp(channels[0]) + ')', **p_opt) if len(channels)==1 else figure(**p_opt) - - p1.yaxis.axis_label = 'Weighted number of events' - p2.yaxis.axis_label = 'Trigger acceptance gain (w.r.t. trigger baseline) [%]' - pics = (p1, p2) - for p in pics: - set_fig(p) - - for ichn,chn in enumerate(channels): - md = adir[chn] - - p1.quad(top=nevents[md][chn]["base"], bottom=0, - left=edges_x[:-1], right=edges_x[1:], - legend_label=legends["base"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), - fill_color="dodgerblue", line_color="black") - p1.quad(top=nevents[md][chn]["met"], bottom=nevents[md][chn]["base"], - left=edges_x[:-1], right=edges_x[1:], - legend_label=legends["met"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), - fill_color="green", line_color="black") - p1.quad(top=nevents[md][chn]["tau"], bottom=nevents[md][chn]["met"], - left=edges_x[:-1], right=edges_x[1:], - legend_label=legends["tau"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), - fill_color="red", line_color="black") - - for itd,td in enumerate(('met', 'tau', 'two')): - p2.circle([x+shift_kin[td][ichn] for x in linear_x], - [(x-1)*100. for x in ratios[md][chn][td]], - color=colors[itd], fill_alpha=1., **opt_points) - p2.line([x+shift_kin[td][ichn] for x in linear_x], - [(x-1)*100. for x in ratios[md][chn][td]], - color=colors[itd], line_dash=styles[ichn], - legend_label=legends[td]+(' ('+pp(chn)+')' if len(channels)>1 else ''), **opt_line) - p2.multi_line( - [(x+shift_kin[td][ichn],x+shift_kin[td][ichn]) for x in linear_x], - [((y-1)*100-(x*50.),(y-1)*100+(x*50.)) for x,y in zip(eratios[md][chn][td],ratios[md][chn][td])], - color=colors[itd], **opt_line) - - p1.legend.location = 'top_right' - p2.legend.location = 'top_left' - for p in pics: - p.xaxis[0].ticker = xticks - p.xgrid[0].ticker = xticks - p.xgrid.grid_line_alpha = 0.2 - p.xgrid.grid_line_color = 'black' - # p.yaxis[0].ticker = yticks - # p.ygrid[0].ticker = yticks - p.ygrid.grid_line_alpha = 0.2 - p.ygrid.grid_line_color = 'black' - - p.xaxis.axis_label = "m(X) [GeV]" - - p.xaxis.major_label_overrides = dict(zip(linear_x,x_str)) - - p.legend.click_policy='hide' - - p.output_backend = 'svg' - #export_svg(p, filename='line_graph.svg') - - g = gridplot([[p] for p in pics]) - save(g, title=md) - -if __name__ == '__main__': - desc = "Produce plots of trigger gain VS resonance mass.\n" - desc += "Uses the output of test_trigger_regions.py." - desc += "When running on many channels, one should keep in mind each channel has different pT cuts." - desc += "This might imply moving sub-folders (produced by the previous script) around." - parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) - - parser.add_argument('--masses', required=True, nargs='+', type=str, - help='Resonance mass') - parser.add_argument('--channels', required=True, nargs='+', type=str, - choices=('etau', 'mutau', 'tautau'), - help='Select the channel over which the workflow will be run.' ) - parser.add_argument('--year', required=True, type=str, choices=('2016', '2017', '2018'), - help='Select the year over which the workflow will be run.' ) - parser.add_argument('--deltaR', type=float, default=0.5, help='DeltaR between the two leptons.') - parser.add_argument('--bigtau', action='store_true', - help='Consider a larger single tau region, reducing the ditau one.') - parser.add_argument('--met_turnon', required=False, type=str, default=180, - help='MET trigger turnon cut [GeV].' ) - parser.add_argument('--region_cuts', required=False, type=float, nargs=2, default=(190., 190.), - help='High/low regions pT1 and pT2 selection cuts [GeV].' ) - - args = utils.parse_args(parser) - - base_dir = '/eos/home-b/bfontana/www/TriggerScaleFactors/' - main_dir = [{"etau": "Region_Spin2_190_190_PT_33_25_35_DR_{}_TURNON_200_190".format(args.deltaR), - "mutau": "Region_Spin2_190_190_PT_25_21_32_DR_{}_TURNON_200_190".format(args.deltaR), - "tautau": "Region_Spin2_190_190_PT_40_40_DR_{}_TURNON_200_190".format(args.deltaR)}, - ] - - main(args) diff --git a/tests/test_trigger_regions.py b/tests/test_trigger_regions.py deleted file mode 100644 index b1ed2c9..0000000 --- a/tests/test_trigger_regions.py +++ /dev/null @@ -1,710 +0,0 @@ -# coding: utf-8 - -_all_ = [ 'test_trigger_regions' ] - -import os -import sys -parent_dir = os.path.abspath(__file__ + 2 * '/..') -sys.path.insert(0, parent_dir) -import argparse -import glob -import multiprocessing -import itertools as it -import csv -import numpy as np -import h5py -from collections import defaultdict as dd -import importlib - -import inclusion -from inclusion import selection -from inclusion.config import main -from inclusion.utils import utils - -import ROOT -import hist -import pickle - -from bokeh.plotting import figure, output_file, save -from bokeh.models import Range1d, Label - -tau = '\u03C4' -mu = '\u03BC' -pm = '\u00B1' -ditau = tau+tau - -def contamination_save(savepath, label, c1, c2, e1, e2, m, mode='w'): - with h5py.File(os.path.join(savepath, label + '.hdf5'), mode) as f: - dset = f.create_dataset(label, (5,len(m)), dtype='f') - dset[0, :] = m - dset[1, :] = c1 - dset[2, :] = c2 - dset[3, :] = e1 - dset[4, :] = e2 - dset.cols = ['mass [GeV]', - 'contamination ' + ditau + '(%)', - 'contamination ' + ditau + ' MET (%)', - 'uncertainty' + ditau, - 'uncertainty' + ditau + ' MET'] - -def stats_save(savepath, label, c1, e1, m, mode='w'): - with h5py.File(os.path.join(savepath, label + '.hdf5'), mode) as f: - dset = f.create_dataset(label+'_stats', (3,len(m)), dtype='f') - dset[0, :] = m - dset[1, :] = c1 - dset[2, :] = e1 - dset.cols = ['mass [GeV]', - 'stats', - 'uncertainty'] - -def rec_dd(): - return dd(rec_dd) - -def square_diagram(c_legacy_trg, c_met_trg, c_tau_trg, channel, - ptcuts, text, notext=False, bigtau=False): - base = {'etau': 'e+e'+tau, 'mutau': mu+'+'+mu+tau, 'tautau': ditau} - output_file(text['out']) - print('Saving file {}'.format(text['out'])) - - topr = 9.9 - shft = 0.1 - start, b1, b2, b3 = 0.0, 2, 2.5, 7.5 - xgap = b1+shft# else b2+shft - - p = figure(title='m(X)={}GeV'.format(text['mass']), width=600, height=400, - tools='save') - p.x_range = Range1d(0, 11.5) - p.y_range = Range1d(0, 10) - p.outline_line_color = None - p.toolbar.logo = None - p.xgrid.grid_line_color = None - p.ygrid.grid_line_color = None - p.xaxis.ticker = [b1,b3]#else [b1, b2, b3] - p.xaxis.major_label_overrides = ({b1: str(ptcuts[0]), b3: str(regcuts[0])}) - - for aaxis in (p.xaxis, p.yaxis): - aaxis.axis_label_text_font_size = "12pt" - aaxis.axis_label_text_font_style = "normal" - aaxis.major_label_text_font_size = "11pt" - - p.yaxis.axis_label_standoff = 0 - p.yaxis.ticker = [b1, b3]# else [b1, b2, b3] - if len(ptcuts) > 1: - p.yaxis.major_label_overrides = ({b1: str(ptcuts[0]), b3: str(regcuts[1])}) - else: - p.yaxis.major_label_overrides = ({b1: str(ptcuts[0])}) - - p.xaxis.axis_label = r'\(p_T(\tau_1) [GeV]\)' - p.yaxis.axis_label = r'\(p_T(\tau_2) [GeV]\)' - - # add a square renderer with a size, color, and alpha - polyg_opt = dict(alpha=0.3) - p.multi_polygons(color='green', - xs=[[[[xgap,b3-shft,b3-shft,xgap]]]] if bigtau else [[[[xgap,topr,topr,xgap]]]], - ys=[[[[b3-shft,b3-shft,xgap,xgap]]]] if bigtau else [[[[topr,topr,xgap,xgap]]]], - legend_label=base[channel], **polyg_opt) - - p.legend.title = 'Regions' - p.legend.title_text_font_style = 'bold' - p.legend.border_line_color = None - p.legend.background_fill_color = 'white' - p.legend.click_policy = 'hide' - - label_opt = dict(x_units='data', y_units='data', text_font_size='10pt') - - gain = (100*float(c_met_trg['legacy']+c_tau_trg['legacy']) / - (c_legacy_trg['legacy']+c_met_trg['legacy']+c_tau_trg['legacy'])) - gain = str(round(gain,2)) - if not notext: - stats_ditau = {'legacy': Label(x=b1+0.3, y=b1+1.5, text=ditau+': '+str(c_legacy_trg['legacy']), - text_color='black', **label_opt), - 'met': Label(x=b1+0.3, y=b1+1.1, text='met && !'+ditau+': '+str(c_met_trg['legacy']), - text_color='black', **label_opt), - 'tau': Label(x=b1+0.3, y=b1+0.7, text=tau+' && !met && !'+ditau+': '+str(c_tau_trg['legacy']), - text_color='black', **label_opt), - 'gain': Label(x=b1+0.3, y=b1+0.3, text='Gain: '+gain+'%', - text_color='blue', **label_opt),} - for key,elem in stats_ditau.items(): - p.add_layout(elem) - - - try: - contam_by_tau = (100*(float(c_tau_trg['met'])+c_legacy_trg['met']) / - (c_met_trg['met']+c_tau_trg['met']+c_legacy_trg['met'])) - contam_by_tau = str(round(contam_by_tau,2)) - except ZeroDivisionError: - contam_by_tau = '0' - if not notext: - stats_met = {'met': Label(x=b1+0.3, y=1.3, text='met: '+str(c_met_trg['met']), - text_color='black', **label_opt), - 'tau': Label(x=b1+0.3, y=0.5, text=tau+' && !'+ditau+' && !met: '+str(c_tau_trg['met']), - text_color='black', **label_opt), - 'legacy': Label(x=b1+0.3, y=0.9, text=ditau+' && !met: '+str(c_legacy_trg['met']), - text_color='black', **label_opt), - 'contamination': Label(x=b1+0.3, y=0.1, text='Contam.: '+contam_by_tau+'%', - text_color='blue', **label_opt),} - for key,elem in stats_met.items(): - if key != 'contamination': - p.add_layout(elem) - - - num_ditau = float(c_legacy_trg['tau']) - num_both = num_ditau + float(c_met_trg['tau']) - den = float(c_met_trg['tau']+c_tau_trg['tau']+c_legacy_trg['tau']) - if num_ditau == 0 or num_both == 0: - contam_ditau = '0' - contam_both = '0' - err_ditau = '0' - err_both = '0' - else: - contam_ditau = 100*num_ditau/den - contam_both = 100*num_both/den - - enum_ditau = np.sqrt(c_legacy_trg['tau']) - eden_ditau = enum_ditau + np.sqrt(c_met_trg['tau']) + np.sqrt(c_tau_trg['tau']) - enum_both = np.sqrt(c_met_trg['tau']) + np.sqrt(c_legacy_trg['tau']) - eden_both = enum_both + np.sqrt(c_tau_trg['tau']) - err_ditau = contam_ditau * np.sqrt(enum_ditau**2/num_ditau**2 + eden_ditau**2/den**2) - err_both = contam_both * np.sqrt(enum_both**2/num_both**2 + eden_both**2/den**2) - - contam_ditau = str(round(contam_ditau,2)) - contam_both = str(round(contam_both,2)) - err_ditau = str(round(err_ditau,2)) - err_both = str(round(err_both,2)) - - if not notext: - stats_tau = {'tau': Label(x=b3+0.2, y=1.3, text=tau+': '+str(c_tau_trg['tau']), - text_color='black', **label_opt), - 'met': Label(x=b3+0.2, y=0.5, text='met && !'+ditau+' && !'+tau+': '+str(c_met_trg['tau']), - text_color='black', **label_opt), - 'legacy': Label(x=b3+0.2, y=0.9, text=ditau+' && !'+tau+': '+str(c_legacy_trg['tau']), - text_color='black', **label_opt), - 'contamination_both': Label(x=b3+0.2, y=0.1, - text='Contam.: ('+str(contam_both)+pm+str(err_both)+')%', - text_color='blue', **label_opt),} - for key,elem in stats_tau.items(): - if key != 'contamination_both': - p.add_layout(elem) - - line_opt = dict(color='black', line_dash='dashed', line_width=2) - p.line(x=[b1,b1], y=[start, topr+shft], **line_opt) - p.line(x=[start,topr+shft], y=[b1,b1], **line_opt) - p.line(x=[b3,b3], y=[start,topr+shft], **line_opt) - p.line(x=[start,topr+shft], y=[b3,b3], **line_opt) - - p.output_backend = 'svg' - save(p) - return contam_ditau, contam_both, err_ditau, err_both - -def get_outname(sample, channel, regcuts, ptcuts, met_turnon, bigtau): - utils.create_single_dir('data') - - name = sample + '_' + channel + '_' #*map(str, regcuts) - name += '_'.join((*[str(x) for x in regcuts], 'ptcuts', - *[str(x) for x in ptcuts], 'turnon', str(met_turnon))) - if bigtau: - name += '_BIGTAU' - name += '.pkl' - - s = 'data/regions_{}'.format(name) - return s - -def set_plot_definitions(): - ROOT.gROOT.SetBatch(ROOT.kTRUE) - ROOT.gStyle.SetOptStat(ROOT.kFALSE) - ret = {'XTitleSize' : 0.045, - 'YTitleSize' : 0.045, - 'LineWidth' : 2, - 'FrameLineWidth' : 1, - } - return ret - -def plot(hbase, hmet, htau, var, channel, sample, region, category, directory): - defs = set_plot_definitions() - - hbase2 = hbase.Clone('hbase2') - hmet2 = hmet.Clone('hmet2') - htau2 = htau.Clone('htau2') - - # Normalized shapes - c2 = ROOT.TCanvas('c2', '', 600, 400) - c2.cd() - try: - hbase2.Scale(1/hbase2.Integral()) - except ZeroDivisionError: - hbase2.Scale(0.) - try: - hmet2.Scale(1/hmet2.Integral()) - except ZeroDivisionError: - hmet2.Scale(0.) - try: - htau2.Scale(1/htau2.Integral()) - except ZeroDivisionError: - htau2.Scale(0.) - - shift_scale = 5. - amax = hbase2.GetMaximum() + (hbase2.GetMaximum()-hbase2.GetMinimum()) / shift_scale - amax = max(amax, hmet2.GetMaximum() + (hmet2.GetMaximum()-hmet2.GetMinimum()) / shift_scale) - amax = max(amax, htau2.GetMaximum() + (htau2.GetMaximum()-htau2.GetMinimum()) / shift_scale) - hbase2.SetMaximum(amax) - - hbase2.GetXaxis().SetTitleSize(defs['XTitleSize']); - hbase2.GetXaxis().SetTitle(var + ' [GeV]'); - hbase2.GetYaxis().SetTitleSize(defs['YTitleSize']); - hbase2.GetYaxis().SetTitle('a. u.'); - hbase2.SetLineWidth(defs['LineWidth']); - hbase2.SetLineColor(4); - - hmet2.SetLineWidth(defs['LineWidth']); - hmet2.SetLineColor(8); - - htau2.SetLineWidth(defs['LineWidth']); - htau2.SetLineColor(2); - - hbase2.Draw('hist') - hmet2.Draw('histsame') - htau2.Draw('histsame') - - leg2 = ROOT.TLegend(0.69, 0.77, 0.90, 0.9) - leg2.SetNColumns(1) - leg2.SetFillStyle(0) - leg2.SetBorderSize(0) - leg2.SetTextFont(43) - leg2.SetTextSize(10) - leg2.AddEntry(hbase2, 'legacy') - leg2.AddEntry(hmet2, '!legacy && MET') - leg2.AddEntry(htau2, '!legacy && !MET && tau') - - leg2.Draw('same') - - c2.Update(); - cat_dir = os.path.join(directory, sample, category) - utils.create_single_dir(cat_dir) - for ext in extensions: - c2.SaveAs( os.path.join(cat_dir, 'norm_' + region + '_' + var + '.' + ext) ) - c2.Close() - -def plot2D(histo, two_vars, channel, sample, trigger_str, category, directory, region, norm=False): - histo = histo.Clone('histo_clone') - defs = set_plot_definitions() - - c = ROOT.TCanvas('c', '', 800, 800) - c.cd() - - pad1 = ROOT.TPad('pad1', 'pad1', 0., 0., 1., 1.) - pad1.SetFrameLineWidth(defs['FrameLineWidth']) - pad1.SetLeftMargin(0.12); - pad1.SetRightMargin(0.14); - pad1.SetBottomMargin(0.12); - pad1.SetTopMargin(0.1); - pad1.Draw() - pad1.cd() - - ROOT.gStyle.SetTitleX(0.44) #title X location - ROOT.gStyle.SetTitleY(1.015) #title Y location - ROOT.gStyle.SetTitleW(0.7) #title width - ROOT.gStyle.SetTitleH(0.15) #title height - ROOT.gStyle.SetTitleColor(ROOT.kBlue, 't') - histo.SetTitle('Region: ' + region +' | Triggers: ' + trigger_str); - histo.SetTitleSize(0.02, 't') - histo.GetXaxis().SetTitle(two_vars[0]) - histo.GetXaxis().SetTitleSize(0.04) - histo.GetYaxis().SetTitle(two_vars[1]) - histo.GetYaxis().SetTitleSize(0.04) - - if norm: - try: - histo.Scale(1/histo.Integral()) - except ZeroDivisionError: - histo.Scale(0.) - histo.Draw('colz') - - c.cd() - cat_folder = os.path.join(directory, sample, category) - utils.create_single_dir(cat_folder) - c.Update(); - tstr = trigger_str.replace(' ', '_').replace('+', '_PLUS_').replace('!', 'NOT') - for ext in extensions: - c.SaveAs(os.path.join(cat_folder, - tstr + '_' + 'reg' + region + '_' + '_VS_'.join(two_vars) + '.' + ext)) - c.Close() - -def which_region(ent, year, ptcuts, regcuts, channel, met_turnon, bigtau=False): - """ - Select one of the threee non-overlapping regions: leg(acy), met and tau - - ptcuts: pT cuts, delimiting the three regions based on the HLT pT thresholds - - regcuts: region cuts, delimiting the three regions in the pT space - Example in tautau: - - ptcuts = (40, 40) - - regcuts = (190, 190) - - Eta cuts are defined with different strategies: - - tautau: eta trigger cut at 2.1, so that the MET trigger can take advantage of high-eta events for the legacy region - - e/mutau: eta cuts correspond to the analysis selection - Since different triggers in the etau and mutau channels can require different eta thresholds - (eg: single-muon has no threshold and cross-muon-tau has one at 2.1), a fully correct approach - would have to apply different cuts depending on the fired trigger bit. To avoid - this extra complication, which would very likely bring a negligible signal acceptance improvement, - the eta cuts applied correspond to the object selection in the analysis. - """ - eta1_sel = {"etau": abs(ent.dau1_eta) <= 2.5, "mutau": abs(ent.dau1_eta) <= 2.4} - eta1_trg = abs(ent.dau1_eta) <= 2.1 - eta2_trg = abs(ent.dau2_eta) <= 2.1 - - if bigtau: - if channel == "tautau": - tau = (ent.dau1_pt >= regcuts[0] and eta1_trg) or (ent.dau2_pt >= regcuts[1] and eta2_trg) - leg = ent.dau1_pt >= ptcuts[0] and ent.dau2_pt >= ptcuts[1] and eta1_trg and eta2_trg and not tau - - elif channel == "etau" and year == "2016": - tau = ent.dau2_pt >= regcuts[1] and eta2_trg - leg = ent.dau1_pt >= ptcuts[0] and eta1_trg and not tau - - else: #mutau or etau non-2016 - single_lepton_validity = ent.dau1_pt >= ptcuts[0] and eta1_sel[channel] - cross_lepton_validity = ent.dau1_pt >= ptcuts[1] and eta1_trg and ent.dau2_pt >= ptcuts[2] and eta2_trg - - tau = ent.dau2_pt >= regcuts[1] and eta2_trg - leg = (single_lepton_validity or cross_lepton_validity) and not tau - - else: - if channel == "tautau": - leg = ent.dau1_pt >= ptcuts[0] and ent.dau2_pt >= ptcuts[1] and eta1_trg and eta2_trg - tau = ((ent.dau1_pt >= regcuts[0] and eta1_trg) or (ent.dau2_pt >= regcuts[1] and eta2_trg)) and not leg - - elif channel == "etau" and year == "2016": - leg = ent.dau1_pt >= ptcuts[0] and eta1_trg - tau = ent.dau2_pt >= regcuts[1] and eta2_trg and not leg - - else: #mutau or etau non-2016 - single_lepton_validity = ent.dau1_pt >= ptcuts[0] and eta1_sel[channel] - cross_lepton_validity = ent.dau1_pt >= ptcuts[1] and eta1_trg and ent.dau2_pt >= ptcuts[2] and eta2_trg - single_tau_validity = ent.dau2_pt >= regcuts[1] and eta2_trg - - leg = single_lepton_validity or cross_lepton_validity - tau = single_tau_validity and not leg - - met = ent.metnomu_et > met_turnon and not leg and not tau - - # only one True: non-overlapping regions - assert int(leg)+int(met)+int(tau)<=1 - - return leg, met, tau - - -def test_trigger_regions(indir, sample, channel, spin, year, deltaR): - outname = get_outname(sample, channel, regcuts, ptcuts, met_turnon, - args.bigtau) - config_module = importlib.import_module(args.configuration) - if channel == 'etau' or channel == 'mutau': - iso1 = (24, 0, 8) - elif channel == 'tautau': - iso1 = (20, 0.965, 1.005) - binning.update({'HHKin_mass': (20, float(sample)-300, float(sample)+300), - 'dau1_iso': iso1}) - - full_sample = {0: 'GluGluToRadionToHHTo2B2Tau_M-' + sample + '_', - 2: 'GluGluToBulkGravitonToHHTo2B2Tau_M-' + sample + '_'}[spin] - - norphans, ntotal = ({k:0 for k in categories} for _ in range(2)) - - ahistos = rec_dd() - for reg in regions: - for cat in categories: - for htype in htypes: - ahistos[htype][reg][cat] = ( - hist.Hist.new.Regular(*binning["dau1_pt"], name="dau1pt") - .Regular(*binning["dau2_pt"], name="dau2pt") - .Weight() - ) - - t_in = ROOT.TChain('HTauTauTree') - glob_files = glob.glob( os.path.join(indir, full_sample, 'output_*.root') ) - if len(glob_files) < 1: - raise RuntimeError("No files!") - for f in glob_files: - t_in.Add(f) - t_in.SetBranchStatus('*', 0) - _entries = utils.define_used_tree_variables(cut=config_module.custom_cut) - _entries += tuple(variables) - for ientry in _entries: - t_in.SetBranchStatus(ientry, 1) - - for entry in t_in: - # this is slow: do it once only - entries = utils.dot_dict({x: getattr(entry, x) for x in _entries}) - - in_legacy, in_met, in_tau = which_region(entries, year, ptcuts, regcuts, channel, met_turnon, - bigtau=args.bigtau) - - sel = selection.EventSelection(entries, isdata=False, configuration=config_module) - - pass_trg = sel.pass_triggers(triggers[channel]) and entries.isLeptrigger - pass_met = sel.pass_triggers(('METNoMu120',)) - pass_tau = sel.pass_triggers(('IsoTau180',)) - - cuts = {'Base' : pass_trg, - 'MET' : pass_met, - 'Tau' : pass_tau, - 'VBF' : False, - 'BaseMET' : pass_trg and pass_met, - 'BaseTau' : pass_trg and pass_tau, - 'METTau' : pass_met and pass_tau, - 'NoBaseMET' : not pass_trg and pass_met, - 'BaseNoMET' : pass_trg and not pass_met, - 'NoBaseNoMETTau' : not pass_trg and not pass_met, - 'NoBaseTau' : not pass_trg and pass_tau, - 'BaseNoTau' : pass_trg and not pass_tau, - 'NoBaseMETNoTau' : not pass_trg and pass_met and not pass_tau, - 'BaseMETTau' : pass_trg and pass_met and pass_tau, - 'BaseNoMETNoTau' : pass_trg and not pass_met and not pass_tau, - 'LegacyKin' : pass_trg and in_legacy, - 'METKin' : pass_met and in_met, - 'TauKin' : pass_tau and in_tau, - 'VBFKin' : False - } - assert htypes == list(cuts.keys()) - - w_mc = entries.MC_weight - w_pure = entries.PUReweight - w_l1pref = entries.L1pref_weight - w_trig = entries.trigSF - w_idiso = entries.IdSF_deep_2d - w_jetpu = entries.PUjetID_SF - # w_btag = entries.bTagweightReshape - - if utils.is_nan(w_mc) : w_mc=1 - if utils.is_nan(w_pure) : w_pure=1 - if utils.is_nan(w_l1pref) : w_l1pref=1 - if utils.is_nan(w_trig) : w_trig=1 - if utils.is_nan(w_idiso) : w_idiso=1 - if utils.is_nan(w_jetpu) : w_jetpu=1 - # if utils.is_nan(w_btag) : w_btag=1 - - evt_weight = w_mc * w_pure * w_l1pref * w_trig * w_idiso * w_jetpu # * w_btag - - tau_gen_cut = {"etau": None, "mutau": None, - "tautau": 'self.entries["isTau1real"] == 1 and self.entries["isTau2real"] == 1'} - if utils.is_channel_consistent(channel, entries.pairType): - if not sel.selection_cuts(lepton_veto=True, bjets_cut=True, - mass_cut=config_module.mass_cut, - custom_cut=tau_gen_cut[channel]): - continue - - for cat in categories: - if sel.sel_category(cat) and entries.ditau_deltaR > deltaR: - ntotal[cat] += 1 - - if in_met: - reg = 'met' - elif in_tau: - reg = 'tau' - elif in_legacy: - reg = 'legacy' - else: - norphans[cat] += 1 - continue - assert reg in regions - - for key,cut in cuts.items(): - if cut: - ahistos[key][reg][cat].fill(dau1pt=entries["dau1_pt"], - dau2pt=entries["dau2_pt"], weight=evt_weight) - - # all MC and signal must be rescaled to get the correct number of events - for key,_ in cuts.items(): - for reg in regions: - for cat in categories: - ahistos[key][reg][cat] *= (utils.get_lumi(args.year) / - utils.total_sum_weights(glob_files[0], isdata=False)) - - with open(outname, "wb") as f: - pickle.dump(ahistos, f) - - for cat in categories: - orph_frac = float(norphans[cat])/ntotal[cat] - if orph_frac > 0.1: - print('{}% orphans ({}/{}). This is unusual.'.format(orph_frac, norphans[cat], ntotal[cat])) - print(met_region) - print(tau_region) - print(legacy_region) - print('Category {} (m(X)={}GeV) had {} orphans ({}%)'.format(cat, sample, norphans[cat], orph_frac)) - print('Raw histograms saved in {}.'.format(outname), flush=True) - -if __name__ == '__main__': - extensions = ('png',) #('png', 'pdf') - triggers = {'etau': ('Ele32', 'EleIsoTauCustom'), - 'mutau': ('IsoMu24', 'IsoMuIsoTauCustom'), - 'tautau': ('IsoDoubleTauCustom',) - } - binning = {'metnomu_et': (20, 0, 450), - 'dau1_pt': (30, 0, 450), - 'dau1_eta': (20, -2.5, 2.5), - 'dau2_iso': (20, 0.88, 1.005), - 'dau2_pt': (30, 0, 400), - 'dau2_eta': (20, -2.5, 2.5), - 'ditau_deltaR': (30, 0.3, 1.3), - 'dib_deltaR': (25, 0, 2.5), - 'bH_pt': (20, 70, 600), - 'bH_mass': (30, 0, 280), - 'tauH_mass': (30, 0, 170), - 'tauH_pt': (30, 0, 500), - 'tauH_SVFIT_mass': (30, 0, 250), - 'tauH_SVFIT_pt': (20, 200, 650), - 'bjet1_pt': (25, 10, 600), - 'bjet2_pt': (25, 10, 550), - 'bjet1_eta': (20, -2.5, 2.5), - 'bjet2_eta': (20, -2.5, 2.5), - } - variables = tuple(binning.keys()) + ('HHKin_mass', 'dau1_iso') - - categories = ('baseline',) #('baseline', 's1b1jresolvedMcut', 's2b0jresolvedMcut', 'sboostedLLMcut') - - htypes = ['Base', 'MET', 'Tau', 'VBF', 'BaseMET', 'BaseTau', 'METTau', 'NoBaseMET', 'BaseNoMET', - 'NoBaseNoMETTau', 'NoBaseTau', 'BaseNoTau', 'NoBaseMETNoTau', 'BaseMETTau', 'BaseNoMETNoTau', - 'LegacyKin', 'METKin', 'TauKin', 'VBFKin'] - - # Parse input arguments - desc = 'Producer trigger histograms.\n' - desc += "Run example: python tests/test_trigger_regions.py --indir /data_CMS/cms/alves/HHresonant_SKIMS/SKIMS_UL18_EOSv4_Signal/ --masses 400 500 600 700 800 900 1000 1250 1500 --channels ETau --met_turnon 180 --region_cuts 40 40 --copy" - parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) - - parser.add_argument('--indir', required=True, type=str, - help='Full path of ROOT input file') - parser.add_argument('--masses', required=True, nargs='+', type=str, - help='Resonance mass') - parser.add_argument('--channel', required=True, type=str, - help='Select the channel over which the workflow will be run.' ) - parser.add_argument('--year', required=True, type=str, choices=('2016', '2016APV', '2017', '2018'), - help='Select the year over which the workflow will be run.' ) - parser.add_argument('--spin', required=True, type=int, choices=(0, 2), - help='Select the spin hypothesis over which the workflow will be run.' ) - parser.add_argument('--deltaR', type=float, default=0.5, help='DeltaR between the two leptons.' ) - parser.add_argument('--plot', action='store_true', - help='Reuse previously produced data for quick plot changes.') - parser.add_argument('--copy', action='store_true', - help='Copy the outputs to EOS at the end.') - parser.add_argument('--notext', action='store_true', help='Square diagram without text.') - parser.add_argument('--sequential', action='store_true', - help='Do not use the multiprocess package.') - parser.add_argument('--bigtau', action='store_true', - help='Consider a larger single tau region, reducing the ditau one.') - parser.add_argument('--met_turnon', type=float, default=180, - help='MET trigger turnon cut [GeV].' ) - parser.add_argument('--region_cuts', required=False, type=float, nargs=2, default=(190, 190), - help='High/low regions pT1 and pT2 selection cuts [GeV].' ) - parser.add_argument('--configuration', dest='configuration', required=True, - help='Name of the configuration module to use.') - args = utils.parse_args(parser) - - met_turnon = args.met_turnon - regcuts = args.region_cuts - ptcuts = utils.get_ptcuts(args.channel, args.year) - - main_dir = os.path.join('/eos/home-b/bfontana/www/TriggerScaleFactors/', - '_'.join(('Spin' + str(args.spin), args.channel, *[str(x) for x in regcuts], - 'DR', str(args.deltaR), 'PT', *[str(x) for x in ptcuts], 'TURNON', - str(met_turnon)))) - if args.bigtau: - main_dir += '_BIGTAU' - - regions = ('legacy', 'met', 'tau') - - #### run main function ### - if not args.plot: - if args.sequential: - for sample in args.masses: - test_trigger_regions(args.indir, sample, args.channel, args.spin, args.year, args.deltaR) - else: - pool = multiprocessing.Pool(processes=6) - pool.starmap(test_trigger_regions, - zip(it.repeat(args.indir), args.masses, it.repeat(args.channel), - it.repeat(args.spin), it.repeat(args.year), it.repeat(args.deltaR))) - - ########################### - - sum_stats, err_sum_stats = ([] for _ in range(2)) - contam1, contam2, contam1_errors, contam2_errors = ([] for _ in range(4)) - from_directory = os.path.join(main_dir, args.channel) - for sample in args.masses: - outname = get_outname(sample, args.channel, - (str(x) for x in regcuts), (str(x) for x in ptcuts), - str(met_turnon), args.bigtau) - with open(outname, "rb") as f: - ahistos = pickle.load(f) - - # write csv header, one per category - out_counts = [] - for cat in categories: - out_counts.append( os.path.join(from_directory, sample, cat, 'counts') ) - utils.create_single_dir(out_counts[-1]) - with open(os.path.join(out_counts[-1], 'table.csv'), 'w') as f: - reader = csv.writer(f, delimiter=',', quotechar='|') - header_row = ['Region'] - header_row.extend(htypes) - reader.writerow(header_row) - - # plot histograms and fill CSV with histogram integrals - c_legacy_trg, c_met_trg, c_tau_trg = ({} for _ in range(3)) - - acounts = rec_dd() - for reg in regions: - for key,cut in ahistos.items(): - acounts[key][reg] = round(ahistos[key][reg]["baseline"].values().sum(), 2) - - # append to table, one line per region - with open(os.path.join(out_counts[categories.index(cat)], 'table.csv'), 'a') as f: - reader = csv.writer(f, delimiter=',', quotechar='|') - row = [reg] - row.extend([acounts[k][reg] for k in acounts.keys()]) - reader.writerow(row) - - if reg=='legacy': - c_legacy_trg[reg] = acounts["Base"][reg] - c_met_trg[reg] = acounts["NoBaseMET"][reg] - c_tau_trg[reg] = acounts["NoBaseNoMETTau"][reg] - elif reg=='met': - c_legacy_trg[reg] = acounts["BaseNoMET"][reg] - c_met_trg[reg] = acounts["MET"][reg] - c_tau_trg[reg] = acounts["NoBaseNoMETTau"][reg] - elif reg=='tau': - c_legacy_trg[reg] = acounts["BaseNoTau"][reg] - c_met_trg[reg] = acounts["NoBaseMETNoTau"][reg] - c_tau_trg[reg] = acounts["Tau"][reg] - - text = {'mass': sample, - 'out': os.path.join(out_counts[categories.index(cat)], 'diagram.html')} - sq_res = square_diagram(c_legacy_trg, c_met_trg, c_tau_trg, args.channel, - [str(x) for x in ptcuts], text=text, notext=args.notext, - bigtau=args.bigtau) - c1, c2, e1, e2 = sq_res - contam1.append(c1) - contam2.append(c2) - contam1_errors.append(e1) - contam2_errors.append(e2) - - stats_l = [c_legacy_trg['legacy'],c_met_trg['legacy'],c_tau_trg['tau'],c_legacy_trg['tau']] - stats = sum(stats_l) - estats = sum(np.sqrt(stats_l)) - sum_stats.append(stats) - err_sum_stats.append(estats) - - contam1 = [float(x) for x in contam1] - contam2 = [float(x) for x in contam2] - contam1_errors = [float(x) for x in contam1_errors] - contam2_errors = [float(x) for x in contam2_errors] - masses = [float(x) for x in args.masses] - contamination_save('data', '_'.join([str(x) for x in regcuts]) + '_' + args.channel, - contam1, contam2, contam1_errors, contam2_errors, masses) - stats_save('data', '_'.join([str(x) for x in regcuts]) + '_' + args.channel, - sum_stats, err_sum_stats, masses, mode='a') - - if args.copy: - import subprocess - to_directory = os.path.join('/eos/home-b/bfontana/www/TriggerScaleFactors', main_dir) - to_directory = os.path.join(to_directory, args.channel) - - for sample in args.masses: - sample_from = os.path.join(from_directory, sample) - print('Copying: {}\t\t--->\t{}'.format(sample_from, to_directory), flush=True) - subprocess.run(['rsync', '-ah', sample_from, to_directory]) diff --git a/tests/test_trigger_stats.py b/tests/test_trigger_stats.py deleted file mode 100644 index ff69919..0000000 --- a/tests/test_trigger_stats.py +++ /dev/null @@ -1,796 +0,0 @@ -# coding: utf-8 - -_all_ = [ 'test_trigger_stats' ] - -import os -import sys -parent_dir = os.path.abspath(__file__ + 2 * '/..') -sys.path.insert(0, parent_dir) -import argparse -import glob -import multiprocessing -import itertools as it -from collections import defaultdict - -import inclusion -from inclusion import selection -from inclusion.config import main -from inclusion.utils import utils - -import ROOT - -def get_outname(suffix, mode, cut, ext): - pref = {'met': 'MET', 'tau': 'Tau', - 'tau_nomet': 'noMET_and_Tau', 'met_notau': 'MET_and_noTau'} - assert mode in list(pref.keys()) - if ext == 'csv': - s = 'counts_{}_{}_{}/table.csv'.format(suffix, pref[mode], cut) - elif ext == 'root': - utils.create_single_dir('data') - s = 'data/data_{}_{}_{}.{}'.format(suffix, pref[mode], cut, ext) - else: - raise ValueError('The {} extension is not supported.'.format(ext)) - return s - -def set_plot_definitions(): - ROOT.gROOT.SetBatch(ROOT.kTRUE) - ROOT.gStyle.SetOptStat(ROOT.kFALSE) - ret = {'BoxTextSize' : 50, - 'BoxTextFont' : 43, - 'BoxTextColor' : ROOT.kBlack, - 'XTitleSize' : 0.045, - 'YTitleSize' : 0.045, - 'LineWidth' : 2, - 'FrameLineWidth' : 1, - } - return ret - -def plot(mode, hbase, htrg, htrgcut, cut_strs, var, channel, sample, category, directory): - legends = {'met': ['MET', 'MET + cut', 'met'], - 'tau': ['Tau', 'Tau + cut', 'tau'], - 'tau_nomet': ['MET', 'MET + cuts', 'tau_nomet', 'Tau', 'Tau + cuts'], - 'met_notau': ['Tau', 'Tau + cuts', 'met_notau', 'MET', 'MET + cuts']} - - assert mode in list(legends.keys()) - - cat_dir = os.path.join(directory, sample, category) - utils.create_single_dir(cat_dir) - - if isinstance(htrg, (tuple,list)): - plot_three_histos(legends[mode], cut_strs, - hbase, htrg, htrgcut, var, channel, directory, cat_dir) - else: - plot_two_histos(legends[mode], cut_strs, - hbase, htrg, htrgcut, var, channel, directory, cat_dir) - - -def plot_three_histos(legends, cut_strs, hbase, htrg, htrgcut, var, channel, directory, cat_folder): - defs = set_plot_definitions() - - hbase1 = hbase.Clone('hbase1') - htrgcut1 = [ h.Clone('htrgcut1_' + str(ih)) for ih,h in enumerate(htrgcut) ] - - hbase2 = hbase.Clone('hbase2') - htrg2 = [ h.Clone('htrg2_' + str(ih)) for ih,h in enumerate(htrg) ] - htrgcut2 = [ h.Clone('htrg2_' + str(ih)) for ih,h in enumerate(htrgcut) ] - - c1 = ROOT.TCanvas('c1', '', 600, 400) - c1.cd() - - # Absolute shapes - max_base = hbase1.GetMaximum() + (hbase1.GetMaximum()-hbase1.GetMinimum())/5. - max_cut = max(htrgcut1[0].GetMaximum(),htrgcut1[1].GetMaximum()) + (htrgcut1[0].GetMaximum()-htrgcut1[0].GetMinimum())/5. - htrgcut1[0].SetMaximum( max(max_base, max_cut) ) - - htrgcut1[1].GetXaxis().SetTitleSize(defs['XTitleSize']); - htrgcut1[1].GetXaxis().SetTitle(var + ' [GeV]'); - htrgcut1[1].GetYaxis().SetTitleSize(defs['YTitleSize']); - htrgcut1[1].GetYaxis().SetTitle('a. u.'); - htrgcut1[1].SetLineWidth(defs['LineWidth']); - htrgcut1[1].SetLineColor(8); - - hbase1.SetLineWidth(1); - hbase1.SetLineColor(1); - htrgcut1[0].SetLineWidth(1); - htrgcut1[0].SetLineColor(1); - htrgcut1[1].SetLineWidth(1); - htrgcut1[1].SetLineColor(1); - - htrgcut1[1].Add(hbase) - htrgcut1[1].Add(htrgcut1[0]) - htrgcut1[1].SetFillColor(8) - htrgcut1[1].Draw('hist') - htrgcut1[0].Add(hbase) - htrgcut1[0].SetFillColor(2) - htrgcut1[0].Draw('histsame') - hbase1.SetFillColor(4) - hbase1.Draw('histsame') - - leg1 = ROOT.TLegend(0.65, 0.75, 0.90, 0.9) - leg1.SetNColumns(1) - leg1.SetFillStyle(0) - leg1.SetBorderSize(0) - leg1.SetTextFont(43) - leg1.SetTextSize(10) - leg1.AddEntry(htrgcut1[1], legends[3], 'F') - leg1.AddEntry(htrgcut1[0], legends[0], 'F') - leg1.AddEntry(hbase1, '+'.join(triggers[channel]), 'F') - leg1.Draw('same') - - c1.Update(); - for ext in ('png', 'pdf'): - c1.SaveAs( os.path.join(cat_folder, legends[2] + '_abs_' + var + '_' + cut_strs + '.' + ext) ) - c1.Close() - - # Normalized shapes - c2 = ROOT.TCanvas('c2', '', 600, 400) - c2.cd() - try: - for h in htrg2: - h.Scale(1/h.Integral()) - except ZeroDivisionError: - pass - try: - hbase2.Scale(1/hbase2.Integral()) - except ZeroDivisionError: - pass - try: - for h in htrgcut2: - h.Scale(1/h.Integral()) - except ZeroDivisionError: - pass - - max_base2 = hbase2.GetMaximum() + (hbase2.GetMaximum()-hbase2.GetMinimum())/5. - max_cut2 = (max(htrgcut2[0].GetMaximum(),htrgcut2[1].GetMaximum()) + - max((htrgcut2[0].GetMaximum()-htrgcut2[0].GetMinimum())/5., (htrgcut2[1].GetMaximum()-htrgcut2[1].GetMinimum())/5.)) - hbase2.SetMaximum( max(max_base2, max_cut2) ) - - hbase2.GetXaxis().SetTitleSize(defs['XTitleSize']); - hbase2.GetXaxis().SetTitle(var + ' [GeV]'); - hbase2.GetYaxis().SetTitleSize(defs['YTitleSize']); - hbase2.GetYaxis().SetTitle('Normalized to 1'); - hbase2.SetLineWidth(defs['LineWidth']); - hbase2.SetLineColor(4); - htrgcut2[0].SetLineWidth(defs['LineWidth']); - htrgcut2[0].SetLineColor(2); - htrgcut2[1].SetLineWidth(defs['LineWidth']); - htrgcut2[1].SetLineColor(8); - - #htrg2.Draw('hist') - hbase2.Draw('hist') - htrgcut2[0].Draw('histsame') - htrgcut2[1].Draw('histsame') - - leg2 = ROOT.TLegend(0.69, 0.77, 0.90, 0.9) - leg2.SetNColumns(1) - leg2.SetFillStyle(0) - leg2.SetBorderSize(0) - leg2.SetTextFont(43) - leg2.SetTextSize(10) - #leg2.AddEntry(htrg2, legends[0]) - leg2.AddEntry(htrgcut2[0], legends[1]) - leg2.AddEntry(htrgcut2[1], legends[4]) - leg2.AddEntry(hbase2, '+\n'.join(triggers[channel])) - leg2.Draw('same') - - c2.Update(); - for ext in ('png', 'pdf'): - c2.SaveAs( os.path.join(cat_folder, legends[2] + '_norm_' + var + '_' + cut_strs + '.' + ext) ) - c2.Close() - -def plot_two_histos(legends, cut_strs, hbase, htrg, htrgcut, var, channel, directory, cat_folder): - defs = set_plot_definitions() - - hbase1 = hbase.Clone('hbase1') - htrgcut1 = htrgcut.Clone('htrgcut1') - - htrg2 = htrg.Clone('htrg2') - hbase2 = hbase.Clone('hbase2') - htrgcut2 = htrgcut.Clone('htrgcut2') - - c1 = ROOT.TCanvas('c1', '', 600, 400) - c1.cd() - - # Absolute shapes - max_base = hbase1.GetMaximum() + (hbase1.GetMaximum()-hbase1.GetMinimum())/5. - max_cut = htrgcut1.GetMaximum() + (htrgcut1.GetMaximum()-htrgcut1.GetMinimum())/5. - htrgcut1.SetMaximum( max(max_base, max_cut) ) - - htrgcut1.GetXaxis().SetTitleSize(defs['XTitleSize']); - htrgcut1.GetXaxis().SetTitle(var + ' [GeV]'); - htrgcut1.GetYaxis().SetTitleSize(defs['YTitleSize']); - htrgcut1.GetYaxis().SetTitle('a. u.'); - htrgcut1.SetLineWidth(defs['LineWidth']); - htrgcut1.SetLineColor(8); - - hbase1.SetLineWidth(2); - hbase1.SetLineColor(4); - htrgcut1.SetLineWidth(2); - htrgcut1.SetLineColor(2); - - #htrg.Draw('hist') - htrgcut1.Add(hbase) - htrgcut1.SetFillColor(2) - htrgcut1.Draw('hist') - hbase1.SetFillColor(4) - hbase1.Draw('histsame') - - leg1 = ROOT.TLegend(0.69, 0.77, 0.90, 0.9) - leg1.SetNColumns(1) - leg1.SetFillStyle(0) - leg1.SetBorderSize(0) - leg1.SetTextFont(43) - leg1.SetTextSize(10) - leg1.AddEntry(htrgcut1, legends[0]) - leg1.AddEntry(hbase1, '+'.join(triggers[channel])) - leg1.Draw('same') - - c1.Update(); - for ext in ('png', 'pdf'): - c1.SaveAs( os.path.join(cat_folder, legends[2] + '_abs_' + var + '_' + cut_strs + '.' + ext) ) - c1.Close() - - # Normalized shapes - c2 = ROOT.TCanvas('c2', '', 600, 400) - c2.cd() - try: - htrg2.Scale(1/htrg2.Integral()) - except ZeroDivisionError: - pass - try: - hbase2.Scale(1/hbase2.Integral()) - except ZeroDivisionError: - pass - try: - htrgcut2.Scale(1/htrgcut2.Integral()) - except ZeroDivisionError: - pass - - max_met2 = htrg2.GetMaximum() + (htrg2.GetMaximum()-htrg2.GetMinimum())/5. - max_base2 = hbase2.GetMaximum() + (hbase2.GetMaximum()-hbase2.GetMinimum())/5. - max_cut2 = htrgcut2.GetMaximum() + (htrgcut2.GetMaximum()-htrgcut2.GetMinimum())/5. - htrg2.SetMaximum( max(max_met2, max_base2, max_cut2) ) - - htrg2.GetXaxis().SetTitleSize(defs['XTitleSize']); - htrg2.GetXaxis().SetTitle(var + ' [GeV]'); - htrg2.GetYaxis().SetTitleSize(defs['YTitleSize']); - htrg2.GetYaxis().SetTitle('Normalized to 1'); - htrg2.SetLineWidth(defs['LineWidth']); - htrg2.SetLineColor(8); - - hbase2.SetLineWidth(2); - hbase2.SetLineColor(4); - htrgcut2.SetLineWidth(2); - htrgcut2.SetLineColor(2); - - htrg2.Draw('hist') - hbase2.Draw('histsame') - htrgcut2.Draw('histsame') - - leg2 = ROOT.TLegend(0.69, 0.77, 0.90, 0.9) - leg2.SetNColumns(1) - leg2.SetFillStyle(0) - leg2.SetBorderSize(0) - leg2.SetTextFont(43) - leg2.SetTextSize(10) - leg2.AddEntry(htrg2, legends[0]) - leg2.AddEntry(htrgcut2, legends[1]) - leg2.AddEntry(hbase2, '+\n'.join(triggers[channel])) - leg2.Draw('same') - - c2.Update(); - for ext in ('png', 'pdf'): - c2.SaveAs( os.path.join(cat_folder, legends[2] + '_norm_' + var + '_' + cut_strs + '.' + ext) ) - c2.Close() - - -def plot2D(mode, hbase, htrg, htrgcut, cut_strs, two_vars, channel, sample, category, directory): - legends = {'met': ['MET', 'MET + cut', 'met'], - 'tau': ['Tau', 'Tau + cut', 'tau'], - 'met_tau': ['MET', 'MET + cuts', 'met_and_tau', 'Tau', 'Tau + cuts']} - - #htrg1 = htrg.Clone('htrg1') - hbase1 = hbase.Clone('hbase1') - htrgcut1 = htrgcut.Clone('htrgcut1') - - defs = set_plot_definitions() - c = ROOT.TCanvas('c', '', 600, 400) - - c.cd() - pad1 = ROOT.TPad('pad1', 'pad1', 0., 0., 0.5, 1.) - pad1.SetFrameLineWidth(defs['FrameLineWidth']) - pad1.SetLeftMargin(0.15); - pad1.SetRightMargin(0.0); - pad1.SetBottomMargin(0.08); - pad1.SetTopMargin(0.055); - pad1.Draw() - pad1.cd() - - hbase.GetXaxis().SetTitle(''); - hbase.GetYaxis().SetTitle(''); - try: - hbase.Scale(1/hbase.Integral()) - except ZeroDivisionError: - pass - hbase.Draw('colz'); - - # c.cd() - # pad2 = ROOT.TPad('pad2', 'pad2', 0.333, 0.0, 0.665, 1.0) - # pad2.SetFrameLineWidth(defs['FrameLineWidth']) - # pad2.SetLeftMargin(0.0); - # pad2.SetRightMargin(0.0); - # pad2.SetBottomMargin(0.08); - # pad2.SetTopMargin(0.055); - # pad2.Draw() - # pad2.cd() - - # htrg1.GetXaxis().SetTitle('') - # htrg1.GetYaxis().SetTitle(two_vars[1]) - # htrgcut1.GetYaxis().SetTitleSize(0.045) - # htrgcut1.GetYaxis().SetTitle(two_vars[1]) - # try: - # htrg1.Scale(1/htrg1.Integral()) - # except ZeroDivisionError: - # pass - # htrg1.Draw('colz'); - - c.cd() - pad3 = ROOT.TPad('pad3', 'pad3', 0.5, 0.0, 1.0, 1.0) - pad3.SetFrameLineWidth(defs['FrameLineWidth']) - pad3.SetLeftMargin(0.0); - pad3.SetRightMargin(0.15); - pad3.SetBottomMargin(0.08); - pad3.SetTopMargin(0.055); - pad3.Draw() - pad3.cd() - - htrgcut1.GetXaxis().SetTitle(two_vars[0]) - htrgcut1.GetXaxis().SetTitleSize(0.045) - try: - htrgcut1.Scale(1/htrgcut1.Integral()) - except ZeroDivisionError: - pass - htrgcut1.Draw('colz same') - - cat_folder = os.path.join(directory, sample, category) - utils.create_single_dir(cat_folder) - c.Update(); - for ext in ('png', 'pdf'): - c.SaveAs( os.path.join(cat_folder, legends[mode][2] + '_' + '_VS_'.join(two_vars) + '_' + cut_strs + '.' + ext) ) - c.Close() - -def count(mode, hbase, htrg, htrgcut, cut_strings, var, channel, sample, category, directory): - cat_folder = os.path.join(directory, sample, category) - titles = {'met': ['MET', 'MET + cut', 'Trigger baseline (no MET)', 'Fraction [%]: {[MET + Cut] / [Trigger baseline]} + 1\n'], - 'tau': ['Tau', 'Tau + cut', 'Trigger baseline (no Tau)', 'Fraction [%]: {[Tau + Cut] / [Trigger baseline]} + 1\n'], - 'tau_nomet': ['MET + Tau', 'MET + Tau + cut', 'Trigger baseline (no MET + Tau)', 'Fraction [%]: {[MET + Tau + Cut] / [Trigger baseline]} + 1\n'], - 'met_notau': ['Tau + MET', 'Tau + MET + cut', 'Trigger baseline (MET + no Tau)', 'Fraction [%]: {[MET + Tau + Cut] / [Trigger baseline]} + 1\n'],} - - def calc_frac(c1, c2): - try: - frac = c2 / (c1 + c2) - except ZeroDivisionError: - frac = 0 - return frac * 100 - - name_met = os.path.join(cat_folder, - get_outname(suffix=var, mode=mode, cut=cut_strings, ext='csv')) - utils.create_single_dir(os.path.dirname(name_met)) - with open(name_met, 'w') as f: - f.write(','.join(('Bin label', titles[mode][0], titles[mode][1], titles[mode][2], titles[mode][3]))) - for ibin in range(1, htrg.GetNbinsX()+1): - label = str(round(htrg.GetXaxis().GetBinLowEdge(ibin),2)) + ' / ' + str(round(htrg.GetXaxis().GetBinLowEdge(ibin+1),2)) - cmet = htrg.GetBinContent(ibin) - cnomet = hbase.GetBinContent(ibin) - cmetcut = htrgcut.GetBinContent(ibin) - assert cmet >= cmetcut - - frac = calc_frac(cnomet, cmetcut) - f.write(','.join((label, str(round(cmet,2)), str(round(cmetcut,2)), str(round(cnomet,2)), str(round(frac,2)))) + '\n') - - totmet = htrg.Integral(0,htrg.GetNbinsX()+1) - totnomet = hbase.Integral(0,hbase.GetNbinsX()+1) - totmetcut = htrgcut.Integral(0,htrgcut.GetNbinsX()+1) - - totfrac = calc_frac(totnomet, totmetcut) - f.write(','.join(('Total', str(round(totmet,2)), str(round(totmetcut,2)), str(round(totnomet,2)), str(round(totfrac,2)))) + '\n') - return totfrac - -def counts_total(mode, totarr, channel, category, directory): - titles = {'met': ['MET', 'MET + cut', 'Trigger baseline (no MET)', 'Fraction [%]: {[MET + Cut] / [Trigger baseline]} + 1\n'], - 'tau': ['Tau', 'Tau + cut', 'Trigger baseline (no Tau)', 'Fraction [%]: {[Tau + Cut] / [Trigger baseline]} + 1\n'], - 'tau_nomet': ['MET + Tau', 'MET + Tau + cut', 'Trigger baseline (no MET + Tau)', 'Fraction [%]: {[MET + Tau + Cut] / [Trigger baseline]} + 1\n'], - 'met_notau': ['Tau + MET', 'Tau + MET + cut', 'Trigger baseline (no Tau + MET)', 'Fraction [%]: {[MET + Tau + Cut] / [Trigger baseline]} + 1\n'],} - - name = os.path.join(directory, 'counts_total_' + category + '_' + mode, 'table.csv') - utils.create_single_dir(os.path.dirname(name)) - with open(name, 'w') as f: - f.write(','.join(('Sample', 'Fraction')) + '\n') - for sample, frac in totarr: - f.write(','.join((sample, str(round(frac,3)))) + '\n') - -def test_triger_stats(indir, sample, channel, plot_only, cut_strings): - outname = get_outname(suffix=sample+'_'+channel, mode='met', - cut=cut_strings['met_tau'], ext='root') - - if channel == 'etau' or channel == 'mutau': - iso1 = (24, 0, 8) - elif channel == 'tautau': - iso1 = binning['dau2_iso'] - binning.update({'HHKin_mass': (20, float(sample)-300, float(sample)+300), - 'dau1_iso': iso1}) - - full_sample = 'GluGluToBulkGravitonToHHTo2B2Tau_M-' + sample + '_' - - t_in = ROOT.TChain('HTauTauTree') - glob_files = glob.glob( os.path.join(indir, full_sample, 'output_*.root') ) - for f in glob_files: - t_in.Add(f) - - hBaseline = defaultdict(dict) - hMET, hMETWithCut = (defaultdict(dict) for _ in range(2)) - hTau, hTauWithCut = (defaultdict(dict) for _ in range(2)) - hTauNoMET, hTauNoMETWithCut = (defaultdict(dict) for _ in range(2)) - hMETNoTau, hMETNoTauWithCut = (defaultdict(dict) for _ in range(2)) - hOR, hORWithCut = (defaultdict(dict) for _ in range(2)) - for v in tuple(variables): - for cat in categories: - suff = lambda x : x + '_' + v + '_' + cat - hopt = ('', *binning[v]) - hBaseline[v][cat] = ROOT.TH1D(suff('hBaseline'), *hopt) - hMET[v][cat] = ROOT.TH1D(suff('hMET'), *hopt) - hMETWithCut[v][cat] = ROOT.TH1D(suff('hMETWithCut'), *hopt) - hTau[v][cat] = ROOT.TH1D(suff('hTau'), *hopt) - hTauWithCut[v][cat] = ROOT.TH1D(suff('hTauWithCut'), *hopt) - hTauNoMET[v][cat] = ROOT.TH1D(suff('hTauNoMET'), *hopt) - hTauNoMETWithCut[v][cat] = ROOT.TH1D(suff('hTauNoMETWithCut'), *hopt) - hMETNoTau[v][cat] = ROOT.TH1D(suff('hMETNoTau'), *hopt) - hMETNoTauWithCut[v][cat] = ROOT.TH1D(suff('hMETNoTauWithCut'), *hopt) - hOR[v][cat] = ROOT.TH1D(suff('hOR'), *hopt) - hORWithCut[v][cat] = ROOT.TH1D(suff('hORWithCut'), *hopt) - - hBaseline_2D = defaultdict(dict) - hMET_2D, hMETWithCut_2D = (defaultdict(dict) for _ in range(2)) - hTau_2D, hTauWithCut_2D = (defaultdict(dict) for _ in range(2)) - hOR_2D, hORWithCut_2D = (defaultdict(dict) for _ in range(2)) - for v in variables_2D: - for cat in categories: - suff = lambda x: x + '_' + '_'.join(v) + '_' + cat - hopt = ('', *binning[v[0]], *binning[v[1]]) - hBaseline_2D[v][cat] = ROOT.TH2D(suff('hBaseline_2D'), *hopt) - hMET_2D[v][cat] = ROOT.TH2D(suff('hMET_2D'), *hopt) - hMETWithCut_2D[v][cat] = ROOT.TH2D(suff('hMETWithCut_2D'), *hopt) - hTau_2D[v][cat] = ROOT.TH2D(suff('hTau_2D'), *hopt) - hTauWithCut_2D[v][cat] = ROOT.TH2D(suff('hTauWithCut_2D'), *hopt) - hOR_2D[v][cat] = ROOT.TH2D(suff('hOR_2D'), *hopt) - hORWithCut_2D[v][cat] = ROOT.TH2D(suff('hORWithCut_2D'), *hopt) - - t_in.SetBranchStatus('*', 0) - _entries = ('triggerbit', 'RunNumber', 'isLeptrigger', - 'bjet1_bID_deepFlavor', 'bjet2_bID_deepFlavor', 'isBoosted', - 'isVBF', 'VBFjj_mass', 'VBFjj_deltaEta', 'PUReweight', 'lumi', 'IdAndIsoSF_deep_pt', - 'pairType', 'dau1_eleMVAiso', 'dau1_iso', 'dau1_deepTauVsJet', 'dau2_deepTauVsJet', - 'nleps', 'nbjetscand', 'tauH_SVFIT_mass', 'bH_mass_raw',) - _entries += tuple(variables) - for ientry in _entries: - t_in.SetBranchStatus(ientry, 1) - - for entry in t_in: - # this is slow: do it once only - entries = utils.dot_dict({x: getattr(entry, x) for x in _entries}) - sel = selection.EventSelection(entries, isdata=False, configuration=None) - - # mcweight = entries.MC_weight - pureweight = entries.PUReweight - lumi = entries.lumi - idandiso = entries.IdAndIsoSF_deep_pt - - #if utils.is_nan(mcweight) : mcweight=1 - if utils.is_nan(pureweight) : pureweight=1 - if utils.is_nan(lumi) : lumi=1 - if utils.is_nan(idandiso) : idandiso=1 - - evt_weight = pureweight*lumi*idandiso - if utils.is_nan(evt_weight): - evt_weight = 1 - - if utils.is_channel_consistent(channel, entries.pairType): - if not sel.selection_cuts(lepton_veto=True, bjets_cut=True, - standard_mass_cut=True, invert_mass_cut=False): - continue - - for v in variables: - for cat in categories: - if sel.sel_category(cat): - - # passes the OR of the trigger baseline (not including METNoMu120 trigger) - pass_trg = sel.pass_triggers(triggers[channel]) and entries.isLeptrigger - if pass_trg: - hBaseline[v][cat].Fill(entries[v], evt_weight) - - met_cut_expr = entries.metnomu_et > met_cut - tau_cut_expr = ((entries.dau1_pt > tau_cut and args.channel=='tautau') or - (entries.dau2_pt > tau_cut and args.channel!='tautau')) - - # passes the METNoMu120 trigger and does *not* pass the OR of the baseline - if not pass_trg and eval(' '.join(args.custom_cut)): - if sel.pass_triggers(('METNoMu120',)): - hMET[v][cat].Fill(entries[v], evt_weight) - if met_cut_expr: - hMETWithCut[v][cat].Fill(entries[v], evt_weight) - - # passes the IsoTau180 trigger and does *not* pass the OR of the baseline - if sel.pass_triggers(('IsoTau180',)): - hTau[v][cat].Fill(entries[v], evt_weight) - if tau_cut_expr: - hTauWithCut[v][cat].Fill(entries[v], evt_weight) - - # passes the IsoTau180 trigger and does *not* pass the OR of the baseline and METNoMu120 - if sel.pass_triggers(('IsoTau180',)) and not sel.pass_triggers(('METNoMu120',)): - hTauNoMET[v][cat].Fill(entries[v], evt_weight) - if tau_cut_expr: - hTauNoMETWithCut[v][cat].Fill(entries[v], evt_weight) - - # passes the METNoMu120 trigger and does *not* pass the OR of the baseline and IsoTau180 - if sel.pass_triggers(('METNoMu120',)) and not sel.pass_triggers(('IsoTau180',)): - hMETNoTau[v][cat].Fill(entries[v], evt_weight) - if met_cut_expr: - hMETNoTauWithCut[v][cat].Fill(entries[v], evt_weight) - - # passes the METNoMu120 or the IsoTau180 triggers and does *not* pass the OR of the baseline - if sel.pass_triggers(('METNoMu120', 'IsoTau180',)): - hOR[v][cat].Fill(entries[v], evt_weight) - if ((sel.pass_triggers(('METNoMu120',)) and met_cut_expr) or - (sel.pass_triggers(('IsoTau180',)) and tau_cut_expr)): - hORWithCut[v][cat].Fill(entries[v], evt_weight) - - for v in variables_2D: - for cat in categories: - if sel.sel_category(cat): - - # passes the OR of the trigger baseline (not including METNoMu120 trigger) - pass_trg = sel.pass_triggers(triggers[channel]) and entries.isLeptrigger - if pass_trg: - hBaseline_2D[v][cat].Fill(entries[v[0]], entries[v[1]], evt_weight) - - # passes the METNoMu120 trigger and does *not* pass the OR of the baseline - if not pass_trg and eval(' '.join(args.custom_cut)): - if sel.pass_triggers(('METNoMu120',)): - hMET_2D[v][cat].Fill(entries[v[0]], entries[v[1]], evt_weight) - if met_cut_expr: - hMETWithCut_2D[v][cat].Fill(entries[v[0]], entries[v[1]], evt_weight) - - if sel.pass_triggers(('IsoTau180',)): - hTau_2D[v][cat].Fill(entries[v[0]], entries[v[1]], evt_weight) - if tau_cut_expr: - hTauWithCut_2D[v][cat].Fill(entries[v[0]], entries[v[1]], evt_weight) - - if sel.pass_triggers(('METNoMu120', 'IsoTau180',)): - hOR_2D[v][cat].Fill(entries[v[0]], entries[v[1]], evt_weight) - if ((sel.pass_triggers(('METNoMu120',)) and met_cut_expr) or - (sel.pass_triggers(('IsoTau180',)) and tau_cut_expr)): - hORWithCut_2D[v][cat].Fill(entries[v[0]], entries[v[1]], evt_weight) - - - f_out = ROOT.TFile(outname, 'RECREATE') - f_out.cd() - for cat in categories: - for v in variables: - suff = lambda x : x + '_' + v + '_' + cat - hBaseline[v][cat].Write(suff('hBaseline')) - hMET[v][cat].Write(suff('hMET')) - hMETWithCut[v][cat].Write(suff('hMETWithCut')) - hTau[v][cat].Write(suff('hTau')) - hTauWithCut[v][cat].Write(suff('hTauWithCut')) - hTauNoMET[v][cat].Write(suff('hTauNoMET')) - hTauNoMETWithCut[v][cat].Write(suff('hTauNoMETWithCut')) - hMETNoTau[v][cat].Write(suff('hMETNoTau')) - hMETNoTauWithCut[v][cat].Write(suff('hMETNoTauWithCut')) - hOR[v][cat].Write(suff('hOR')) - hORWithCut[v][cat].Write(suff('hORWithCut')) - for v in variables_2D: - suff2 = lambda x: x + '_' + '_'.join(v) + '_' + cat - hBaseline_2D[v][cat].Write(suff2('hBaseline_2D')) - hMET_2D[v][cat].Write(suff2('hMET_2D')) - hMETWithCut_2D[v][cat].Write(suff2('hMETWithCut_2D')) - hTau_2D[v][cat].Write(suff2('hTau_2D')) - hTauWithCut_2D[v][cat].Write(suff2('hTauWithCut_2D')) - hOR_2D[v][cat].Write(suff2('hOR_2D')) - hORWithCut_2D[v][cat].Write(suff2('hORWithCut_2D')) - f_out.Close() - print('Raw histograms saved in {}.'.format(outname), flush=True) - -if __name__ == '__main__': - triggers = {'etau': ('Ele32', 'EleIsoTauCustom'), - 'mutau': ('IsoMu24', 'IsoMuIsoTauCustom'), - 'tautau': ('IsoDoubleTauCustom',)} - binning = {'metnomu_et': (20, 0, 450), - 'dau1_pt': (30, 0, 450), - 'dau1_eta': (20, -2.5, 2.5), - 'dau2_iso': (20, 0.88, 1.01), - 'dau2_pt': (30, 0, 350), - 'dau2_eta': (20, -2.5, 2.5), - 'ditau_deltaR': (30, 0.3, 1.3), - 'dib_deltaR': (25, 0, 2.5), - 'bH_pt': (20, 70, 600), - 'bH_mass': (30, 0, 280), - 'tauH_mass': (30, 0, 170), - 'tauH_pt': (30, 0, 500), - 'tauH_SVFIT_mass': (30, 0, 250), - 'tauH_SVFIT_pt': (20, 200, 650), - 'bjet1_pt': (25, 10, 600), - 'bjet2_pt': (25, 10, 550), - 'bjet1_eta': (20, -2.5, 2.5), - 'bjet2_eta': (20, -2.5, 2.5), - } - variables = tuple(binning.keys()) + ('HHKin_mass', 'dau1_iso') - #variables = tuple(('dau1_pt', 'dau2_pt', 'metnomu_et', 'HHKin_mass', 'dau1_iso', 'dau2_iso')) - variables_2D = (('dau1_pt', 'dau2_pt'),)#('dau1_iso', 'dau2_iso')) - - #categories = ('baseline', 's1b1jresolvedMcut', 's2b0jresolvedMcut', 'sboostedLLMcut') - categories = ('baseline',) - met_cut = 200 - tau_cut = 190 - - # Parse input arguments - parser = argparse.ArgumentParser(description='Producer trigger histograms.') - - parser.add_argument('--indir', required=True, type=str, - help='Full path of ROOT input file') - parser.add_argument('--samples', required=True, nargs='+', type=str, - help='Full path of ROOT input file') - parser.add_argument('--channel', required=True, type=str, - help='Select the channel over which the workflow will be run.' ) - parser.add_argument('--plot_only', action='store_true', - help='Reuse previously produced data for quick plot changes.') - parser.add_argument('--plot_2D_only', action='store_true', - help='Reuse previously produced data for quick plot changes.') - parser.add_argument('--custom_cut', nargs='+', default=['True'], - help='Customisable cut provided by the user.') - parser.add_argument('--copy', action='store_true', - help='Do not copy the outputs to EOS at the end.') - parser.add_argument('--sequential', action='store_true', - help='Do not use the multiprocess package.') - args = utils.parse_args(parser) - - main_dir = 'TriggerStudy_MET'+str(met_cut)+'_SingleTau'+str(tau_cut) - cut_expr = '' - if '_'.join(args.custom_cut) != 'True': - cut_expr = '_CUT_' + '_'.join(args.custom_cut) - cut_expr = cut_expr.replace('.','_').replace(' ','_').replace('>','GT').replace('<','ST') - main_dir += cut_expr - - cut_strings = {'met': 'met' + str(met_cut) + cut_expr, - 'tau': 'tau' + str(tau_cut) + cut_expr, - 'met_tau': 'met' + str(met_cut) + '_tau' + str(tau_cut) + cut_expr} - - #### run major function ### - if args.sequential: - if not args.plot_only and not args.plot_2D_only: - for sample in args.samples: - test_triger_stats(args.indir, sample, args.channel, args.plot_only, cut_strings) - else: - if not args.plot_only and not args.plot_2D_only: - pool = multiprocessing.Pool(processes=6) - pool.starmap(test_triger_stats, zip(it.repeat(args.indir), args.samples, - it.repeat(args.channel), it.repeat(args.plot_only), - it.repeat(cut_strings))) - ########################### - - from_directory = os.path.join(main_dir, args.channel) - totcounts = {'met': {}, 'tau': {}, 'tau_nomet': {}, 'met_notau': {}} - for cat in categories: - totcounts['met'][cat] = [] - totcounts['tau'][cat] = [] - totcounts['tau_nomet'][cat] = [] - totcounts['met_notau'][cat] = [] - - for sample in args.samples: - outname = get_outname(suffix=sample+'_'+args.channel, mode='met', - cut=cut_strings['met_tau'], ext='root') - f_in = ROOT.TFile(outname, 'READ') - f_in.cd() - for cat in categories: - if not args.plot_2D_only: - for v in variables: - suff = lambda x : x + '_' + v + '_' + cat - - hBaseline = f_in.Get(suff('hBaseline')) - hMET = f_in.Get(suff('hMET')) - hMETWithCut = f_in.Get(suff('hMETWithCut')) - hTau = f_in.Get(suff('hTau')) - hTauWithCut = f_in.Get(suff('hTauWithCut')) - hTauNoMET = f_in.Get(suff('hTauNoMET')) - hTauNoMETWithCut = f_in.Get(suff('hTauNoMETWithCut')) - hMETNoTau = f_in.Get(suff('hMETNoTau')) - hMETNoTauWithCut = f_in.Get(suff('hMETNoTauWithCut')) - hOR = f_in.Get(suff('hOR')) - hORWithCut = f_in.Get(suff('hORWithCut')) - - hBaseline_c = hBaseline.Clone(suff('hBaseline') + '_c') - hMET_c = hMET.Clone(suff('hMET') + '_c') - hMETWithCut_c = hMETWithCut.Clone(suff('hMETWithCut') + '_c') - hTau_c = hTau.Clone(suff('hTau') + '_c') - hTauWithCut_c = hTauWithCut.Clone(suff('hTauWithCut') + '_c') - hTauNoMET_c = hTauNoMET.Clone(suff('hTauNoMET') + '_c') - hTauNoMETWithCut_c = hTauNoMETWithCut.Clone(suff('hTauNoMETWithCut') + '_c') - hMETNoTau_c = hMETNoTau.Clone(suff('hMETNoTau') + '_c') - hMETNoTauWithCut_c = hMETNoTauWithCut.Clone(suff('hMETNoTauWithCut') + '_c') - # hOR_c = hOR.Clone(suff('hOR') + '_c') - # hORWithCut_c = hORWithCut.Clone(suff('hORWithCut') + '_c') - - hOverlayBaseline_c = hBaseline.Clone(suff('hOverlayBaseline_') + '_c') - hOverlayMET_c = hMET.Clone(suff('hOverlayMET_') + '_c') - hOverlayBaseline_c.Add(hOverlayMET_c) - - opt = (v, args.channel, sample, cat, from_directory) - - plot('met', hBaseline, hMET, hMETWithCut, cut_strings['met'], *opt) - plot('tau', hBaseline, hTau, hTauWithCut, cut_strings['tau'], *opt) - plot('tau_nomet', hBaseline, [hMET,hTauNoMET], [hMETWithCut,hTauNoMETWithCut], - cut_strings['met_tau'], *opt) - plot('met_notau', hBaseline, [hTau,hMETNoTau], [hTauWithCut,hMETNoTauWithCut], - cut_strings['met_tau'], *opt) - - c1 = count('met', hBaseline_c, hMET_c, hMETWithCut_c, cut_strings['met'], *opt) - c2 = count('tau', hBaseline_c, hTau_c, hTauWithCut_c, cut_strings['tau'], *opt) - c3 = count('tau_nomet', hBaseline_c, hTauNoMET_c, hTauNoMETWithCut_c, - cut_strings['met_tau'],*opt) - c4 = count('tau_nomet', hOverlayBaseline_c, hTauNoMET_c, hTauNoMETWithCut_c, - cut_strings['met_tau'], *opt) - c5 = count('met_notau', hBaseline_c, hMETNoTau_c, hMETNoTauWithCut_c, - cut_strings['met_tau'],*opt) - c6 = count('met_notau', hOverlayBaseline_c, hMETNoTau_c, hMETNoTauWithCut_c, - cut_strings['met_tau'], *opt) - assert c3 <= c2 - assert c4 <= c2 - assert c4 <= c3 - assert c6 <= c5 - - totcounts['met'][cat].append((sample, c1)) - totcounts['tau'][cat].append((sample, c2)) - totcounts['tau_nomet'][cat].append((sample, c4)) - totcounts['met_notau'][cat].append((sample, c6)) - - for v in variables_2D: - opt_2D = (v, args.channel, sample, cat, from_directory) - hBaseline_2D = f_in.Get('hBaseline_2D_' + '_'.join(v)+'_'+ cat) - hMET_2D = f_in.Get('hMET_2D_' + '_'.join(v)+'_'+ cat) - hMETWithCut_2D = f_in.Get('hMETWithCut_2D_' + '_'.join(v)+'_'+ cat) - plot2D('met', hBaseline_2D, hMET_2D, hMETWithCut_2D, cut_strings['met'], *opt_2D) - hTau_2D = f_in.Get('hTau_2D_' + '_'.join(v)+'_'+ cat) - hTauWithCut_2D = f_in.Get('hTauWithCut_2D_' + '_'.join(v)+'_'+ cat) - plot2D('tau', hBaseline_2D, hTau_2D, hTauWithCut_2D, cut_strings['tau'], *opt_2D) - hOR_2D = f_in.Get('hOR_2D_' + '_'.join(v)+'_'+ cat) - hORWithCut_2D = f_in.Get('hORWithCut_2D_' + '_'.join(v)+'_'+ cat) - plot2D('met_tau', hBaseline_2D, hOR_2D, hORWithCut_2D, cut_strings['met_tau'], *opt_2D) - - f_in.Close() - - for cat in categories: - opt2 = (args.channel, cat, from_directory) - counts_total('met', totcounts['met'][cat], *opt2) - counts_total('tau', totcounts['tau'][cat], *opt2) - counts_total('tau_nomet', totcounts['tau_nomet'][cat], *opt2) - counts_total('met_notau', totcounts['met_notau'][cat], *opt2) - - if args.copy: - import subprocess - to_directory = os.path.join('/eos/user/b/bfontana/www/TriggerScaleFactors', main_dir) - to_directory = os.path.join(to_directory, args.channel) - - for m in ('met', 'tau', 'tau_nomet', 'met_notau'): - for cat in categories: - folder_name = 'counts_total_' + cat + '_' + m - folder_to = os.path.join(to_directory, folder_name) - utils.create_single_dir(folder_to) - counts_files = os.path.join(from_directory, folder_name, 'table.csv') - print('Copying: {}\t\t--->\t{}'.format(counts_files, folder_to), flush=True) - subprocess.run(['rsync', '-ah', counts_files, os.path.join(folder_to, 'table.csv')]) - - for sample in args.samples: - sample_from = os.path.join(from_directory, sample) - print('Copying: {}\t\t--->\t{}'.format(sample_from, to_directory), flush=True) - subprocess.run(['rsync', '-ah', sample_from, to_directory]) - - print('Done.') diff --git a/tests/test_util.py b/tests/test_util.py deleted file mode 100644 index 223f923..0000000 --- a/tests/test_util.py +++ /dev/null @@ -1,10 +0,0 @@ -# coding: utf-8 - -__all__ = ['TestCase'] - -import unittest - -class TestCase(unittest.TestCase): - - def test_dummy(self): - return True diff --git a/tests/trigger_efficiencies_run3.py b/tests/trigger_efficiencies_run3.py new file mode 100644 index 0000000..175c8c0 --- /dev/null +++ b/tests/trigger_efficiencies_run3.py @@ -0,0 +1,456 @@ +# coding: utf-8 + +_all_ = [ 'test_trigger_gains' ] + +import os +import sys +parent_dir = os.path.abspath(__file__ + 2 * '/..') +sys.path.insert(0, parent_dir) + +import json +import argparse +from inclusion.utils import utils +import numpy as np +from collections import defaultdict as dd +import hist +import mplhep as hep +from hist.intervals import clopper_pearson_interval as clop +import pickle +import uproot +import matplotlib.pyplot as plt + + +# import bokeh +# from bokeh.plotting import figure, output_file, save +# from bokeh.models import Whisker +# from bokeh.layouts import gridplot +# from bokeh.io import export_svg, export_png + +tau = '\u03C4' +mu = '\u03BC' +pm = '\u00B1' +ditau = tau+tau + +def get_outname(channel, regcuts, ptcuts, met_turnon, bigtau): + utils.create_single_dir('data') + + name = channel + '_' + name += '_'.join((*regcuts, 'ptcuts', *[str(x) for x in ptcuts], 'turnon', str(met_turnon))) + if bigtau: + name += '_BIGTAU' + name += '.pkl' + + s = 'data/regions_{}'.format(name) + return s + +def pp(chn): + if chn == "tautau": + return ditau + elif chn == "etau": + return "e" + tau + elif chn == "mutau": + return mu + tau + +def rec_dd(): + return dd(rec_dd) + +def set_fig(fig, legend=True): + fig.output_backend = 'svg' + fig.toolbar.logo = None + # if legend: + # fig.legend.click_policy='hide' + # fig.legend.location = 'top_left' + # fig.legend.label_text_font_size = '8pt' + fig.min_border_bottom = 5 + fig.xaxis.visible = True + fig.title.align = "left" + fig.title.text_font_size = "20px" + fig.xaxis.axis_label_text_font_style = "bold" + fig.yaxis.axis_label_text_font_style = "bold" + fig.xaxis.axis_label_text_font_size = "13px" + fig.yaxis.axis_label_text_font_size = "13px" + +def main(args): + vars = [ + # 'dau1_iso', + 'dau1_pt', + # 'dau2_iso', + 'dau2_pt', 'dau1_eta', 'dau2_eta', + 'bjet1_pNet', 'bjet2_pNet', 'bjet1_pt', + 'bjet2_pt', 'bjet1_eta', 'bjet2_eta'] + if args.channels[0] == "etau": + base_extension = "E" + channel_text = r"e$\tau_{h}$" + elif args.channels[0] == "mutau": + base_extension = "Mu" + channel_text = r"$\mu \tau_{h}$" + elif args.channels[0] == "tautau": + base_extension = "Tau" + channel_text = r"$\tau_{h} \tau_{h}$" + with uproot.open("data/regions_preEE_10p20_all.root") as file: + # from matplotlib.colors import ListedColormap + petroff6 = ["#5790fc", "#f89c20", "#e42536", "#964a8b", "#9c9ca1", "#7a21dd"] + + channel_all = file['All_baseline_{}_genHH_mass'.format(args.channels[0])] + channel_base = file['Base{}_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # channel_base_plus_met = file['Base{}ORMET_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + channel_base_plus_tau = file['Base{}ORTauTauJet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # channel_base_plus_Mu50 = file['Base{}ORMu50_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + channel_base_plus_quadJetPNet = file['Base{}OR4JetsPNet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # channel_base_plus_met = file['NoBaseMuTTJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_quadJetDeep = file['NoBaseMu4JetsDeepJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # if args.channels[0] == "tautau": + # channel_base_all = file['Base{}ORTauTauJetOR4JetsPNet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # if args.channels[0] == "mutau": + # channel_base_all = file['Base{}ORMETORTauORMu50OR4JetsPNet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # if args.channels[0] == "etau": + # channel_base_all = file['Base{}ORMETORTauOR4JetsPNet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + + # channel_base_plus_Mu50 = file['NoBaseMuMu50_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_Ele28 = file['NoBaseEle28_baseline_{}_genHH_mass'.format(args.channels[0])] + # if args.channels[0] == "tautau": + # channel_all = file['NoBaseMuTauMETORTauORTTJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # base = "DoubleMediumDeepTau" + # last_trig = "TauTauJet" + # if args.channels[0] == "mutau": + # channel_all = file['NoBaseMETORTauORMu50_baseline_{}_genHH_mass'.format(args.channels[0])] + # base = "IsoMu + CrossMuTau" + # last_trig = "Mu50" + # if args.channels[0] == "etau": + # channel_all = file['NoBaseMETORTauOREle28_baseline_{}_genHH_mass'.format(args.channels[0])] + # base = "Ele24 + CrossEleTau" + # last_trig = "Ele28HT" + var_base_tot = channel_base.variances().sum() + # var_all_tot = channel_all.variances().sum() + # err_tot_all = np.sqrt(var_all_tot / (channel_base.values().sum() ** 2) + (channel_all.values().sum() ** 2) * var_base_tot / (channel_base.values().sum() ** 4)) + err_base = np.sqrt( (1/channel_all.values() * channel_base.errors()) ** 2 + (channel_base.values()/(channel_all.values() ** 2) * channel_all.errors()) ** 2 ) + # err_base_plus_met = np.sqrt( (1/channel_all.values() * channel_base_plus_met.errors()) ** 2 + (channel_base_plus_met.values()/(channel_all.values() ** 2) * channel_all.errors()) ** 2 ) + err_base_plus_tau = np.sqrt( (1/channel_all.values() * channel_base_plus_tau.errors()) ** 2 + (channel_base_plus_tau.values()/(channel_all.values() ** 2) * channel_all.errors()) ** 2 ) + err_base_plus_quadJetPNet = np.sqrt( (1/channel_all.values() * channel_base_plus_quadJetPNet.errors()) ** 2 + (channel_base_plus_quadJetPNet.values()/(channel_all.values() ** 2) * channel_all.errors()) ** 2 ) + # err_base_plustau_quadJet = np.sqrt( (1/channel_base.values() * channel_base_plus_quadJetPNet.errors()) ** 2 + (channel_base_plus_quadJetPNet.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_met = np.sqrt( (1/channel_base.values() * channel_base_plus_met.errors()) ** 2 + (channel_base_plus_met.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_quadJetDeep = np.sqrt( (1/channel_base.values() * channel_base_plus_quadJetDeep.errors()) ** 2 + (channel_base_plus_quadJetDeep.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_all = np.sqrt( (1/channel_all.values() * channel_base_all.errors()) ** 2 + (channel_base_all.values()/(channel_all.values() ** 2) * channel_all.errors()) ** 2 ) + + # err_base_plus_Mu50 = np.sqrt( (1/channel_base.values() * channel_base_plus_Mu50.errors()) ** 2 + (channel_base_plus_Mu50.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_Ele28 = np.sqrt( (1/channel_base.values() * channel_base_plus_Ele28.errors()) ** 2 + (channel_base_plus_Ele28.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_all= np.sqrt( (1/channel_base.values() * channel_all.errors()) ** 2 + (channel_all.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + ax = channel_base.axes[0].centers() + edges = channel_base.axes[0].edges() + xerr_r = ax - edges[:-1] + xerr_l = edges[1:] - ax + + + hep.style.use("CMS") + fig, ax1 = plt.subplots() + ax2 = ax1.twinx() + ax2.set_ylabel("Weighted MC events [a.u.]") # we already handled the x-label with ax1 + print("genHH", ax, channel_all.values()) + ax2.bar(ax, height=channel_all.values(), width=xerr_r * 2, color='lightgrey', alpha=0.35, zorder=0, label="HHbbtautau") + + hep.style.use("CMS") + # ax1.errorbar(ax - 15, (channel_base.values() / channel_all.values()) * 100, yerr=err_base*100, xerr=[xerr_r - 15, xerr_l + 15], fmt='o', label="Base", color=petroff6[0], zorder=5) + # ax1.errorbar(ax - 10, (channel_base_plus_met.values() / channel_all.values()) * 100, yerr=err_base_plus_met*100, xerr=[xerr_r -10, xerr_l + 10], fmt='o', label="Base + MET", color=petroff6[1], zorder=5) + # if args.channels[0] == "tautau": + # ax1.errorbar(ax, (channel_base_plus_met.values() / channel_base.values()) * 100, yerr=err_base_plus_met*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + TauTauJet", color=petroff6[2], zorder=5) + + # ax1.errorbar(ax + 10, (channel_base_plus_quadJetDeep.values() / channel_base.values()) * 100, yerr=err_base_plus_quadJetDeep*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + QuadJetDeep", color=petroff6[3], zorder=5) + + # ax1.errorbar(ax + 15, (channel_base_plus_quadJetPNet.values() / channel_base.values()) * 100, yerr=err_base_plus_quadJetPNet*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + QuadJetPNet", color=petroff6[4], zorder=5) + # if args.channels[0] == "mutau": + # ax1.errorbar(ax + 5, (channel_base_plus_Mu50.values() / channel_base.values()) * 100, yerr=err_base_plus_Mu50*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + Mu50", color=petroff6[2], zorder=5) + # ax1.errorbar(ax + 15, (channel_base_plus_quadJetPNet.values() / channel_base.values()) * 100, yerr=err_base_plus_quadJetPNet*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + QuadJetPNet", color=petroff6[4], zorder=5) + # if args.channels[0] == "etau": + ax1.errorbar(ax - 5, channel_base.values(), yerr=channel_base.errors(), xerr=[xerr_r -5 , xerr_l +5], fmt='o', label="Base", color=petroff6[1], zorder=5) + ax1.errorbar(ax + 5, channel_base_plus_quadJetPNet.values(), yerr=channel_base_plus_quadJetPNet.errors(), xerr=[xerr_r + 5, xerr_l - 5], fmt='o', label="Base + QuadJetPNet", color=petroff6[2], zorder=5) + # ax1.errorbar(ax + 15, (channel_base_all.values() / channel_all.values()) * 100, yerr=err_base_all*100, xerr=[xerr_r + 15, xerr_l - 15], fmt='o', label="Base + TauTauJet\n+ QuadJetPNet", color=petroff6[5], zorder=5) + + # ax1.errorbar(ax + 10, (channel_base_all.values() / channel_all.values()) * 100, yerr=err_base_all*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + MET + IsoTau\n+ Mu50 + QuadJetPNet", color=petroff6[4], zorder=5) + # ax1.errorbar(ax + 10, (channel_base_all.values() / channel_all.values()) * 100, yerr=err_base_all*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + MET + IsoTau\n+ QuadJetPNet", color=petroff6[4], zorder=5) + + ax1.text(0.05, 0.94, 'Channel: '+ channel_text, horizontalalignment='left', verticalalignment='bottom', transform=ax1.transAxes, size=21) + # ax1.errorbar(ax + 20, (channel_all.values() / channel_base.values()) * 100, yerr=err_all*100, xerr=[xerr_r + 20, xerr_l - 20], fmt='o', label="Base + MET + IsoTau + {}".format( last_trig), color=petroff6[3]) + ax1.set_xlabel(r"m$_{HH}$ [GeV]") + ax1.set_ylabel("Events/bin [a. u.]") + lines, labels = ax1.get_legend_handles_labels() + lines2, labels2 = ax2.get_legend_handles_labels() + plt.legend(lines + lines2, labels + labels2, loc=4, bbox_to_anchor=(1, 0.1)) + # print("Total Gain: {} +/- {}".format(round(channel_all.values().sum()/channel_base.values().sum() * 100, 2), round(err_tot_all * 100, 2))) + hep.cms.label(lumi="9.8", com="13.6") + plt.savefig("distribution_{}_quadJet_preEE_10p11.png".format(args.channels[0])) + + for i in vars: + channel_all = file['All_baseline_{}_{}'.format(args.channels[0], i)] + channel_base_tautaujet = file['Base{}ORTauTauJetNo4JetsPNet_baseline_{}_{}'.format(base_extension, args.channels[0], i)] + # channel_base_plus_Mu50 = file['Base{}ORMu50_baseline_{}_{}'.format(base_extension, args.channels[0], i)] + channel_only_quadJetPNet = file['NoBase{}ORNoTauTauJet4JetsPNet_baseline_{}_{}'.format(base_extension, args.channels[0], i)] + + var_base_tot = channel_base.variances().sum() + # var_all_tot = channel_all.variances().sum() + # err_tot_all = np.sqrt(var_all_tot / (channel_base.values().sum() ** 2) + (channel_all.values().sum() ** 2) * var_base_tot / (channel_base.values().sum() ** 4)) + err_base_tautaujet = np.sqrt( (1/channel_all.values() * channel_base_tautaujet.errors()) ** 2 + (channel_base_tautaujet.values()/(channel_all.values() ** 2) * channel_all.errors()) ** 2 ) + # err_base_plus_met = np.sqrt( (1/channel_all.values() * channel_base_plus_met.errors()) ** 2 + (channel_base_plus_met.values()/(channel_all.values() ** 2) * channel_all.errors()) ** 2 ) + # err_base_plus_tau = np.sqrt( (1/channel_all.values() * channel_base_plus_tau.errors()) ** 2 + (channel_base_plus_tau.values()/(channel_all.values() ** 2) * channel_all.errors()) ** 2 ) + err_only_quadJetPNet = np.sqrt( (1/channel_all.values() * channel_only_quadJetPNet.errors()) ** 2 + (channel_only_quadJetPNet.values()/(channel_all.values() ** 2) * channel_all.errors()) ** 2 ) + + # err_base_plus_Mu50 = np.sqrt( (1/channel_base.values() * channel_base_plus_Mu50.errors()) ** 2 + (channel_base_plus_Mu50.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_Ele28 = np.sqrt( (1/channel_base.values() * channel_base_plus_Ele28.errors()) ** 2 + (channel_base_plus_Ele28.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_all= np.sqrt( (1/channel_base.values() * channel_all.errors()) ** 2 + (channel_all.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + ax = channel_all.axes[0].centers() + edges = channel_all.axes[0].edges() + xerr_r = ax - edges[:-1] + xerr_l = edges[1:] - ax + print(edges, xerr_r, xerr_l) + + + hep.style.use("CMS") + fig, ax1 = plt.subplots() + ax2 = ax1.twinx() + ax2.set_ylabel("Weighted MC events [a.u.]") # we already handled the x-label with ax1 + print(i, channel_all.values()) + ax2.bar(ax, height=channel_all.values(), width=xerr_r * 2, color='lightgrey', alpha=0.35, zorder=0, label="HHbbtautau") + + hep.style.use("CMS") + # ax1.errorbar(ax - 15, (channel_base.values() / channel_all.values()) * 100, yerr=err_base*100, xerr=[xerr_r - 15, xerr_l + 15], fmt='o', label="Base", color=petroff6[0], zorder=5) + ax1.errorbar(ax - xerr_r * 0.2, channel_base_tautaujet.values(), yerr=channel_base_tautaujet.errors(), xerr=[xerr_r * 0.8 , xerr_l * 1.2], fmt='o', label="Base + TauTauJet", color=petroff6[3], zorder=5) + ax1.errorbar(ax + xerr_r * 0.2, channel_only_quadJetPNet.values(), yerr=channel_only_quadJetPNet.errors(), xerr=[xerr_r * 1.2, xerr_l * 0.8], fmt='o', label="QuadJetPNet", color=petroff6[2], zorder=5) + # ax1.errorbar(ax + 15, (channel_base_all.values() / channel_all.values()) * 100, yerr=err_base_all*100, xerr=[xerr_r + 15, xerr_l - 15], fmt='o', label="Base + TauTauJet\n+ QuadJetPNet", color=petroff6[5], zorder=5) + + # ax1.errorbar(ax + 10, (channel_base_all.values() / channel_all.values()) * 100, yerr=err_base_all*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + MET + IsoTau\n+ Mu50 + QuadJetPNet", color=petroff6[4], zorder=5) + # ax1.errorbar(ax + 10, (channel_base_all.values() / channel_all.values()) * 100, yerr=err_base_all*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + MET + IsoTau\n+ QuadJetPNet", color=petroff6[4], zorder=5) + + ax1.text(0.05, 0.94, 'Channel: '+ channel_text, horizontalalignment='left', verticalalignment='bottom', transform=ax1.transAxes, size=21) + # ax1.errorbar(ax + 20, (channel_all.values() / channel_base.values()) * 100, yerr=err_all*100, xerr=[xerr_r + 20, xerr_l - 20], fmt='o', label="Base + MET + IsoTau + {}".format( last_trig), color=petroff6[3]) + ax1.set_xlabel("{}".format(i)) + ax1.set_ylabel("Events/bin [a. u.]") + lines, labels = ax1.get_legend_handles_labels() + lines2, labels2 = ax2.get_legend_handles_labels() + plt.legend(lines + lines2, labels + labels2, loc=4, bbox_to_anchor=(1, 0.1)) + # print("Total Gain: {} +/- {}".format(round(channel_all.values().sum()/channel_base.values().sum() * 100, 2), round(err_tot_all * 100, 2))) + hep.cms.label(lumi="9.8", com="13.6") + plt.savefig("distribution_{}_quadJet_preEE_10p20_{}.png".format(args.channels[0], i)) + # print(ratio.axes[0]) + return + channels = args.channels + linear_x = [k for k in range(1,2)] + edges_x = [k-0.5 for k in range(1,2)] + [1.5] + ptcuts = {chn: utils.get_ptcuts(chn, args.year) for chn in args.channels} + + nevents, errors = dd(lambda: dd(dict)), dd(lambda: dd(dict)) + ratios, eratios = dd(lambda: dd(dict)), dd(lambda: dd(dict)) + + for adir in main_dir: + dRstr = str(args.deltaR).replace('.', 'p') + if len(args.channels) == 1: + output_name = os.path.join(base_dir, 'trigger_gains_{}_{}_DR{}'.format(args.channels[0], + args.year, dRstr)) + elif len(args.channels) == 2: + output_name = os.path.join(base_dir, 'trigger_gains_{}_{}_{}_DR{}'.format(*args.channels[:2], + args.year, dRstr)) + elif len(args.channels) == 3: + output_name = os.path.join(base_dir, 'trigger_gains_all_{}_DR{}'.format(args.year, dRstr)) + if args.bigtau: + output_name += "_BIGTAU" + output_name += ".html" + output_file(output_name) + print('Saving file {}.'.format(output_name)) + + for chn in channels: + md = adir[chn] + in_base = os.path.join(base_dir, md) + + nevents[md][chn]['base'], nevents[md][chn]['met'], nevents[md][chn]['tau'] = [], [], [] + ratios[md][chn]['two'], ratios[md][chn]['met'], ratios[md][chn]['tau'] = [], [], [] + eratios[md][chn]['two'], eratios[md][chn]['met'], eratios[md][chn]['tau'] = [], [], [] + errors[md][chn] = [] + + outname = get_outname( chn, [str(x) for x in args.region_cuts], + [str(x) for x in ptcuts[chn]], str(args.met_turnon), + args.bigtau) + + print(outname) + with open(outname, "rb") as f: + ahistos = pickle.load(f) + + #all regions summed + sum_base_tot = round(ahistos["Base"]["legacy"]["baseline"].values().sum() + + ahistos["Base"]["tau"]["baseline"].values().sum() + + ahistos["Base"]["met"]["baseline"].values().sum()) + print(ahistos["Base"]["legacy"]["baseline"].values()) + #legacy region + l1 = lambda x : round(x["legacy"]["baseline"].values().sum(), 2) + sum_base = l1(ahistos["Base"]) + sum_vbf = l1(ahistos["VBF"]) + sum_met = l1(ahistos["NoBaseMET"]) + sum_only_tau = l1(ahistos["NoBaseNoMETTau"]) + sum_tau = l1(ahistos["NoBaseTau"]) + sum_basekin = l1(ahistos["LegacyKin"]) + w2_basekin = ahistos["METKin"]["legacy"]["baseline"].variances().sum() + + #MET region + l2 = lambda x : round(x["met"]["baseline"].values().sum(), 2) + sum_metkin = l2(ahistos["METKin"]) + w2_metkin = ahistos["METKin"]["met"]["baseline"].variances().sum() + + #Single Tau region + l3 = lambda x : round(x["tau"]["baseline"].values().sum(), 2) + sum_taukin = l3(ahistos["TauKin"]) + w2_taukin = ahistos["TauKin"]["tau"]["baseline"].variances().sum() + + #hypothetical VBF region + sum_vbfkin = l2(ahistos["VBFKin"]) + l3(ahistos["VBFKin"]) + + nevents[md][chn]['base'].append(sum_basekin) + nevents[md][chn]['met'].append(sum_basekin + sum_metkin) + nevents[md][chn]['tau'].append(sum_basekin + sum_metkin + sum_taukin) + + rat_met_num = sum_basekin + sum_metkin + rat_met_all = rat_met_num / sum_base_tot + + rat_tau_num = sum_basekin + sum_taukin + rat_tau_all = rat_tau_num / sum_base_tot + + rat_all_num = sum_basekin + sum_taukin + sum_metkin + rat_all = rat_all_num / sum_base_tot + + ratios[md][chn]['met'].append(rat_met_all) + ratios[md][chn]['tau'].append(rat_tau_all) + ratios[md][chn]['two'].append(rat_all) + + e_metkin = np.sqrt(w2_metkin) + e_taukin = np.sqrt(w2_taukin) + e_basekin = np.sqrt(w2_basekin) + + e_tau_num = np.sqrt(w2_taukin + w2_basekin) + e_met_num = np.sqrt(w2_metkin + w2_basekin) + e_all_num = np.sqrt(w2_metkin + w2_taukin + w2_basekin) + print(rat_tau_num, rat_met_num, rat_all_num) + + eratios[md][chn]['tau'].append(rat_tau_all * np.sqrt(e_tau_num**2/rat_tau_num**2 + 1/sum_base_tot)) + eratios[md][chn]['met'].append(rat_met_all * np.sqrt(e_met_num**2/rat_met_num**2 + 1/sum_base_tot)) + eratios[md][chn]['two'].append(rat_all * np.sqrt(e_all_num**2/rat_all_num**2 + 1/sum_base_tot)) + errors[md][chn].append(e_all_num) + + json_name = 'data_' + chn + '_' + json_name += ('bigtau' if args.bigtau else 'standard') + '.json' + with open(json_name, 'w', encoding='utf-8') as json_obj: + json_data = {"vals": {chn: nevents[adir[chn]][chn]['tau'] for chn in channels}} + json_data.update({"errs": {chn: errors[adir[chn]][chn] for chn in channels}}) + json.dump(json_data, json_obj, ensure_ascii=False, indent=4) + + opt_points = dict(size=8) + opt_line = dict(width=1.5) + colors = ('green', 'blue', 'red', 'brown') + styles = ('solid', 'dashed', 'dotdash') + legends = {'base': 'Legacy', + 'met': 'MET', 'tau': 'Single Tau', + 'two': 'MET + Single Tau', 'vbf': 'VBF'} + + x_str = [str(k) for k in [0]] + xticks = linear_x[:] + yticks = [x for x in range(0,110,5)] + shift_one = {'met': [-0.20, 0., 0.20], 'tau': [-0.20, -0.05, 0.1], + 'vbf': [-0.10, 0.05, 0.20]} + shift_both = {'met': [-0.20, 0., 0.20], 'two': [-0.20, -0.05, 0.1]} + shift_kin = {'met': [-0.09, 0., 0.20], 'tau': [0.03, -0.05, 0.1], + 'two': [-0.03, 0.05, 0.20], 'vbf': [0.09, 0.1, 0.25]} + + for adir in main_dir: + print(adir) + p_opt = dict(width=800, height=400, x_axis_label='x', y_axis_label='y') + p1 = figure(title='Event number (' + pp(channels[0]) + ')', y_axis_type="linear", **p_opt) + p2 = figure(title='Acceptance Gain (' + pp(channels[0]) + ')', **p_opt) if len(channels)==1 else figure(**p_opt) + + p1.yaxis.axis_label = 'Weighted number of events' + p2.yaxis.axis_label = 'Trigger acceptance gain (w.r.t. trigger baseline) [%]' + pics = (p1, p2) + for p in pics: + set_fig(p) + + for ichn,chn in enumerate(channels): + md = adir[chn] + print(md) + + p1.quad(top=nevents[md][chn]["base"], bottom=0, + left=edges_x[:-1], right=edges_x[1:], + legend_label=legends["base"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), + fill_color="dodgerblue", line_color="black") + p1.quad(top=nevents[md][chn]["met"], bottom=nevents[md][chn]["base"], + left=edges_x[:-1], right=edges_x[1:], + legend_label=legends["met"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), + fill_color="green", line_color="black") + p1.quad(top=nevents[md][chn]["tau"], bottom=nevents[md][chn]["met"], + left=edges_x[:-1], right=edges_x[1:], + legend_label=legends["tau"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), + fill_color="red", line_color="black") + print(md) + for itd,td in enumerate(('met', 'tau', 'two')): + p2.circle([x+shift_kin[td][ichn] for x in linear_x], + [(x-1)*100. for x in ratios[md][chn][td]], + color=colors[itd], fill_alpha=1., **opt_points) + p2.line([x+shift_kin[td][ichn] for x in linear_x], + [(x-1)*100. for x in ratios[md][chn][td]], + color=colors[itd], line_dash=styles[ichn], + legend_label=legends[td]+(' ('+pp(chn)+')' if len(channels)>1 else ''), **opt_line) + p2.multi_line( + [(x+shift_kin[td][ichn],x+shift_kin[td][ichn]) for x in linear_x], + [((y-1)*100-(x*50.),(y-1)*100+(x*50.)) for x,y in zip(eratios[md][chn][td],ratios[md][chn][td])], + color=colors[itd], **opt_line) + + p1.legend.location = 'top_right' + p2.legend.location = 'top_left' + for p in pics: + p.xaxis[0].ticker = xticks + p.xgrid[0].ticker = xticks + p.xgrid.grid_line_alpha = 0.2 + p.xgrid.grid_line_color = 'black' + # p.yaxis[0].ticker = yticks + # p.ygrid[0].ticker = yticks + p.ygrid.grid_line_alpha = 0.2 + p.ygrid.grid_line_color = 'black' + + p.xaxis.axis_label = "m(X) [GeV]" + + p.xaxis.major_label_overrides = dict(zip(linear_x,x_str)) + + p.legend.click_policy='hide' + + p.output_backend = 'svg' + #export_svg(p, filename='line_graph.svg') + + g = gridplot([[p] for p in pics]) + print(g) + save(g, title=md) + export_png(g, filename="line_graph_postEE.png") + +if __name__ == '__main__': + desc = "Produce plots of trigger gain VS resonance mass.\n" + desc += "Uses the output of test_trigger_regions.py." + desc += "When running on many channels, one should keep in mind each channel has different pT cuts." + desc += "This might imply moving sub-folders (produced by the previous script) around." + parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) + + # parser.add_argument('--masses', required=True, nargs='+', type=str, + # help='Resonance mass') + parser.add_argument('--channels', required=True, nargs='+', type=str, + choices=('etau', 'mutau', 'tautau'), + help='Select the channel over which the workflow will be run.' ) + parser.add_argument('--year', required=True, type=str, choices=('2016', '2017', '2018', '2022'), + help='Select the year over which the workflow will be run.' ) + parser.add_argument('--deltaR', type=float, default=0.5, help='DeltaR between the two leptons.') + parser.add_argument('--bigtau', action='store_true', + help='Consider a larger single tau region, reducing the ditau one.') + parser.add_argument('--met_turnon', required=False, type=str, default=180, + help='MET trigger turnon cut [GeV].' ) + parser.add_argument('--region_cuts', required=False, type=float, nargs=2, default=(190, 190), + help='High/low regions pT1 and pT2 selection cuts [GeV].' ) + + args = utils.parse_args(parser) + + base_dir = '/t3home/fbilandz/TriggerScaleFactors/' + main_dir = [{"etau": "Region_Spin2_190_190_PT_33_25_35_DR_{}_TURNON_200_190".format(args.deltaR), + "mutau": "mutau_190_190_DR_0.5_PT_25_21_32_TURNON_180", + "tautau": "Region_Spin2_190_190_PT_40_40_DR_{}_TURNON_200_190".format(args.deltaR)}, + ] + + main(args) diff --git a/tests/trigger_gains_refined_run3.py b/tests/trigger_gains_refined_run3.py new file mode 100644 index 0000000..eadda11 --- /dev/null +++ b/tests/trigger_gains_refined_run3.py @@ -0,0 +1,118 @@ +# coding: utf-8 + +_all_ = [ 'test_trigger_gains' ] + +import os +import sys +parent_dir = os.path.abspath(__file__ + 2 * '/..') +sys.path.insert(0, parent_dir) + +import json +import argparse +from inclusion.utils import utils +import numpy as np +from collections import defaultdict as dd +import hist +import mplhep as hep +from hist.intervals import clopper_pearson_interval as clop +import pickle +import uproot +import matplotlib.pyplot as plt + +tau = '\u03C4' +mu = '\u03BC' +pm = '\u00B1' +ditau = tau+tau + +def main(args): + if args.channels[0] == "etau": + base_extension = "E" + channel_text = r"e$\tau_{h}$" + elif args.channels[0] == "mutau": + base_extension = "Mu" + channel_text = r"$\mu \tau_{h}$" + elif args.channels[0] == "tautau": + base_extension = "Tau" + channel_text = r"$\tau_{h} \tau_{h}$" + genHH_mass_baseline = uproot.open("data/regions_postBPix_15p1-no-trigger_all.root")['tautau_genHH_mass'] + genHH_mass_base_tau = uproot.open("data/regions_2023_postBPix_15p35-base-tau_all.root")['tautau_genHH_mass'] + genHH_mass_ditaujet = uproot.open("data/regions_2023_postBPix_15p36-ditaujet_all.root")['tautau_genHH_mass'] + genHH_mass_quadjet = uproot.open("data/regions_2023_postBPix_15p34_all.root")['tautau_genHH_mass'] + genHH_mass_both = uproot.open("data/regions_2023_postBPix_15p33_all.root")['tautau_genHH_mass'] + # ht = uproot.open("data/regions_postBPix_15p11-quadjet-confirm_all.root")['tautau_SoftActivityJetHT'] + petroff6 = ["#5790fc", "#f89c20", "#e42536", "#964a8b", "#9c9ca1", "#7a21dd"] + + err_ditaujet = genHH_mass_ditaujet.errors()/ genHH_mass_base_tau.values() + err_quadjet = genHH_mass_quadjet.errors()/ genHH_mass_base_tau.values() + err_both = genHH_mass_both.errors()/ genHH_mass_base_tau.values() + + ax = genHH_mass_baseline.axes[0].centers() + edges = genHH_mass_baseline.axes[0].edges() + xerr_r = ax - edges[:-1] + xerr_l = edges[1:] - ax + hep.style.use("CMS") + # print("CMS style") + fig, ax1 = plt.subplots() + # ax1.errorbar(ax[0:10], ht.values()[0:10], yerr=ht.errors()[0:10], xerr=[xerr_r[0:10], xerr_l[0:10]], fmt='o', label="SoftActivityJetHT", color=petroff6[2], zorder=5) + # hep.cms.label(lumi="9.96", com="13.6") + # plt.legend() + # ax1.set_xlabel(r"HT [GeV]") + # ax1.set_ylabel("N [a. u.]") + # plt.savefig("ht_postBPix_pf75.png".format(args.channels[0])) + + # return + ax1.set_ylim(-4, 30) + # print("subplots") + ax2 = ax1.twinx() + print("generated additional axis") + ax2.set_ylabel("Weighted MC events [a.u.]") # we already handled the x-label with ax1 + ax2.bar(ax, height=genHH_mass_baseline.values(), width=xerr_r * 2, color=petroff6[4], alpha=0.3, zorder=0, label="HHbbtautau") + # print(channel_base_60.values(), channel_base_70.values(), channel_base_80.values()) + # hep.style.use("CMS") + print(genHH_mass_ditaujet.values()/ genHH_mass_base_tau.values()) + ax1.errorbar(ax - 10, (genHH_mass_ditaujet.values() / genHH_mass_base_tau.values() - 1) * 100, yerr=err_ditaujet*100, xerr=[xerr_r - 10, xerr_l + 10], fmt='o', label="DiTau+Jet", color=petroff6[0], zorder=5) + ax1.errorbar(ax, (genHH_mass_quadjet.values() / genHH_mass_base_tau.values() - 1) * 100, yerr=err_quadjet*100, xerr=[xerr_r, xerr_l], fmt='o', label="QuadJet parking", color=petroff6[2], zorder=5) + ax1.errorbar(ax + 10, (genHH_mass_both.values() / genHH_mass_base_tau.values() - 1) * 100, yerr=err_both*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="QuadJet + DiTau+Jet", color=petroff6[4], zorder=5) + ax1.set_xlabel(r"m$_{HH}$ [GeV]") + ax1.set_ylabel("Gain [%]") + # print("here") + lines, labels = ax1.get_legend_handles_labels() + lines2, labels2 = ax2.get_legend_handles_labels() + plt.legend(lines + lines2, labels + labels2) + # # print("Total Gain: {} +/- {}".format(round(channel_all.values().sum()/channel_base.values().sum() * 100, 2), round(err_tot_all * 100, 2))) + hep.cms.label(lumi="9.7", com="13.6") + plt.savefig("gains_ditaujet_postBPix.png".format(args.channels[0])) + + return + +if __name__ == '__main__': + desc = "Produce plots of trigger gain VS resonance mass.\n" + desc += "Uses the output of setup_regions.py." + desc += "When running on many channels, one should keep in mind each channel has different pT cuts." + desc += "This might imply moving sub-folders (produced by the previous script) around." + parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) + + # parser.add_argument('--masses', required=True, nargs='+', type=str, + # help='Resonance mass') + parser.add_argument('--channels', required=True, nargs='+', type=str, + choices=('etau', 'mutau', 'tautau'), default="tautau", + help='Select the channel over which the workflow will be run.' ) + parser.add_argument('--year', required=True, type=str, choices=('2016', '2017', '2018', '2022', '2023'), + help='Select the year over which the workflow will be run.' ) + parser.add_argument('--deltaR', type=float, default=0.5, help='DeltaR between the two leptons.') + parser.add_argument('--bigtau', action='store_true', + help='Consider a larger single tau region, reducing the ditau one.') + parser.add_argument('--met_turnon', required=False, type=str, default=180, + help='MET trigger turnon cut [GeV].' ) + parser.add_argument('--region_cuts', required=False, type=float, nargs=2, default=(190, 190), + help='High/low regions pT1 and pT2 selection cuts [GeV].' ) + + args = utils.parse_args(parser) + + base_dir = '/t3home/fbilandz/TriggerScaleFactors/' + main_dir = [{"etau": "Region_Spin2_190_190_PT_33_25_35_DR_{}_TURNON_200_190".format(args.deltaR), + "mutau": "mutau_190_190_DR_0.5_PT_25_21_32_TURNON_180", + "tautau": "Region_Spin2_190_190_PT_40_40_DR_{}_TURNON_200_190".format(args.deltaR)}, + ] + + main(args) From 6cea51549bdb0d1ad3c4675589cf6500a621ae90 Mon Sep 17 00:00:00 2001 From: Filip Bilandzija Date: Thu, 14 Nov 2024 11:31:24 +0100 Subject: [PATCH 3/5] Updated gains procedure --- GluGlu.txt | 101 + boosted_decision_tree.py | 69 + cutoff_comparison.png | Bin 0 -> 86953 bytes data_mutau_standard.json | 12 + dnn.py | 65 + extracted_param_pairType_2.csv | 5187 +++++++++++++++++ inclusion/DeepJetWPs.py | 37 + inclusion/config/sel_trigger_regions_22.py | 131 + inclusion/quickRDF.py | 81 + tests/trigger_gains_run3.py | 481 ++ .../trigger_orthogonal_gains_refined_run3.py | 157 + tests/trigger_orthogonal_gains_run3.py | 594 ++ tests/trigger_regions_run3.py | 967 +++ 13 files changed, 7882 insertions(+) create mode 100644 GluGlu.txt create mode 100644 boosted_decision_tree.py create mode 100644 cutoff_comparison.png create mode 100644 data_mutau_standard.json create mode 100644 dnn.py create mode 100644 extracted_param_pairType_2.csv create mode 100644 inclusion/DeepJetWPs.py create mode 100644 inclusion/config/sel_trigger_regions_22.py create mode 100644 inclusion/quickRDF.py create mode 100644 tests/trigger_gains_run3.py create mode 100644 tests/trigger_orthogonal_gains_refined_run3.py create mode 100644 tests/trigger_orthogonal_gains_run3.py create mode 100644 tests/trigger_regions_run3.py diff --git a/GluGlu.txt b/GluGlu.txt new file mode 100644 index 0000000..e0dd1a6 --- /dev/null +++ b/GluGlu.txt @@ -0,0 +1,101 @@ +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/992697da-4a10-4435-b63a-413f6d33517e.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/8a4e45ad-49b7-4e32-a533-ff93ff70348d.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/81eac07b-1a52-4f41-8f26-8ef5161934d0.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/f626dcfc-b152-4907-a532-17aecd460e38.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/78b973d9-f814-4db9-8f56-ee08a219319d.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/d87a4569-2690-491a-9b28-aef4a6130ea9.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/520bb7f1-7adb-498f-8923-afa5a28e3052.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/7133ca83-b800-4d7e-9511-33e16f491c88.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/e46965e3-6aab-4e8a-8492-7e7b1ba1f322.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/4bb7df73-143b-4baf-a6ac-01de17776023.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/0a2470fa-92df-4c0e-a1fe-6ef046412bbf.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/b053dd11-54b6-471a-99d8-98f42795adfa.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/57624ab3-f418-461f-bd63-0f9462bc02ae.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/acd52e7e-ed48-4325-b32d-262ddb336aaa.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/1648d342-49a4-4c6f-9854-abbd0dd20107.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/3760d1bf-4822-446a-b68f-824e80c6ec69.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/1264b81e-ca00-4790-86e3-ca6f9712964f.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/c6f99ccf-adfc-4702-a9ff-5067439e8525.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/d1791fc0-e9c1-4a7e-afa2-5314d58f48d9.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/151e2c3d-dc6b-4e91-97ee-b23b940f7394.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/f98a8f23-7f71-4946-82f4-7f6d98b79eaa.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/f848324e-8c06-4a56-a31d-103e0cd80f6d.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/f876776d-c35d-42e7-8703-62977b7c2126.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/11f59655-9cc4-4351-a018-3ad8b1302101.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/50ae38e7-c844-4eaf-b0d9-d68d62919504.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/597192ec-8c80-430d-8201-d70c75c91dcc.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/d958efc3-b7c7-4d06-a94f-8b9695be7788.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/0b28e75e-f2d8-4ca7-a4e6-2ad728eb4dac.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/75f7a385-546c-4db5-92c1-87ad0c359c4f.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/bcf455c7-ec61-4f02-95e6-74fa42f814fb.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/91605215-6c95-4c2c-b5aa-74e32e1a0fe6.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/5a5e7a39-00fa-4f86-8282-8e1919b5a05e.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/eac83039-1574-4554-b210-0e1b6093918d.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/e1ee4e21-eec4-4cfe-a446-bea08ae7fc5b.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/91bd1de0-d322-4869-965f-328008d8d074.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/090155b3-0e4a-4020-8bae-40d69402420a.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/cd2a69e5-b995-4086-9f5b-76fca6c1f321.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/279a23b6-ddc6-48a8-bc3d-4b648a8ae7d9.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/c360f597-b2e9-4533-a351-1b8413133177.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/662fa958-acd8-4e3a-8eb6-0f1da543fabe.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/a8acae5a-fd0b-4b67-bc02-5c78e0e264b4.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/005637fa-982b-4b1c-93e4-73c493e19688.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/220eed97-8662-4b2f-b272-c615721f3c29.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/3f886598-5ab3-4d3f-a758-4be0f3ed7fd9.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/2c8f4f1e-5962-4d9b-8162-14fe1c6fee54.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/17ced396-61d4-4ce6-992d-b6d5c3b782eb.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/68752110-d30c-4884-94c0-8b3187f8db48.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/965bb8a7-e795-447b-ac6b-2cd921972905.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/d6c5e92a-d66a-4636-b4f8-b0eba913a2c9.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/4ee9ec8c-164b-4225-88ab-797528ad4209.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/e8febb18-c0ba-4c4f-8e63-1ebc717f29fe.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/aae78759-58a2-4e95-884f-ecc0ac214f3e.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/8b4e1c11-b93b-4da9-93a9-841f70cced21.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/6bec652f-5b1f-464c-ad94-3b50c27b515c.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/01886557-3793-4af5-bb7f-84b52987ccab.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/5b9e8f88-d845-4406-b0e3-a2b75ada15ad.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/021eaa44-5b83-40dc-bd8f-d9013755f017.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/b05b9929-9e00-4967-8de5-7ecdf72a949d.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/30723082-ffe8-44f7-acb2-9b35ea1f76f7.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/2df71f4e-2ba1-45e2-9780-c820984e262a.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/26c65654-3c99-41a1-b033-f7d42b1d271e.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/f42c5fa3-f49a-4170-ae95-d1d370d877e4.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/ef03d901-5a39-4855-b0f8-09bcde17b68a.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/8aaffb0c-24ab-4d70-b6ad-7b40ae21e7d0.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/54682498-9067-4fe6-829c-25e0e0a169fa.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2540000/3ff8ce67-039d-4939-9d6d-739aec4267e6.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/0adb440b-ac73-4fbc-be19-68b4ee1c0025.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/43871aa3-162b-44c8-b51f-042abf7ee4be.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/ce5c53f6-0098-4529-bb00-0659952f4eb1.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/77a1eaae-80cf-42a2-ab4b-74d0338e2b22.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/6f44ba0a-1f72-4d5e-910e-f9a0d6b161cb.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/b0a11ef1-3647-4f64-b132-7888ca03f4d9.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/7228d0a4-d984-4279-86a8-51d1cb0e26d6.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/cf9f0565-4dda-415c-969b-12739ef6c9ac.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/0847efd0-a5ba-442d-9a52-594212f9b65b.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/6ba373fb-8267-4cc5-b084-e7a0680f2632.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/343e1f54-bd34-4987-9717-ae44317a99aa.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/830fa5ff-1f70-4c16-a68e-b5e8ef371b1e.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/9342295a-d891-4cb4-b16a-0da32f3af027.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/f25519e7-fa95-440e-87d6-e736a8d1b9f2.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/1f48a225-e09a-46e4-bb75-5c780dd537d0.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/85ab76cd-0611-46d6-b17f-463719993a15.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/aa444a4f-6a08-40f9-b37a-91d20cbb289d.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/5b6f40b5-771d-4420-b308-2dd292b9a5ca.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/e1b86369-a394-4a16-8902-9bfe2bdd8ebd.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/41183793-1eb4-4290-bc4d-a790ff8f8fa4.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/b3719044-967a-4bec-a52f-abf1a43537a1.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/c794410c-4bfe-454f-bec5-7b81f54fd5fd.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/5e90d949-62f5-4d3f-9f11-d64d80a41aae.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/9888b348-1ea2-4ac6-9805-73d349850854.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/702a5554-375b-4a68-ad4d-9bbf647df554.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/e655040e-67de-49f5-963c-46e04266c78b.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/98bcc413-7bdd-432c-83b8-1dc8b0025520.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/50000/7204c422-7fc9-4fd6-b37d-988d0954444e.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/8a73c033-214d-4fa2-9aef-5237ebb01f2a.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/48089e52-8505-4989-aa20-1344a45e14d2.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/29e0168e-b744-48d3-b8f1-ce5b9cf6b88e.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/e2745063-7a43-44fa-bad1-7c45a7bc66da.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/b11b167c-15c8-4e7c-a439-6b6f883c8ec2.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/51a09224-de30-42ab-aa02-aec998f9a47c.root +/store/mc/Run3Summer22NanoAODv12/GluGlutoHHto2B2Tau_kl-1p00_kt-1p00_c2-0p00_LHEweights_TuneCP5_13p6TeV_powheg-pythia8/NANOAODSIM/130X_mcRun3_2022_realistic_v5-v2/2550000/fa4b6af7-1c56-4b2c-9edd-834455750122.root \ No newline at end of file diff --git a/boosted_decision_tree.py b/boosted_decision_tree.py new file mode 100644 index 0000000..88cac69 --- /dev/null +++ b/boosted_decision_tree.py @@ -0,0 +1,69 @@ +import os +# Keep using Keras 2 +os.environ['TF_USE_LEGACY_KERAS'] = '1' +import locale +# locale.setlocale(locale.LC_ALL, 'en_US') + +import tensorflow_decision_forests as tfdf + +import numpy as np +import pandas as pd +import tensorflow as tf +import tf_keras +import math + +dataset_df = pd.read_csv("extracted_param_pairType_2.csv") + +# Display the first 3 examples. +print(dataset_df.head(3)) + +label = "cat" + +classes = dataset_df[label].unique().tolist() +print(f"Label classes: {classes}") + +dataset_df[label] = dataset_df[label].map(classes.index) + +def split_dataset(dataset, test_ratio=0.20): + """Splits a panda dataframe in two.""" + test_indices = np.random.rand(len(dataset)) < test_ratio + return dataset[~test_indices], dataset[test_indices] + + +train_ds_pd, test_ds_pd = split_dataset(dataset_df) +print("{} examples in training, {} examples for testing.".format( + len(train_ds_pd), len(test_ds_pd))) + +train_ds = tfdf.keras.pd_dataframe_to_tf_dataset(train_ds_pd, label=label) +test_ds = tfdf.keras.pd_dataframe_to_tf_dataset(test_ds_pd, label=label) + + +feature_1 = tfdf.keras.FeatureUsage(name="dau1_tauIdVSjet", semantic=tfdf.keras.FeatureSemantic.CATEGORICAL) +feature_2 = tfdf.keras.FeatureUsage(name="dau1_pt") +feature_3 = tfdf.keras.FeatureUsage(name="dau2_tauIdVSjet") +feature_4 = tfdf.keras.FeatureUsage(name="dau2_pt") +feature_5 = tfdf.keras.FeatureUsage(name="pNet_sum") +feature_6 = tfdf.keras.FeatureUsage(name="bjet1_pt") +feature_7 = tfdf.keras.FeatureUsage(name="bjet2_pt") +feature_8 = tfdf.keras.FeatureUsage(name="bjet1_eta") +feature_9 = tfdf.keras.FeatureUsage(name="bjet2_eta") +all_features = [feature_1, feature_2, feature_3, feature_4, feature_5, feature_6, feature_7, feature_8, feature_9] + +# Specify the model. +model_1 = tfdf.keras.GradientBoostedTreesModel(growing_strategy="LOCAL", num_trees=1000, max_depth=5, + split_axis="SPARSE_OBLIQUE", + categorical_algorithm="RANDOM",) + +# Train the model. +model_1.fit(train_ds) + +model_1.compile(metrics=["accuracy"]) +evaluation = model_1.evaluate(test_ds, return_dict=True) +print() + +for name, value in evaluation.items(): + print(f"{name}: {value:.4f}") + +model_1.save("my_saved_model") + +tfdf.model_plotter.plot_model_in_colab(model_1, tree_idx=0, max_depth=3) \ No newline at end of file diff --git a/cutoff_comparison.png b/cutoff_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..a68c7291fb6db3bdc6731c3d1d700f3deb15f652 GIT binary patch literal 86953 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 z z7-T(vDtzl{Vs9bAe;XEHe~S5Y@teo*zfH7%DwcDp{j2gxG)zeH3R!O90$AL-Jn|a6 zbpvtnc#nJ@)VU{SaD@n3U)R6?P&-sN5xGROYW4I@?$v6p8oCTzjf0oB|85dLEWkxI zH42NaG)r~io$jg2!y^jsp7zyf-Jy%}@x;mr?%{i#+nW4!DArey=sbMSAx;Ozu7ei` zS|~15u){$=4PwD>ZV90AG^#BFGQaGBdf=2 ztiNs;{QP+(`x6U)E^6t1%kN^yN%n3=HshX~+RE{B!6VZ$bBsaynNaj?MBS1UcW_z6dzR|zu4Q;241(Tc} z!mF_6HN8)TTs{7$4OmhXKNuZMrz%9EiI>XS?1aRM z+)5f~fMG{osbJM2xck-2A|T{g4x=sou-hj-^I9sxBwIOI3NU8_g3WHM@>+NwKNE0D z6$s3MMB?KZm%|92AXzGwa-eg|0XbFy)CK5VMP6?N4DIcH+qL%)a6DqtvF8EH^CFWX zDKfCOYa@7>CQzGjLxfxL*F3%!U>?N$BFzc*w_AwWrGY4D$Si-jwg>|LzYIVgEbw}y zpKO7hwKi#_TuB1kNfAQvh-h%1GzH-h0+9ZZgc5)8nMe;aK>UHrSGr#r9D?|>D%A7q zF-IM;wLJiiwUHu9)C59*EZ~Pj?{y80a{JxybjnfYS)f_#42>$88oJIz(WC!(YV8o< zS_{AzZ5_^+(F}Zp;*5OszbaidBhY#OF)b&^w*reYU51GJF-h=oaVG(>3e)S=1c z2w|g18#H%-@=!2A#HXIbr_gP~S`Vj5Z)1)&B3kQG&Ai7(`X!hMFS&!vsB5~T zy~PXgTUhlfM=0`SFdMKkm%q|Ca^__@NzfVJ^9k~cPE>cSNJ{O2meA?_MNT?{-H1PU zh3h$lqEJ!MpneNNn_!r97x63XxH;*KN=f)NDR&lYo38ulNE{L z^M;AKE-G$lBSe^Jk;3TCb$4fw;~o;uSK!nm&~GcUDE$c4foQ+$6x*}^g^LQH*bDZ7 zqeRH+7h<1b31nGAS>ekb=LGh^w}2%rx5VQXye8xWWJN-}N>QS$0n=3<>5FJMQaR-x zqW+As-~(6>&p9iS7&vpeje=|7#y#;RH(E}!8>s%N1JU6~+W0FtIJlv)vBJgV zIc`G8K$HUfG4J&xoo>;aLSimhc0M~~WKc47U*m3c z152ynHJ~hUmYLWOH2ZC-Kjo(^dgfsh-C)VLe7QUnK~UVb$;B)84=k9}SfrnMVjByK z;B^xnLqkKR%=RtxT`kWLTT|MNt7CfbK9$pUm^c&pAn=cy<+~Bw0Xw|ktoqZalzHx- zB$w@FSJN|l8h3-FpfDoy+OJP#JNAhSSjJrFFZrcdG<=fKP`rP#Y#?zI5FDB#g9gq= zVI~Q}*NFu?Rc52tX4%Ls*Z}!wt_hyL^3x)>`8HmVpHy$I?=ZTl(2 zilmAL&%dEAmtOqK)7+R0Y(y+yipXKc<@+eicVH-kf`2OtwHTnHOiUJzDhX+{-N^nX z<#6UB`-Z&zHXOyumPdr>+6zasHL|1sd{o4{pu6WPMuK!6sOjjod@}i zrL?YB*H6p#;h?LP%bYq^pt({4$lI2SkJiHs=2K;oEGN?w*7bY7ZQjJYx`h+8ms0J# zwFr13C7+CbW-aicQmG|eOR3C)a%{g3w+zGxyFpjgz+X}7jQf2IPd40X?x$Lh+u;O+ zyiYv_J>~=Ji13poLDwzdsc`%TS(w=Y@!?e8t6U7@utwMBv7v=n$IbK><2)=5MB>_# z^+ykR4B|7pF*N2C{QpZmY&)yXCIde$bXdFXRzLQMZ)R%iqCC{qYy}?r5^V#z^6|e?Y22ceL|2~Eu z36g4^e_m|3^OacCi2z*NtRIMt)*S5#D-v3#sL{yZ<=em%cE?!R?<$DS?RWkv;rI1K z%ILk=H_2kI5yWW~5Uqd!&MKsXd&a;%ccc}Q)cfICzUdj58UhgY3Y zcH_B(JW+y4N;e2lmsVrcD#AQo@G~KW(g_U=4g0-g*CW7orMBZFgJLrRpm#B*PM6}{ zs7DoGQebowBDtRQtbR8HaH>rkV`}0*NB&rU|JlWdmFD9-j!$<-XC3#>jO#Xhd96LF zCH7!I@&ys`$Ntf!zF79%Z61td}>(}Oa_6cMuD}gOS!#JiyDGjb;or0L(tp-&Q>QE{rQ2Y8N#;%e- zDRa8^^x)VXw*`q78`u1!8`96Emga$SDzg7QH)u+nRF#tH_Zg|3DYI9u0o=<`jv;|)qJy_N|34}!)t*V7)rXXfQfT@bbx}eP8%r^il$9bdH%!Brf}2*OP<3g84o@_950h z;pMjijMwAM&uP=4CUk~Dm%FRT0G=1Xs08znjh|o~Vm`1X z$AXEYC?lG}#~I)mkyv%C3TQTp@U6;Wa{6HSDu6kVrj^d2%9dd$h&!$czU@zh)lcaD ztQ~PZP8xzArtsGaaAi+4n0^X9nzpl@w*P%IQ4pYvP#4wbW>&2Qd*9&>>bxS6ml@^y zPx<3$eXJ6n4f{wyw$AsUULC zxM;}Y)sMEmzEy^XygH&69YhH-x-9(?7SgEFM$I!(^GmslES_YxnAyiBJ#_QHya!Yc zJ2v2DkHly|*}B77pSVn;cF%5^KkpXcBcv8KJ)o`-4+wRIMniw%6Eo(atuwwJW;kv1 z%V`M8TLz_r*PB00v&X+{;X)Xz{G7$luJQ(h#AGu-EnNSl4gBw&r~ekwD%A9QM}aTw z2q;V^NDzkm!?hE*K11-AQTC627-fHAQ#TnC$lU+-5_csdw$z58Nm{493~=XbUGQd{G{J@=|hmqbK<-nDx*q>rWFGBZyjV^5n?!ei0S06ddE zDzM1AyZpI;E5w8=SpVQ~!1&u_R?Ho4`{24Bec!Hq>vC3vSi6`-_rkD!AWG4{c;I>s z%Vcv(4{=>Hmf;ExJoXUNO%b&fi%q!wAviKidqT<)EyAt&QsM7qfxmU5Y_}?THg4a$ z_Pn>xo5hob^K)) z7`-kEG4F@<>g0iHDe3C>-+~+M+Cv6bH)oPkaDxop=8rz~t$GW&NSw8OwPVd{6Yb9P z_WD<{PWGQuQP!8(ueI!+c16NA2|L-n;;&yTBJ*G!kDCuzr&U|swtDFScM!UL7$ISSH)TExH%yoh=Aey#Ni#^4})ZC#w26(;;39vb!i+ zIV@9|bMcm1tpAO$J8x8j>o$wmE2~$XIdXWRYP2WhRZ03pz9UIv!*5uh!~Z%0zXwMO zJVkK<0ny^h%Ou@0({BZ}ax+g3bI3V8;E%2R%dJ=}Jn4bhB2A=4KoY^vq}3-mc`hGa z`;19|eIET0X`p3LCF3}jBP!#%Gp9Oh$FsM|vzwot`ANY)vcgyayqfVZM)Lm|`1!{Z z|BsghJn5r%Bme)`WCzLMQmse1+q5QLav~VQX|NAJ(a%0(I*<5|`+`INDvVB5Axscj z?f=!T{{LcifYgCeX!{91nzX0l%q5NaKEYR3F#m%RjQ)2hHQ{1~IgV)iv)}&{==!g} z_kUgl|8YwGuU?`zBWH>b4^o6M6>?74giIyVo#H*Qognbx#y_PYj2Q($n-I@kxcz5L zH-lpXzI?+Z8O|NePD{-DkowcIsrTC{6MeQ8>TY;8x-a+1PnJxC`~P#5eZ;|DN;8#F zbFjyA{O3h1?zymvbiMz%h@PmwqI@_0Muk z5mGi)|3aBQc9c2rOe?~9v5oE88CeQ=P4c`~SKI*rhRB`4{2N&hzypE)$ka62c8d50 zhR-Ex2T-mc>y4i|SMEzVtP9___swHG;zGm%xy_iYk>?9cK#e0}?O#dQ!(wT2lQC`S z$ZV&1K2A=~&>+mbCrBUa{uT2s=lRTkg}e|AKyBOmuG*fJV-Cb)HkV&H4#73Gt` z2*L&gPL86m0I`q?o?t?lK=cc9*%!EB2C}Lq!GXi##~5nJY?ak0#1(5oBTU(dj?2P; z$tv)5ki16uI8!PTqfl%z4q)B6Jk!!8xnwIu&lJ=S%?q_fV83GEe@|PJ&8Ge`ccIbe zgkXgR1}7s=J{o94C#na^%mxlf32RCSYhclA7%~0q^}1GIdXdKl@vUK=A$^@t(t-1R zEhzj}$4iZ#zKOvQNBbJGAk|qP<^b3VT3-!7h!hbBcx%g#uY;6t2xaeGee92D&f2mK zG2kX>)!rLE-DC8D?r9KeCLuq7W-q2RAopMx5$QB`lEh#nLIag;dZ6j>J)zB;H`jcF z)1%BzpWZ3&1m*07g2TlAr6IwbSx$3_%@L*yhK^%r4TEFHKR4Ty1G9=uCHn<=VK$Ovvt3#1uongI1(W@x_zEq|Es`f zliG5j5qZ(rs0=XvTA1O*b3FhnFio>`9+;@ggAM7m#0TAuNrUGJFdeam^*+Spx4 zg3+0Rm?fG4k^i~(-ki?Yv9FK9h|y*fR*dS_H2YIMu1{$$mf zL2U*avb+LL#>-fUX-i)lHioK!K0UrH^|QCz*BwxwFtt}nOKYLM zA%sIfpufZ5_Rha2_X;xj-!$?xjr?_GuY<(du>Cl?&Nb)yT4zTn)84}R)$HLoB^M_ZAEOD2#0PUZb=Dh03?dzK=-g?`sxE;P= zVQD#bZcYPL1@N7szuWsKmI_L*qLnuI&)yl~&Hr!e?Y&doQ>YSKWQ zum-R-@H_D+z}emnas4>Jp2hX`Id^krgkwpcWoPXpx3_-w6=|7{K$mYLTk^#<8;3`p z`Nz&xR*5pm-AE<4YQmax1{YvFQGv&k%nb4#U1Qu`joS~V3t}RVkrempxavcGI-joh zxbdd9o%wvubIWh1qW8OhP``MJ+6P7fxH^VU4Bi!IU=WnOUiQ@1hC0>K0aiizp8NOh z79v|Q%u|i;yKtC$cAUR2@{XnVuyCL7qZQfxauP%S-cxK8@}Ww(2@m3ut*2UJnnQ0U zY6_2nmHC+`%j)Z|kbmN`*gv+wi69#k0skc{j!2(((lnhjEkkG5}nG&|BzdjAZKqdHb^wpIVW;=GlzY5<8C)W4`MR7itDS6*iwVdudbF zpkmtQ+7{|8@yEojFD-v=qB5E7GojWvG=;wd2gzW(Vf=c@Bdcoh{o(;iKrIbk%*a{D zW~3f%-SbLf|GpS?Oc1!!VZ?rm>*I+Xm0{`5o*l$sa4% z_fazl8nBq}BkX8D@Asz^*oH;x9yQT(F72Sq{(D_WdeneyT@@%e6daLo>Ij1!025aO z9o=P1deT4jUL_L;9t>ySsHiA&_-L}*f-HP*-qh=A;U=R% z^M&z!aBfH;m!7Ex{Q;%%<9`&Po+-zYfaGs6CRtn&T=72Lo73R*Ha0gxo_~ZLtL@$m z7sSR$Fq8(U(#YzLuZzHze!BMvM%vT4X8VQ7WlbN{uxc0wM1#TUFUY|&_N0*>Gt+)Ra54-< zs1gecH;LOCPgN+-m_1t)*Y24&Z!#2tN-Q`G%&Y7&;Eg8}CY$6U|10ic%z~wCl|+;X z8nBfI@RKhAT&GxPo3Q%Nn?D)J5#iuMyD}brOQbkf6BCn#@r}gL0qA6^!S-N>$WG2X z75A-l3Hf6E6gW3Vd~>Ex8nZw_)A$pjLw6tFR(+z@2Dj*LA&355awPTvfFS@IFP ztpVRL7E9>s#Zj(_=j~B^d1$~lB$6C_fm>%lr!2PzNaH}Q0vMp&`wkm;x?V?IlB%W= z3|cx`TK+huRZJ)()gi&Dk^9ixypWSqkPQ!~q^6$ns(JhK$ul635ia;@Lp3$Ed-bg- zSc1!(`b>}rGf?+jQ(Hgkg?LVh881z@u93rR*91>b&y~;LH7OiC7=%$A>PPk6R!-c# zr)dEL&Eoy=!v+wA{KA;7fa@@zlMGE$b>;BP>p#FHGmCS~oQxn-GZxDkxwh5gN@v7_ zYglAd|Huqe8>wIxwXVw(!Tug&zXP5T7Oa^12 zh@_-CmVkv@W+e|?*Bg0S69Dp#Le_%3`kL}KoXHFbu;XpLI`!N9)>9kPak!V4O=pDv zzR7#B*x0RiuLmUCBg=m9H5zQvnyvO(*ty1F+H;wJ{7&Ds;_*$f>amwE$BD&M><`VK z&hR}Cmsvscj23&AzyB%{nHI0fReIjf zFTH1c-;VV@vt{r1Y#aWLy!Ep8HD!)})jiv;{v^)A&?(51$0l>>nzE9TatOK7w~83V zCKX?cRQaa9%bUUIN#^IY5;;z+E39{ZES8#aNoOrT59c5uLlvI)^5u=_1W^xG_gqx zA!kHb1@HsdPQfowc+{92xopv*Xvh?zVq!Q0^>TQ(IrQU(P<#DHcCuVCH=dVwtcr~; zJ9f>zb>L-7b!A^d{Fc!8uoM-eb`{P$!OQe@WpCY*8@JgIc}U^@!An1K5-xQLiitR( z_PyfcHndShLI`|zSJ1*6> z65W`DT_Z=pF{pa~Om8f2cd&P{A}K~rQ1H3M_|2OvOlm-g$IwCZ`M2c zOQAhFAGNK`_T0VweJuOA?b)-FAV^nooUP2N93AT}wmZYwoMIf4*RZlxpmR>h?AZ6| z2Cqg33%Vq3*@;wLPS7!Jp2Bz(jBTm|HED#NcmM);!%+r0CT5VL*3lu54D<>*;B1Cq zQ&D8$-@m&y)V>K~Q3}x)?k{uz2cEd@QvNOMvt1B(Y92clM; zz|;6CW@eQpu!pL0C7OO+1-~K~96mvlmjD`lit30x5WOsxPHgNzN zXyS5Nk-#u1u-{~0Oz8?$Z*TMlJ?3nABOnngJtw9zN84adpTF5A&&s2N-WNYt+}Oe*Z^KB%QVvTawQg&)OR5J=IDrkIg?bR{N|7>P`N?b6CmvoP|2il1pA&h zE0~lTSZ3S$Vx|I4`<{JQEOeoloJJ2vRSny&GucUdh^Z0fP ziHVUbLGA2DDvrR0E&TJ(cj?|_Rwybe8moW%JsPh*@CQcN;Do>=a_n!zBRW*~gwLOm z6@B&GL8*$^_hoxnPk6t%&G0&&bZ}oxm#LXWRa`$m6Up~}w{IV_ZQ)_4uCUI0Kb`cl z$8(4M5fT~+RKM{3i91kd5aYGX?dwStcARyzy}FLqzwM$?sT4#9(LPmzbowyekBIPT z)5rE%O_@A-2qR>V!mt~J4cHa&=ft6r(-bFn8HP+?_(fuZp;IiC!m2v5Zd=sF?$j={ z`8x3C{g>gB@i0Mm14v{@>NX%kSRCVsPpFZ*FlNE7-N`tqs4*_7k*&*<_KPE?pF9fUsD%ZeB?* z%1pZ!Zg~6Vz8Ksn`(NkNpBP`y{S4XsXLZkR0NhuXwXUlk@V<0aPn_?1u*H^u(f%KW zEp5fEHaca1HyWDPGQM!LJ^kT|^>^z#A~YYa>PQxo?mB%s$|>2gqK<3dX#Vd4hV@!o zzxPd#N6x7T$D&1X-AUtD(MsvPjF^5x_yZehj7ylEbG7M9ag;jOL%AB$vem=7>$N5c zy4?@mRQ+{^Rae9Q_6DywCAHnpbi@qv4vvrT3MH?Yl6m1IwG?k&23|K`xMixwB=LZu z*Tr2do`+wVbF`D3k^^sMd{x@geB!H9OT+ujyWC80x7->co7ML5w#qYi?yS1gvEorQ zEl%#eCAw*MZeMa=y<8wBX8*~3# zSi7yc(d%hl7bDF$PQMI(S!0--4ew6D|0(WEz-nICzS}l?Z(|$Tu~BG6MWqZ$wInK) z29zd>lBr2WG-Dqk5laK2qG%p8YTgu4Q7S7`qEW48&ExmGSN8e7>pSPX*ZY3wd#|_a z?0xptxYqyw{GaE(fA?=FJ!`+t`r~?-jlZ^BbXqh)kc#31a(9C-C!Ww05B-{9 zm!3U)p6~B_^*5|z#X6K)3T}?Np1iKm`2Nw$L_hDdfn;~>8yBGbtEA{XRPD2 zjGf>~p2o>_6j6qs{933oN1@B+)7x&T>?P_?Mi1Zip0kA4F{gai`q0W(RI>lj&Jr)^Rj!{a`NRD$7J~i8SBPhe(Bor0&b$J z_H#n^bx#Jip8XFR)PLQ>DjrbgI?eRqldf9q5Kq1!$8rdOh;-UNCTJP-5d z_NpHSLlP}Q0B}PJSLHUC06qz=lKFIIn?ykL15YlPDKc$duX+R7hXE_>LnE|WPOd7n z4SoyGb(dFFKt9RhcDE6K+flmu^37G+QQE=G!j~^~kV!@U`=rcXv}lo0y8Xkr^rqj{ z%CLd=fs3Hub(DJngq6>msO`MFX&0Ca*BRuM#1&`HpC1YXAqv!^RuB#s3Y(#FjyJZC+o{k*yNkal=bTZ`_A zW&G2o@q~!2*cv|-2mO(yb34)6+|}wl$U#MtG74I(U(tK+MY8y#Dn@S#)oAv}^>Srr zwj~z9$RPxDNb-4Vlv>ZDIL-dzR0xLS`Oh%du2lrvHS+A-A9&ghLqr36)H{{Z5)#&1 z7+?Je4UcUEe86v%hB}BW#jXi%iL0p4MY}od#JzlN=rCw2H7SCYWJj6x#i`lzHX-&X z1un4dN*onsWv661(sbchH?I43o8p3-e&&2j4=~g`-R(s-l`hvm?60-KdWBjc({=ls z>jxSe8jP&L+DJgOftcBoh&qYftX2I z_3OLS0ATU(oS{opJ)b~pexpi-V@!AwlsKbZ1{ScU?^cjozoF)>wf8I9x%V¾W8 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', [120,]), + # 'mhtnomu_et': ('>', [100,])}, + } + +# which triggers are exclusive to a particular channel? +exclusive = {'mutau' : ( + 'IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1',), + 'mumu' : (), + 'tautau': ( + 'DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1', + ), + 'general' : ( + # "LooseDeepTauPFTauHPS180_L2NN_eta2p1", + # "PFMETNoMu120_PFMHTNoMu120_IDTight", + 'IsoMu24', + 'QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65', + # "Mu50", + # 'METNoMu120','IsoTau180' + ),} + +inters_general = {'MET' : (), + 'EG' : (), + 'Mu' : ( + # ('METNoMu120',), + ), + 'Tau' : ()} +inters = { + 'mutau': + {'MET' : (), + 'EG' : (), + 'Mu' : (('IsoMu24',), + ('IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1',), + ('IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1', 'IsoMu24',), + ('QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65',), + ('QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65', 'IsoMu24'), + ('IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1', 'QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65',), + ('IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1', 'IsoMu24', 'QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65',), +), + 'Tau' : () + }, + 'tautau': + {'MET' : (), + 'EG' : (), + 'Mu' : (), + 'Tau' : ( + ('IsoMu24',), + ('IsoMu24', 'QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65',), + ('DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1', 'IsoMu24',), + ('DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1', 'IsoMu24', 'QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65'), + ('DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1', 'QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65',), + ('QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65',), + ('DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1',), + )} +} +for x in inters: + utils.check_inters_correctness(triggers, inters[x], inters_general, channel=x, exclusive=exclusive) + + +fit_vars = ["dau1_pt", "dau2_pt"] +pairs2D = {'IsoMu24': (('dau1_pt', 'dau2_pt'),), 'IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1': (('dau1_pt', 'dau2_pt'),), + "DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1": (('dau1_pt', 'dau2_pt',), ('dau1_tauIdVSjet', 'dau2_tauIdVSjet'),), "QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65": (('dau1_pt', 'dau2_pt'), ('bjet1_btagDeepFlavB', 'bjet2_btagDeepFlavB'),), } +assert( set(pairs2D.keys()).issubset(set(triggers)) ) +for x in pairs2D.values(): + for pair in x: + assert( pair[0] in main.var_eff and pair[1] in main.var_eff ) + +pog_pt_binedges = (0, 26, 30, 40, 50, 60, 120, 200) +binedges = { + 'dau1_pt': {'etau': pog_pt_binedges, + 'mutau': pog_pt_binedges, + 'tautau': pog_pt_binedges }, + 'dau2_pt': { + 'etau': pog_pt_binedges, + 'mutau': pog_pt_binedges, + 'tautau': pog_pt_binedges + }, + 'dau1_eta': { + 'mutau': (-2.4, -2.1, -1.2, -0.9, 0, 0.9, 1.2, 2.1, 2.4), + 'etau': (-2.4, -2.1, -1.2, -0.9, 0, 0.9, 1.2, 2.1, 2.4), + 'tautau': (-2.4, -2.1, -1.2, -0.9, 0, 0.9, 1.2, 2.1, 2.4), + }, + 'dau1_tauIdVSjet': { + 'tautau': (0, 1, 2, 3, 4, 5, 6, 7), + }, + 'dau2_tauIdVSjet': { + 'tautau': (0, 1, 2, 3, 4, 5, 6, 7), + }, + 'bjet1_btagDeepFlavB': { + 'tautau': (0, 0.2, 0.3, 0.4, 0.5, 0.6, 1), + }, + 'bjet2_btagDeepFlavB': { + 'tautau': (0, 0.2, 0.3, 0.4, 0.5, 0.6, 1), + } + # ("quantiles", 100, 300), + # 'metnomu_et': {'mutau' : metnomu_et_binedges['mutau'], + # 'mumu' : metnomu_et_binedges['mumu'] }, + # 'mhtnomu_et': {'mutau' : (90,360), + # 'mumu' : (90,360) }, +} \ No newline at end of file diff --git a/inclusion/quickRDF.py b/inclusion/quickRDF.py new file mode 100644 index 0000000..903822d --- /dev/null +++ b/inclusion/quickRDF.py @@ -0,0 +1,81 @@ +import os +import sys + +import ROOT + +# Enable multi-threading +# ROOT.ROOT.EnableImplicitMT() +ROOT.gROOT.SetBatch(True) + +core_dir = str(os.getcwd()).split('producer') + +sys.path.insert(1, core_dir[0]+"/python") + +import argparse +parser = argparse.ArgumentParser(description='Ntuplizer options') +parser.add_argument('-v','--tauid_version', choices=['2p1', '2p5'], dest="tauid_version", default='2p5') +parser.add_argument('-o','--outfile', dest='outfile') +parser.add_argument('-i','--inputFiles', dest='inputFiles', default = False) +parser.add_argument('-mc', '--isMC', action='store_true', default = False) +options = parser.parse_args() + +# select the version +TauID_ver = options.tauid_version +outFile = options.outfile +useFiles = options.inputFiles +isMC = options.isMC + +def create_rdataframe(folders, inputFiles=None): + if not inputFiles: + inputFiles = [] + for folder in folders: + files = os.listdir(folder) + inputFiles += [folder + f for f in files] + + return ROOT.RDataFrame("Events", tuple(inputFiles)) + +def obtain_picontuple(df): + + branches = [] + + # Tag And Probe selection {Obtaining high pure Z -> mu tau Events} + ## select muon (Tag) candidate + + df = df.Filter("HLT_IsoMu24 > 0") + + df = df.Define("pass_quadJet", "return HLT_QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65 > 0;") + df = df.Define("pass_deepTau", "return HLT_DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1 > 0;") + df = df.Define("pass_both", "return HLT_QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65 > 0 && HLT_DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1 > 0;" ) + branches = ["dau1_pt", "dau1_eta", "pass_quadJet", "pass_deepTau", "pass_both"] + branch_list = ROOT.vector('string')() + for branch_name in branches: + branch_list.push_back(branch_name) + df.Snapshot("Events", "outfile.root", branch_list) + + + +if __name__ == '__main__': + + useFiles = str(useFiles) + + if ".txt" in useFiles: + print("Using files in {}".format(useFiles)) + folders = [] + inputFiles_run3 = [] + + with open(useFiles) as f: + for line in f: + line = line.replace('\n',"") # trim newline character + if "root://" not in line and not line.startswith("/eos") and not line.startswith("/pnfs"): + inputFiles_run3.append('root://cms-xrd-global.cern.ch//'+line) # prepend with redirector + else: + inputFiles_run3.append(line) + print(inputFiles_run3) + df = ROOT.RDataFrame("Events", tuple(inputFiles_run3)) + + # else: + # print("Not a valid inputFiles argument") + # print("Use 8102, 8136, or a text file of nanoaodfile locations") + + obtain_picontuple(df) + \ No newline at end of file diff --git a/tests/trigger_gains_run3.py b/tests/trigger_gains_run3.py new file mode 100644 index 0000000..d3634c5 --- /dev/null +++ b/tests/trigger_gains_run3.py @@ -0,0 +1,481 @@ +# coding: utf-8 + +_all_ = [ 'test_trigger_gains' ] + +import os +import sys +parent_dir = os.path.abspath(__file__ + 2 * '/..') +sys.path.insert(0, parent_dir) + +import json +import argparse +from inclusion.utils import utils +import numpy as np +from collections import defaultdict as dd +import hist +import mplhep as hep +from hist.intervals import clopper_pearson_interval as clop +import pickle +import uproot +import matplotlib.pyplot as plt + + +# import bokeh +# from bokeh.plotting import figure, output_file, save +# from bokeh.models import Whisker +# from bokeh.layouts import gridplot +# from bokeh.io import export_svg, export_png + +tau = '\u03C4' +mu = '\u03BC' +pm = '\u00B1' +ditau = tau+tau + +def get_outname(channel, regcuts, ptcuts, met_turnon, bigtau): + utils.create_single_dir('data') + + name = channel + '_' + name += '_'.join((*regcuts, 'ptcuts', *[str(x) for x in ptcuts], 'turnon', str(met_turnon))) + if bigtau: + name += '_BIGTAU' + name += '.pkl' + + s = 'data/regions_{}'.format(name) + return s + +def pp(chn): + if chn == "tautau": + return ditau + elif chn == "etau": + return "e" + tau + elif chn == "mutau": + return mu + tau + +def rec_dd(): + return dd(rec_dd) + +def set_fig(fig, legend=True): + fig.output_backend = 'svg' + fig.toolbar.logo = None + # if legend: + # fig.legend.click_policy='hide' + # fig.legend.location = 'top_left' + # fig.legend.label_text_font_size = '8pt' + fig.min_border_bottom = 5 + fig.xaxis.visible = True + fig.title.align = "left" + fig.title.text_font_size = "15px" + fig.xaxis.axis_label_text_font_style = "bold" + fig.yaxis.axis_label_text_font_style = "bold" + fig.xaxis.axis_label_text_font_size = "13px" + fig.yaxis.axis_label_text_font_size = "13px" + +def main(args): + vars = ['dau1_pt', + 'dau2_pt', 'dau1_eta', 'dau2_eta', + 'dau1_tauIdVSjet', 'dau2_tauIdVSjet', + 'bjet1_pNet', 'bjet2_pNet', 'bjet1_pt', + 'bjet2_pt', 'bjet1_eta', 'bjet2_eta'] + if args.channels[0] == "etau": + base_extension = "E" + channel_text = r"e$\tau_{h}$" + elif args.channels[0] == "mutau": + base_extension = "Mu" + channel_text = r"$\mu \tau_{h}$" + elif args.channels[0] == "tautau": + base_extension = "Tau" + channel_text = r"$\tau_{h} \tau_{h}$" + file70 = uproot.open("data/regions_preEE_11p2_all.root") + file60 = uproot.open("data/regions_preEE_11p3-jet-lower-pt_all.root") + file80 = uproot.open("data/regions_preEE_11p4-jet-higher-pt_all.root") + # petroff6 = ["#5790fc", "#f89c20", "#e42536", "#964a8b", "#9c9ca1", "#7a21dd"] + + # channel_base_60 = file60['Base{}_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # channel_base_70 = file70['Base{}_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # channel_base_80 = file80['Base{}_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # channel_base_plus_ttjet_60 = file60['Base{}ORTauTauJet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # channel_base_plus_ttjet_70 = file70['Base{}ORTauTauJet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # channel_base_plus_ttjet_80 = file80['Base{}ORTauTauJet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # channel_all = file60['Base{}ORTauTauJetOR4JetsPNet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + # err_base_plus_ttjet_60 = np.sqrt(channel_base_plus_ttjet_60.values())/ channel_all.values() + # err_base_plus_ttjet_70 = np.sqrt(channel_base_plus_ttjet_70.values())/ channel_all.values() + # err_base_plus_ttjet_80 = np.sqrt(channel_base_plus_ttjet_80.values())/ channel_all.values() + # ax = channel_base_60.axes[0].centers() + # edges = channel_base_60.axes[0].edges() + # xerr_r = ax - edges[:-1] + # xerr_l = edges[1:] - ax + # hep.style.use("CMS") + # print("CMS style") + # fig, ax1 = plt.subplots() + # ax1.set_ylim(-4, 30) + # print("subplots") + # ax2 = ax1.twinx() + # print("generated additional axis") + # ax2.set_ylabel("Weighted MC events [a.u.]") # we already handled the x-label with ax1 + # ax2.bar(ax, height=channel_all.values(), width=xerr_r * 2, color=petroff6[4], alpha=0.3, zorder=0, label="HHbbtautau") + # print(channel_base_60.values(), channel_base_70.values(), channel_base_80.values()) + # hep.style.use("CMS") + # ax1.errorbar(ax - 10, (channel_base_plus_ttjet_60.values() / channel_base_60.values() - 1) * 100, yerr=err_base_plus_ttjet_60*100, xerr=[xerr_r - 10, xerr_l + 10], fmt='o', label=r"p$_{t}^{jet} > 60$ GeV", color=petroff6[0], zorder=5) + # ax1.errorbar(ax, (channel_base_plus_ttjet_70.values() / channel_base_60.values() - 1) * 100, yerr=err_base_plus_ttjet_70*100, xerr=[xerr_r, xerr_l], fmt='o', label=r"p$_{t}^{jet} > 70$ GeV", color=petroff6[2], zorder=5) + # ax1.errorbar(ax + 10, (channel_base_plus_ttjet_80.values() / channel_base_60.values() - 1) * 100, yerr=err_base_plus_ttjet_80*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label=r"p$_{t}^{jet} > 80$ GeV", color=petroff6[4], zorder=5) + # ax1.set_xlabel(r"m$_{HH}$ [GeV]") + # ax1.set_ylabel("Gain [%]") + # print("here") + # lines, labels = ax1.get_legend_handles_labels() + # lines2, labels2 = ax2.get_legend_handles_labels() + # plt.legend(lines + lines2, labels + labels2) + # # print("Total Gain: {} +/- {}".format(round(channel_all.values().sum()/channel_base.values().sum() * 100, 2), round(err_tot_all * 100, 2))) + # hep.cms.label(lumi="9.8", com="13.6") + # plt.savefig("cutoff_comparison.png".format(args.channels[0])) + with uproot.open("data/regions_preEE_12p11-old-way-pf75_all.root") as file: + # from matplotlib.colors import ListedColormap + petroff6 = ["#5790fc", "#f89c20", "#e42536", "#964a8b", "#9c9ca1", "#7a21dd"] + channel_all = file['Base{}ORTauTauJetOR4JetsPNet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + channel_base = file['Base{}_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + channel_base_plus_ttjet = file['Base{}ORTauTauJet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + channel_base_plus_quadJetPNet = file['Base{}OR4JetsPNet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + all_channel = file['All_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_ttjet = file['NoBaseMuTTJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_quadJetDeep = file['NoBaseMu4JetsDeepJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_all = file['Base{}ORTauTauJetOR4JetsPNet_baseline_{}_genHH_mass'.format(base_extension, args.channels[0])] + + # channel_base_plus_Mu50 = file['NoBaseMuMu50_baseline_{}'.format(args.channels[0])] + # channel_base_plus_Ele28 = file['NoBaseEle28_baseline_{}'.format(args.channels[0])] + # if args.channels[0] == "tautau": + # channel_all = file['NoBaseMuTauMETORTauORTTJet_baseline_{}'.format(args.channels[0])] + # base = "DoubleMediumDeepTau" + # last_trig = "TauTauJet" + # if args.channels[0] == "mutau": + # channel_all = file['NoBaseMETORTauORMu50_baseline_{}'.format(args.channels[0])] + # base = "IsoMu + CrossMuTau" + # last_trig = "Mu50" + # if args.channels[0] == "etau": + # channel_all = file['NoBaseMETORTauOREle28_baseline_{}'.format(args.channels[0])] + # base = "Ele24 + CrossEleTau" + # last_trig = "Ele28HT" + var_base_tot = channel_base.variances().sum() + # var_all_tot = channel_all.variances().sum() + # err_tot_all = np.sqrt(var_all_tot / (channel_base.values().sum() ** 2) + (channel_all.values().sum() ** 2) * var_base_tot / (channel_base.values().sum() ** 4)) + err_base_plus_ttjet = np.sqrt(channel_base_plus_ttjet.values())/ channel_all.values() + err_base_plus_quadJetPNet = np.sqrt(channel_base_plus_quadJetPNet.values())/ channel_all.values() + # err_base_plus_ttjet = np.sqrt( (1/channel_base.values() * channel_base_plus_ttjet.errors()) ** 2 + (channel_base_plus_ttjet.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_quadJetDeep = np.sqrt( (1/channel_base.values() * channel_base_plus_quadJetDeep.errors()) ** 2 + (channel_base_plus_quadJetDeep.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + err_all = np.sqrt(channel_all.values())/ channel_all.values() + + # err_base_plus_Mu50 = np.sqrt( (1/channel_base.values() * channel_base_plus_Mu50.errors()) ** 2 + (channel_base_plus_Mu50.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_Ele28 = np.sqrt( (1/channel_base.values() * channel_base_plus_Ele28.errors()) ** 2 + (channel_base_plus_Ele28.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_all= np.sqrt( (1/channel_base.values() * channel_all.errors()) ** 2 + (channel_all.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + ax = channel_base.axes[0].centers() + edges = channel_base.axes[0].edges() + xerr_r = ax - edges[:-1] + xerr_l = edges[1:] - ax + print(edges, xerr_r, xerr_l) + print(channel_base.errors()/channel_base.values()) + + + hep.style.use("CMS") + print("CMS style") + fig, ax1 = plt.subplots() + ax1.set_ylim(-5, 40) + print("subplots") + ax2 = ax1.twinx() + print("generated additional axis") + ax2.set_ylabel("Weighted MC events [a.u.]") # we already handled the x-label with ax1 + ax2.bar(ax, height=all_channel.values(), width=xerr_r * 2, color=petroff6[4], alpha=0.3, zorder=0, label="HHbbtautau") + print("Initialized bars") + hep.style.use("CMS") + ax1.errorbar(ax, (channel_base_plus_ttjet.values() / channel_base.values() - 1) * 100, yerr=err_base_plus_ttjet*100, xerr=[xerr_r, xerr_l], fmt='o', label="Base + TauTauJet", color=petroff6[0], zorder=5) + ax1.errorbar(ax, (channel_base_plus_quadJetPNet.values() / channel_base.values() - 1) * 100, yerr=err_base_plus_quadJetPNet*100, xerr=[xerr_r, xerr_l], fmt='o', label="Base + QuadJetPNet", color=petroff6[1], zorder=5) + if args.channels[0] == "tautau": + # ax1.errorbar(ax, (channel_base_plus_ttjet.values() / channel_base.values()) * 100, yerr=err_base_plus_ttjet*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + TauTauJet", color=petroff6[2], zorder=5) + # ax1.errorbar(ax, (channel_base_plus_quadJetPNet.values() / channel_base.values() - 1) * 100, yerr=err_base_plus_quadJetPNet*100, xerr=[xerr_r, xerr_l], fmt='o', label="Base + QuadJetPNet", color=petroff6[1], zorder=5) + # ax1.errorbar(ax + 10, (channel_base_plus_quadJetDeep.values() / channel_base.values()) * 100, yerr=err_base_plus_quadJetDeep*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + QuadJetDeep", color=petroff6[3], zorder=5) + + ax1.errorbar(ax + 10, (channel_all.values() / channel_base.values() - 1) * 100, yerr=err_all*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + TauTauJet + QuadJetPNet", color=petroff6[5], zorder=5) + # if args.channels[0] == "mutau": + # # ax1.errorbar(ax + 5, (channel_base_plus_Mu50.values() / channel_base.values()) * 100, yerr=err_base_plus_Mu50*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + Mu50", color=petroff6[2], zorder=5) + # # ax1.errorbar(ax + 15, (channel_base_plus_quadJetPNet.values() / channel_base.values()) * 100, yerr=err_base_plus_quadJetPNet*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + QuadJetPNet", color=petroff6[4], zorder=5) + # # ax1.errorbar(ax + 10, (channel_all.values() / channel_base.values() - 1) * 100, yerr=err_all*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + TauTauJet + QuadJetPNet", color=petroff6[5], zorder=5) + # if args.channels[0] == "etau": + # ax1.errorbar(ax + 10, (channel_base_plus_quadJetPNet.values() / channel_base.values()) * 100, yerr=err_base_plus_quadJetPNet*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + QuadJetPNet", color=petroff6[4], zorder=5) + # ax1.errorbar(ax + 10, (channel_all.values() / channel_base.values() - 1) * 100, yerr=err_all*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + TauTauJet + QuadJetPNet", color=petroff6[5], zorder=5) + # ax1.errorbar(ax + 20, (channel_all.values() / channel_base.values()) * 100, yerr=err_all*100, xerr=[xerr_r + 20, xerr_l - 20], fmt='o', label="Base + MET + IsoTau + {}".format( last_trig), color=petroff6[3]) + ax1.set_xlabel(r"m$_{HH}$ [GeV]") + ax1.set_ylabel("Gain [%]") + print("here") + lines, labels = ax1.get_legend_handles_labels() + lines2, labels2 = ax2.get_legend_handles_labels() + plt.legend(lines + lines2, labels + labels2) + # print("Total Gain: {} +/- {}".format(round(channel_all.values().sum()/channel_base.values().sum() * 100, 2), round(err_tot_all * 100, 2))) + hep.cms.label(lumi="9.8", com="13.6") + plt.savefig("gains_{}_11p5_ttjet-only_pf75.png".format(args.channels[0])) + # print(ratio.axes[0]) + return + for i in vars: + channel_base_plus_ttjet = file['Base{}ORTauTauJet_baseline_{}_{}'.format(base_extension, args.channels[0], i)] + channel_only_quadJetPNet = file['NoBase{}ORNoTauTauJet4JetsPNet_baseline_{}_{}'.format(base_extension, args.channels[0], i)] + channel_all = file['Base{}ORTauTauJetOR4JetsPNet_baseline_{}_{}'.format(base_extension, args.channels[0], i)] + err_base_plus_ttjet = np.sqrt(channel_base_plus_ttjet.values()) + err_only_quadJetPNet = np.sqrt(channel_only_quadJetPNet.values()) + # err_base_plus_ttjet = np.sqrt( (1/channel_base.values() * channel_base_plus_ttjet.errors()) ** 2 + (channel_base_plus_ttjet.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_quadJetDeep = np.sqrt( (1/channel_base.values() * channel_base_plus_quadJetDeep.errors()) ** 2 + (channel_base_plus_quadJetDeep.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_Mu50 = np.sqrt( (1/channel_base.values() * channel_base_plus_Mu50.errors()) ** 2 + (channel_base_plus_Mu50.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_Ele28 = np.sqrt( (1/channel_base.values() * channel_base_plus_Ele28.errors()) ** 2 + (channel_base_plus_Ele28.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_all= np.sqrt( (1/channel_base.values() * channel_all.errors()) ** 2 + (channel_all.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + ax = channel_all.axes[0].centers() + edges = channel_all.axes[0].edges() + xerr_r = ax - edges[:-1] + xerr_l = edges[1:] - ax + print(edges, xerr_r, xerr_l) + + + hep.style.use("CMS") + fig, ax1 = plt.subplots() + ax2 = ax1.twinx() + ax2.set_ylabel("Weighted MC events [a.u.]") # we already handled the x-label with ax1 + print(i, channel_all.values()) + ax2.bar(ax, height=channel_all.values(), width=xerr_r * 2, color='lightgrey', alpha=0.35, zorder=0, label="HHbbtautau") + + hep.style.use("CMS") + # ax1.errorbar(ax - 15, (channel_base.values() / channel_all.values()) * 100, yerr=err_base*100, xerr=[xerr_r - 15, xerr_l + 15], fmt='o', label="Base", color=petroff6[0], zorder=5) + ax1.errorbar(ax - xerr_r * 0.2, channel_base_plus_ttjet.values(), yerr=err_base_plus_ttjet, xerr=[xerr_r * 0.8 , xerr_l * 1.2], fmt='o', label="Base + TauTauJet", color=petroff6[3], zorder=5) + ax1.errorbar(ax + xerr_r * 0.2, channel_only_quadJetPNet.values(), yerr=err_only_quadJetPNet, xerr=[xerr_r * 1.2, xerr_l * 0.8], fmt='o', label="QuadJetPNet", color=petroff6[2], zorder=5) + # ax1.errorbar(ax + 15, (channel_base_all.values() / channel_all.values()) * 100, yerr=err_base_all*100, xerr=[xerr_r + 15, xerr_l - 15], fmt='o', label="Base + TauTauJet\n+ QuadJetPNet", color=petroff6[5], zorder=5) + + # ax1.errorbar(ax + 10, (channel_base_all.values() / channel_all.values()) * 100, yerr=err_base_all*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + MET + IsoTau\n+ Mu50 + QuadJetPNet", color=petroff6[4], zorder=5) + # ax1.errorbar(ax + 10, (channel_base_all.values() / channel_all.values()) * 100, yerr=err_base_all*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="Base + MET + IsoTau\n+ QuadJetPNet", color=petroff6[4], zorder=5) + + ax1.text(0.05, 0.94, 'Channel: '+ channel_text, horizontalalignment='left', verticalalignment='bottom', transform=ax1.transAxes, size=21) + # ax1.errorbar(ax + 20, (channel_all.values() / channel_base.values()) * 100, yerr=err_all*100, xerr=[xerr_r + 20, xerr_l - 20], fmt='o', label="Base + MET + IsoTau + {}".format( last_trig), color=petroff6[3]) + ax1.set_xlabel("{}".format(i)) + ax1.set_ylabel("Weighted MC events [a.u.]") + lines, labels = ax1.get_legend_handles_labels() + lines2, labels2 = ax2.get_legend_handles_labels() + plt.legend(lines + lines2, labels + labels2, loc=4, bbox_to_anchor=(1, 0.1)) + # print("Total Gain: {} +/- {}".format(round(channel_all.values().sum()/channel_base.values().sum() * 100, 2), round(err_tot_all * 100, 2))) + hep.cms.label(lumi="9.8", com="13.6") + plt.savefig("gains_{}_quadJet_preEE_10p20_withOffline_{}.png".format(args.channels[0], i)) + return + channels = args.channels + linear_x = [k for k in range(1,2)] + edges_x = [k-0.5 for k in range(1,2)] + [1.5] + ptcuts = {chn: utils.get_ptcuts(chn, args.year) for chn in args.channels} + + nevents, errors = dd(lambda: dd(dict)), dd(lambda: dd(dict)) + ratios, eratios = dd(lambda: dd(dict)), dd(lambda: dd(dict)) + + for adir in main_dir: + dRstr = str(args.deltaR).replace('.', 'p') + if len(args.channels) == 1: + output_name = os.path.join(base_dir, 'trigger_gains_{}_{}_DR{}'.format(args.channels[0], + args.year, dRstr)) + elif len(args.channels) == 2: + output_name = os.path.join(base_dir, 'trigger_gains_{}_{}_{}_DR{}'.format(*args.channels[:2], + args.year, dRstr)) + elif len(args.channels) == 3: + output_name = os.path.join(base_dir, 'trigger_gains_all_{}_DR{}'.format(args.year, dRstr)) + if args.bigtau: + output_name += "_BIGTAU" + output_name += ".html" + output_file(output_name) + print('Saving file {}.'.format(output_name)) + + for chn in channels: + md = adir[chn] + in_base = os.path.join(base_dir, md) + + nevents[md][chn]['base'], nevents[md][chn]['met'], nevents[md][chn]['tau'] = [], [], [] + ratios[md][chn]['two'], ratios[md][chn]['met'], ratios[md][chn]['tau'] = [], [], [] + eratios[md][chn]['two'], eratios[md][chn]['met'], eratios[md][chn]['tau'] = [], [], [] + errors[md][chn] = [] + + outname = get_outname( chn, [str(x) for x in args.region_cuts], + [str(x) for x in ptcuts[chn]], str(args.met_turnon), + args.bigtau) + + print(outname) + with open(outname, "rb") as f: + ahistos = pickle.load(f) + + #all regions summed + sum_base_tot = round(ahistos["Base"]["legacy"]["baseline"].values().sum() + + ahistos["Base"]["tau"]["baseline"].values().sum() + + ahistos["Base"]["met"]["baseline"].values().sum()) + print(ahistos["Base"]["legacy"]["baseline"].values()) + #legacy region + l1 = lambda x : round(x["legacy"]["baseline"].values().sum(), 2) + sum_base = l1(ahistos["Base"]) + sum_vbf = l1(ahistos["VBF"]) + sum_met = l1(ahistos["NoBaseMET"]) + sum_only_tau = l1(ahistos["NoBaseNoMETTau"]) + sum_tau = l1(ahistos["NoBaseTau"]) + sum_basekin = l1(ahistos["LegacyKin"]) + w2_basekin = ahistos["METKin"]["legacy"]["baseline"].variances().sum() + + #MET region + l2 = lambda x : round(x["met"]["baseline"].values().sum(), 2) + sum_metkin = l2(ahistos["METKin"]) + w2_metkin = ahistos["METKin"]["met"]["baseline"].variances().sum() + + #Single Tau region + l3 = lambda x : round(x["tau"]["baseline"].values().sum(), 2) + sum_taukin = l3(ahistos["TauKin"]) + w2_taukin = ahistos["TauKin"]["tau"]["baseline"].variances().sum() + + #hypothetical VBF region + sum_vbfkin = l2(ahistos["VBFKin"]) + l3(ahistos["VBFKin"]) + + nevents[md][chn]['base'].append(sum_basekin) + nevents[md][chn]['met'].append(sum_basekin + sum_metkin) + nevents[md][chn]['tau'].append(sum_basekin + sum_metkin + sum_taukin) + + rat_met_num = sum_basekin + sum_metkin + rat_met_all = rat_met_num / sum_base_tot + + rat_tau_num = sum_basekin + sum_taukin + rat_tau_all = rat_tau_num / sum_base_tot + + rat_all_num = sum_basekin + sum_taukin + sum_metkin + rat_all = rat_all_num / sum_base_tot + + ratios[md][chn]['met'].append(rat_met_all) + ratios[md][chn]['tau'].append(rat_tau_all) + ratios[md][chn]['two'].append(rat_all) + + e_metkin = np.sqrt(w2_metkin) + e_taukin = np.sqrt(w2_taukin) + e_basekin = np.sqrt(w2_basekin) + + e_tau_num = np.sqrt(w2_taukin + w2_basekin) + e_met_num = np.sqrt(w2_metkin + w2_basekin) + e_all_num = np.sqrt(w2_metkin + w2_taukin + w2_basekin) + print(rat_tau_num, rat_met_num, rat_all_num) + + eratios[md][chn]['tau'].append(rat_tau_all * np.sqrt(e_tau_num**2/rat_tau_num**2 + 1/sum_base_tot)) + eratios[md][chn]['met'].append(rat_met_all * np.sqrt(e_met_num**2/rat_met_num**2 + 1/sum_base_tot)) + eratios[md][chn]['two'].append(rat_all * np.sqrt(e_all_num**2/rat_all_num**2 + 1/sum_base_tot)) + errors[md][chn].append(e_all_num) + + json_name = 'data_' + chn + '_' + json_name += ('bigtau' if args.bigtau else 'standard') + '.json' + with open(json_name, 'w', encoding='utf-8') as json_obj: + json_data = {"vals": {chn: nevents[adir[chn]][chn]['tau'] for chn in channels}} + json_data.update({"errs": {chn: errors[adir[chn]][chn] for chn in channels}}) + json.dump(json_data, json_obj, ensure_ascii=False, indent=4) + + opt_points = dict(size=8) + opt_line = dict(width=1.5) + colors = ('green', 'blue', 'red', 'brown') + styles = ('solid', 'dashed', 'dotdash') + legends = {'base': 'Legacy', + 'met': 'MET', 'tau': 'Single Tau', + 'two': 'MET + Single Tau', 'vbf': 'VBF'} + + x_str = [str(k) for k in [0]] + xticks = linear_x[:] + yticks = [x for x in range(0,110,5)] + shift_one = {'met': [-0.15, 0., 0.15], 'tau': [-0.20, -0.05, 0.1], + 'vbf': [-0.10, 0.05, 0.20]} + shift_both = {'met': [-0.15, 0., 0.15], 'two': [-0.20, -0.05, 0.1]} + shift_kin = {'met': [-0.09, 0., 0.15], 'tau': [0.03, -0.05, 0.1], + 'two': [-0.03, 0.05, 0.20], 'vbf': [0.09, 0.1, 0.25]} + + for adir in main_dir: + print(adir) + p_opt = dict(width=800, height=400, x_axis_label='x', y_axis_label='y') + p1 = figure(title='Event number (' + pp(channels[0]) + ')', y_axis_type="linear", **p_opt) + p2 = figure(title='Acceptance Gain (' + pp(channels[0]) + ')', **p_opt) if len(channels)==1 else figure(**p_opt) + + p1.yaxis.axis_label = 'Weighted number of events' + p2.yaxis.axis_label = 'Trigger acceptance gain (w.r.t. trigger baseline) [%]' + pics = (p1, p2) + for p in pics: + set_fig(p) + + for ichn,chn in enumerate(channels): + md = adir[chn] + print(md) + + p1.quad(top=nevents[md][chn]["base"], bottom=0, + left=edges_x[:-1], right=edges_x[1:], + legend_label=legends["base"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), + fill_color="dodgerblue", line_color="black") + p1.quad(top=nevents[md][chn]["met"], bottom=nevents[md][chn]["base"], + left=edges_x[:-1], right=edges_x[1:], + legend_label=legends["met"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), + fill_color="green", line_color="black") + p1.quad(top=nevents[md][chn]["tau"], bottom=nevents[md][chn]["met"], + left=edges_x[:-1], right=edges_x[1:], + legend_label=legends["tau"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), + fill_color="red", line_color="black") + print(md) + for itd,td in enumerate(('met', 'tau', 'two')): + p2.circle([x+shift_kin[td][ichn] for x in linear_x], + [(x-1)*100. for x in ratios[md][chn][td]], + color=colors[itd], fill_alpha=1., **opt_points) + p2.line([x+shift_kin[td][ichn] for x in linear_x], + [(x-1)*100. for x in ratios[md][chn][td]], + color=colors[itd], line_dash=styles[ichn], + legend_label=legends[td]+(' ('+pp(chn)+')' if len(channels)>1 else ''), **opt_line) + p2.multi_line( + [(x+shift_kin[td][ichn],x+shift_kin[td][ichn]) for x in linear_x], + [((y-1)*100-(x*50.),(y-1)*100+(x*50.)) for x,y in zip(eratios[md][chn][td],ratios[md][chn][td])], + color=colors[itd], **opt_line) + + p1.legend.location = 'top_right' + p2.legend.location = 'top_left' + for p in pics: + p.xaxis[0].ticker = xticks + p.xgrid[0].ticker = xticks + p.xgrid.grid_line_alpha = 0.2 + p.xgrid.grid_line_color = 'black' + # p.yaxis[0].ticker = yticks + # p.ygrid[0].ticker = yticks + p.ygrid.grid_line_alpha = 0.2 + p.ygrid.grid_line_color = 'black' + + p.xaxis.axis_label = "m(X) [GeV]" + + p.xaxis.major_label_overrides = dict(zip(linear_x,x_str)) + + p.legend.click_policy='hide' + + p.output_backend = 'svg' + #export_svg(p, filename='line_graph.svg') + + g = gridplot([[p] for p in pics]) + print(g) + save(g, title=md) + export_png(g, filename="line_graph_postEE.png") + +if __name__ == '__main__': + desc = "Produce plots of trigger gain VS resonance mass.\n" + desc += "Uses the output of test_trigger_regions.py." + desc += "When running on many channels, one should keep in mind each channel has different pT cuts." + desc += "This might imply moving sub-folders (produced by the previous script) around." + parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) + + # parser.add_argument('--masses', required=True, nargs='+', type=str, + # help='Resonance mass') + parser.add_argument('--channels', required=True, nargs='+', type=str, + choices=('etau', 'mutau', 'tautau'), + help='Select the channel over which the workflow will be run.' ) + parser.add_argument('--year', required=True, type=str, choices=('2016', '2017', '2018', '2022'), + help='Select the year over which the workflow will be run.' ) + parser.add_argument('--deltaR', type=float, default=0.5, help='DeltaR between the two leptons.') + parser.add_argument('--bigtau', action='store_true', + help='Consider a larger single tau region, reducing the ditau one.') + parser.add_argument('--met_turnon', required=False, type=str, default=180, + help='MET trigger turnon cut [GeV].' ) + parser.add_argument('--region_cuts', required=False, type=float, nargs=2, default=(190, 190), + help='High/low regions pT1 and pT2 selection cuts [GeV].' ) + + args = utils.parse_args(parser) + + base_dir = '/t3home/fbilandz/TriggerScaleFactors/' + main_dir = [{"etau": "Region_Spin2_190_190_PT_33_25_35_DR_{}_TURNON_200_190".format(args.deltaR), + "mutau": "mutau_190_190_DR_0.5_PT_25_21_32_TURNON_180", + "tautau": "Region_Spin2_190_190_PT_40_40_DR_{}_TURNON_200_190".format(args.deltaR)}, + ] + + main(args) diff --git a/tests/trigger_orthogonal_gains_refined_run3.py b/tests/trigger_orthogonal_gains_refined_run3.py new file mode 100644 index 0000000..a46701c --- /dev/null +++ b/tests/trigger_orthogonal_gains_refined_run3.py @@ -0,0 +1,157 @@ +# coding: utf-8 + +_all_ = [ 'test_trigger_gains' ] + +import os +import sys +parent_dir = os.path.abspath(__file__ + 2 * '/..') +sys.path.insert(0, parent_dir) + +import json +import argparse +from inclusion.utils import utils +import numpy as np +from collections import defaultdict as dd +import hist +import mplhep as hep +from hist.intervals import clopper_pearson_interval as clop +import pickle +import uproot +import matplotlib.pyplot as plt + + +# import bokeh +# from bokeh.plotting import figure, output_file, save +# from bokeh.models import Whisker +# from bokeh.layouts import gridplot +# from bokeh.io import export_svg, export_png + +tau = '\u03C4' +mu = '\u03BC' +pm = '\u00B1' +ditau = tau+tau + +def get_outname(channel, regcuts, ptcuts, met_turnon, bigtau): + utils.create_single_dir('data') + + name = channel + '_' + name += '_'.join((*regcuts, 'ptcuts', *[str(x) for x in ptcuts], 'turnon', str(met_turnon))) + if bigtau: + name += '_BIGTAU' + name += '.pkl' + + s = 'data/regions_{}'.format(name) + return s + +def pp(chn): + if chn == "tautau": + return ditau + elif chn == "etau": + return "e" + tau + elif chn == "mutau": + return mu + tau + +def rec_dd(): + return dd(rec_dd) + +def set_fig(fig, legend=True): + fig.output_backend = 'svg' + fig.toolbar.logo = None + # if legend: + # fig.legend.click_policy='hide' + # fig.legend.location = 'top_left' + # fig.legend.label_text_font_size = '8pt' + fig.min_border_bottom = 5 + fig.xaxis.visible = True + fig.title.align = "left" + fig.title.text_font_size = "15px" + fig.xaxis.axis_label_text_font_style = "bold" + fig.yaxis.axis_label_text_font_style = "bold" + fig.xaxis.axis_label_text_font_size = "13px" + fig.yaxis.axis_label_text_font_size = "13px" + +def main(args): + vars = ['dau1_pt', + 'dau2_pt', 'dau1_eta', 'dau2_eta', + 'dau1_tauIdVSjet', 'dau2_tauIdVSjet', + 'bjet1_pNet', 'bjet2_pNet', 'bjet1_pt', + 'bjet2_pt', 'bjet1_eta', 'bjet2_eta'] + if args.channels[0] == "etau": + base_extension = "E" + channel_text = r"e$\tau_{h}$" + elif args.channels[0] == "mutau": + base_extension = "Mu" + channel_text = r"$\mu \tau_{h}$" + elif args.channels[0] == "tautau": + base_extension = "Tau" + channel_text = r"$\tau_{h} \tau_{h}$" + genHH_mass_baseline = uproot.open("data/regions_postBPix_15p1-no-trigger_all.root")['tautau_genHH_mass'] + genHH_mass_base_tau = uproot.open("data/regions_2023_postBPix_15p35-base-tau_all.root")['tautau_genHH_mass'] + genHH_mass_ditaujet = uproot.open("data/regions_2023_postBPix_15p36-ditaujet_all.root")['tautau_genHH_mass'] + genHH_mass_quadjet = uproot.open("data/regions_2023_postBPix_15p34_all.root")['tautau_genHH_mass'] + genHH_mass_both = uproot.open("data/regions_2023_postBPix_15p33_all.root")['tautau_genHH_mass'] + # ht = uproot.open("data/regions_postBPix_15p11-quadjet-confirm_all.root")['tautau_SoftActivityJetHT'] + petroff6 = ["#5790fc", "#f89c20", "#e42536", "#964a8b", "#9c9ca1", "#7a21dd"] + + ditaujet_gain = genHH_mass_ditaujet.values() - genHH_mass_base_tau.values() + quadjet_gain = genHH_mass_quadjet.values() - genHH_mass_base_tau.values() + overlap_gain = genHH_mass_ditaujet.values() + genHH_mass_quadjet.values() - genHH_mass_base_tau.values() - genHH_mass_both.values() + + hep.style.use("CMS") + + ditaujet_gain_err = np.sqrt(ditaujet_gain)/genHH_mass_base_tau.values() + quadjet_gain_err = np.sqrt(quadjet_gain)/genHH_mass_base_tau.values() + overlap_gain_err = np.sqrt(overlap_gain)/genHH_mass_base_tau.values() + + ax = genHH_mass_base_tau.axes[0].centers() + edges = genHH_mass_base_tau.axes[0].edges() + xerr_r = ax - edges[:-1] + xerr_l = edges[1:] - ax + + plt.figure(1) + plt.bar(x=ax, height=(ditaujet_gain/genHH_mass_base_tau.values()) * 100,width=xerr_r * 2, yerr=ditaujet_gain_err * 100, color="#fff", edgecolor=petroff6[0], ecolor=petroff6[0], label="DiTauJet & !DiTau") + + plt.bar(x=ax, height=-(quadjet_gain/genHH_mass_base_tau.values()) * 100, width=xerr_r * 2, yerr=quadjet_gain_err * 100, label="QuadJet & !DiTauJet", color="#fff", edgecolor=petroff6[1], ecolor=petroff6[1]) + plt.bar(x=ax, height=(overlap_gain/genHH_mass_base_tau.values()) * 100, width=xerr_r * 2, yerr=overlap_gain_err * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2], label="DiTauJet & QuadJet") + plt.bar(x=ax, height=-(overlap_gain/genHH_mass_base_tau.values()) * 100, width=xerr_r * 2, yerr=overlap_gain_err * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2]) + plt.axhline(y=0, color='black', linestyle='-') + ticks_locs, ticks_labels = plt.yticks() + plt.yticks(ticks_locs, [int(abs(tick)) for tick in ticks_locs]) + + plt.xlabel(r"m$_{HH}$ [GeV]") + plt.ylabel("Gain [%]") + plt.legend() + hep.cms.label(lumi="9.96", com="13.6") + plt.savefig("orthogonal_gains_postBPix.png".format(args.channels[0])) + +if __name__ == '__main__': + desc = "Produce plots of trigger gain VS resonance mass.\n" + desc += "Uses the output of test_trigger_regions.py." + desc += "When running on many channels, one should keep in mind each channel has different pT cuts." + desc += "This might imply moving sub-folders (produced by the previous script) around." + parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) + + # parser.add_argument('--masses', required=True, nargs='+', type=str, + # help='Resonance mass') + parser.add_argument('--channels', required=True, nargs='+', type=str, + choices=('etau', 'mutau', 'tautau'), + help='Select the channel over which the workflow will be run.' ) + parser.add_argument('--year', required=True, type=str, choices=('2016', '2017', '2018', '2022', '2023'), + help='Select the year over which the workflow will be run.' ) + parser.add_argument('--deltaR', type=float, default=0.5, help='DeltaR between the two leptons.') + parser.add_argument('--bigtau', action='store_true', + help='Consider a larger single tau region, reducing the ditau one.') + parser.add_argument('--met_turnon', required=False, type=str, default=180, + help='MET trigger turnon cut [GeV].' ) + parser.add_argument('--region_cuts', required=False, type=float, nargs=2, default=(190, 190), + help='High/low regions pT1 and pT2 selection cuts [GeV].' ) + + args = utils.parse_args(parser) + + base_dir = '/t3home/fbilandz/TriggerScaleFactors/' + main_dir = [{"etau": "Region_Spin2_190_190_PT_33_25_35_DR_{}_TURNON_200_190".format(args.deltaR), + "mutau": "mutau_190_190_DR_0.5_PT_25_21_32_TURNON_180", + "tautau": "Region_Spin2_190_190_PT_40_40_DR_{}_TURNON_200_190".format(args.deltaR)}, + ] + + main(args) \ No newline at end of file diff --git a/tests/trigger_orthogonal_gains_run3.py b/tests/trigger_orthogonal_gains_run3.py new file mode 100644 index 0000000..442e9c7 --- /dev/null +++ b/tests/trigger_orthogonal_gains_run3.py @@ -0,0 +1,594 @@ +# coding: utf-8 + +_all_ = [ 'test_trigger_gains' ] + +import os +import sys +parent_dir = os.path.abspath(__file__ + 2 * '/..') +sys.path.insert(0, parent_dir) + +import json +import argparse +from inclusion.utils import utils +import numpy as np +from collections import defaultdict as dd +import hist +import mplhep as hep +from hist.intervals import clopper_pearson_interval as clop +import pickle +import uproot +import matplotlib.pyplot as plt + + +# import bokeh +# from bokeh.plotting import figure, output_file, save +# from bokeh.models import Whisker +# from bokeh.layouts import gridplot +# from bokeh.io import export_svg, export_png + +tau = '\u03C4' +mu = '\u03BC' +pm = '\u00B1' +ditau = tau+tau + +def get_outname(channel, regcuts, ptcuts, met_turnon, bigtau): + utils.create_single_dir('data') + + name = channel + '_' + name += '_'.join((*regcuts, 'ptcuts', *[str(x) for x in ptcuts], 'turnon', str(met_turnon))) + if bigtau: + name += '_BIGTAU' + name += '.pkl' + + s = 'data/regions_{}'.format(name) + return s + +def pp(chn): + if chn == "tautau": + return ditau + elif chn == "etau": + return "e" + tau + elif chn == "mutau": + return mu + tau + +def rec_dd(): + return dd(rec_dd) + +def set_fig(fig, legend=True): + fig.output_backend = 'svg' + fig.toolbar.logo = None + # if legend: + # fig.legend.click_policy='hide' + # fig.legend.location = 'top_left' + # fig.legend.label_text_font_size = '8pt' + fig.min_border_bottom = 5 + fig.xaxis.visible = True + fig.title.align = "left" + fig.title.text_font_size = "15px" + fig.xaxis.axis_label_text_font_style = "bold" + fig.yaxis.axis_label_text_font_style = "bold" + fig.xaxis.axis_label_text_font_size = "13px" + fig.yaxis.axis_label_text_font_size = "13px" + +def main(args): + with uproot.open("data/regions_preEE_11p4-jet-higher-pt_all.root".format(args.channels[0])) as file: + # from matplotlib.colors import ListedColormap + petroff6 = ["#5790fc", "#f89c20", "#e42536", "#964a8b", "#9c9ca1", "#7a21dd"] + channel_base = file['BaseTau_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_inclusive = file['NoBaseTauMETTau_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_met = file['NoBaseTauMETNoTau_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_tau = file['NoBaseTauNoMETTau_baseline_{}_genHH_mass'.format(args.channels[0])] + + # err_inclusive = np.sqrt( (1/channel_base.values() * channel_inclusive.errors()) ** 2 + (channel_inclusive.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_met = np.sqrt( (1/channel_base.values() * channel_base_plus_met.errors()) ** 2 + (channel_base_plus_met.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_tau = np.sqrt( (1/channel_base.values() * channel_base_plus_tau.errors()) ** 2 + (channel_base_plus_tau.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # sum_base = channel_base.values().sum() + # err_tot_base = channel_base.variances().sum() + # sum_inclusive = channel_inclusive.values().sum() + # err_tot_inclusive = channel_inclusive.variances().sum() + # sum_met = channel_base_plus_met.values().sum() + # err_tot_omet = channel_base_plus_met.variances().sum() + # sum_tau = channel_base_plus_tau.values().sum() + # err_tot_otau = channel_base_plus_tau.variances().sum() + # err_tot_met = np.sqrt((err_tot_inclusive + err_tot_omet)/(sum_base**2) + (sum_inclusive + sum_met) ** 2 * err_tot_base / (sum_base ** 4)) + # err_tot_tau = np.sqrt((err_tot_inclusive + err_tot_otau)/(sum_base**2) + (sum_inclusive + sum_tau) ** 2 * err_tot_base /(sum_base ** 4)) + # err_tot_met = round(err_tot_met * 100, 2) + # err_tot_tau = round(err_tot_tau * 100, 2) + # sum_overlap = sum_inclusive/(sum_inclusive + sum_met + sum_tau) + # sum_overlap = round(sum_overlap * 100, 2) + # err_overlap = np.sqrt((err_tot_inclusive)/((sum_inclusive + sum_met + sum_tau) ** 2) + (sum_inclusive ** 2)/((sum_inclusive + sum_met + sum_tau) ** 4)*(err_tot_inclusive + err_tot_omet + err_tot_otau)) + # err_overlap = round(err_overlap * 100, 2) + + # err_base_plus_ttjet = np.sqrt( (1/channel_base.values() * channel_base_plus_ttjet.errors()) ** 2 + (channel_base_plus_ttjet.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_Mu50 = np.sqrt( (1/channel_base.values() * channel_base_plus_Mu50.errors()) ** 2 + (channel_base_plus_Mu50.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_Ele28 = np.sqrt( (1/channel_base.values() * channel_base_plus_Ele28.errors()) ** 2 + (channel_base_plus_Ele28.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_all = np.sqrt( (1/channel_base.values() * channel_all.errors()) ** 2 + (channel_all.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + ax = channel_base.axes[0].centers() + edges = channel_base.axes[0].edges() + xerr_r = ax - edges[:-1] + xerr_l = edges[1:] - ax + print(edges, xerr_r, xerr_l) + print(channel_base.errors()/channel_base.values()) + hep.style.use("CMS") + + plt.figure(0) + # plt.bar(x=ax, height=(channel_base_plus_met.values()/channel_base.values()) * 100, bottom= (channel_inclusive.values()/channel_base.values()) * 100,width=xerr_r * 2, yerr=err_base_plus_met * 100, color="#fff", edgecolor=petroff6[0], ecolor=petroff6[0], label="MET & !Tau") + + # plt.bar(x=ax, height=-(channel_base_plus_tau.values()/channel_base.values()) * 100, bottom=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_base_plus_tau * 100, label="Tau & !MET", color="#fff", edgecolor=petroff6[1], ecolor=petroff6[1]) + # plt.bar(x=ax, height=(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2], label="MET & Tau") + # plt.bar(x=ax, height=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2]) + # plt.axhline(y=0, color='black', linestyle='-') + + # plt.text(220, 24, "Total MET Gain: {} +/- {}%".format(round((sum_inclusive + sum_met)/sum_base * 100, 2), err_tot_met)) + # plt.text(250, -30.8, "Total Tau Gain: {} +/- {}%".format(round((sum_inclusive + sum_tau)/sum_base * 100, 2), err_tot_tau)) + # print("Total MET Gain: {} +/- {}%".format(round((sum_inclusive + sum_met)/sum_base * 100, 2), err_tot_met)) + # print("Total Tau Gain: {} +/- {}%".format(round((sum_inclusive + sum_tau)/sum_base * 100, 2), err_tot_tau)) + # print(sum_inclusive, sum_met, sum_tau) + # print("Total MET/Tau overlap: {} +/- {}%".format(sum_overlap, err_overlap)) + # ticks_locs, ticks_labels = plt.yticks() + # print(ticks_locs, ticks_labels) +# set labels to absolute values and with integer representation + # plt.yticks(ticks_locs, [int(abs(tick)) for tick in ticks_locs]) + + # plt.errorbar(ax - 10, (channel_base_plus_met.values() / channel_base.values()) * 100, yerr=err_base_plus_met*100, xerr=[xerr_r - 10, xerr_l + 10], fmt='o', label="{} + MET".format(base), color=petroff6[0]) + # plt.errorbar(ax, (channel_base_plus_tau.values() / channel_base.values()) * 100, yerr=err_base_plus_tau*100, xerr=[xerr_r, xerr_l], fmt='o', label="{} + IsoTau".format(base), color=petroff6[1]) + # if args.channels[0] == "tautau": + # plt.errorbar(ax + 10, (channel_base_plus_Tau.values() / channel_base.values()) * 100, yerr=err_base_plus_ttjet*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="{} + TauTauJet".format(base), color=petroff6[2]) + # if args.channels[0] == "mutau": + # plt.errorbar(ax + 10, (channel_base_plus_Mu50.values() / channel_base.values()) * 100, yerr=err_base_plus_Mu50*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="{} + Mu50".format(base), color=petroff6[2]) + # if args.channels[0] == "etau": + # plt.errorbar(ax + 10, (channel_base_plus_Ele28.values() / channel_base.values()) * 100, yerr=err_base_plus_Ele28*100, xerr=[xerr_r + 10, xerr_l - 10], fmt='o', label="{} + Ele28".format(base), color=petroff6[2]) + + # plt.errorbar(ax + 20, (channel_all.values() / channel_base.values()) * 100, yerr=err_all*100, xerr=[xerr_r + 20, xerr_l - 20], fmt='o', label="{} + MET + IsoTau + {}".format(base, last_trig), color=petroff6[3]) + # plt.xlabel(r"m$_{HH}$ [GeV]") + # plt.ylabel("Gain [%]") + # plt.legend() + # hep.cms.label(lumi="9.8", com="13.6") + # plt.savefig("orthogonal_gains_{}.png".format(args.channels[0])) + + channel_inclusive = file['NoBaseTauTTJet4JetsPNet_baseline_{}_genHH_mass'.format(args.channels[0])] + channel_base_plus_ttjet = file['NoBaseTauTauTauJetNo4JetsPNet_baseline_{}_genHH_mass'.format(args.channels[0])] + channel_base_plus_QuadJetPNet = file['NoBaseTauNoTauTauJet4JetsPNet_baseline_{}_genHH_mass'.format(args.channels[0])] + + err_inclusive = np.sqrt( (1/channel_base.values() * channel_inclusive.errors()) ** 2 + (channel_inclusive.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + err_base_plus_ttjet = np.sqrt( (1/channel_base.values() * channel_base_plus_ttjet.errors()) ** 2 + (channel_base_plus_ttjet.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + err_base_plus_QuadJetPNet = np.sqrt( (1/channel_base.values() * channel_base_plus_QuadJetPNet.errors()) ** 2 + (channel_base_plus_QuadJetPNet.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + sum_base = channel_base.values().sum() + err_tot_base = channel_base.variances().sum() + sum_inclusive = channel_inclusive.values().sum() + err_tot_inclusive = channel_inclusive.variances().sum() + sum_ttjet = channel_base_plus_ttjet.values().sum() + err_tot_ottjet = channel_base_plus_ttjet.variances().sum() + sum_QuadJetPNet = channel_base_plus_QuadJetPNet.values().sum() + err_tot_oQuadJetPNet = channel_base_plus_QuadJetPNet.variances().sum() + err_tot_ttjet = np.sqrt((err_tot_inclusive + err_tot_ottjet)/(sum_base**2) + (sum_inclusive + sum_ttjet) ** 2 * err_tot_base / (sum_base ** 4)) + err_tot_QuadJetPNet = np.sqrt((err_tot_inclusive + err_tot_oQuadJetPNet)/(sum_base**2) + (sum_inclusive + sum_QuadJetPNet) ** 2 * err_tot_base /(sum_base ** 4)) + err_tot_ttjet = round(err_tot_ttjet * 100, 2) + err_tot_QuadJetPNet = round(err_tot_QuadJetPNet * 100, 2) + sum_overlap = sum_inclusive/(sum_inclusive + sum_ttjet + sum_QuadJetPNet) + sum_overlap = round(sum_overlap * 100, 2) + err_overlap = np.sqrt((err_tot_inclusive)/((sum_inclusive + sum_ttjet + sum_QuadJetPNet) ** 2) + (sum_inclusive ** 2)/((sum_inclusive + sum_ttjet + sum_QuadJetPNet) ** 4)*(err_tot_inclusive + err_tot_ottjet + err_tot_oQuadJetPNet)) + err_overlap = round(err_overlap * 100, 2) + + + plt.figure(1) + plt.bar(x=ax, height=(channel_base_plus_ttjet.values()/channel_base.values()) * 100, bottom= (channel_inclusive.values()/channel_base.values()) * 100,width=xerr_r * 2, yerr=err_base_plus_ttjet * 100, color="#fff", edgecolor=petroff6[0], ecolor=petroff6[0], label="TauTauJet & !Tau") + + plt.bar(x=ax, height=-(channel_base_plus_QuadJetPNet.values()/channel_base.values()) * 100, bottom=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_base_plus_QuadJetPNet * 100, label="QuadJetPNet & !TauTauJet", color="#fff", edgecolor=petroff6[1], ecolor=petroff6[1]) + plt.bar(x=ax, height=(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2], label="TauTauJet & QuadJetPNet") + plt.bar(x=ax, height=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2]) + plt.axhline(y=0, color='black', linestyle='-') + + # plt.text(220, 24, "Total ttjet Gain: {} +/- {}%".format(round((sum_inclusive + sum_ttjet)/sum_base * 100, 2), err_tot_ttjet)) + # plt.text(250, -30.8, "Total QuadJetPNet Gain: {} +/- {}%".format(round((sum_inclusive + sum_QuadJetPNet)/sum_base * 100, 2), err_tot_QuadJetPNet)) + print("Total ttjet Gain: {} +/- {}%".format(round((sum_inclusive + sum_ttjet)/sum_base * 100, 2), err_tot_ttjet)) + print("Total QuadJetPNet Gain: {} +/- {}%".format(round((sum_inclusive + sum_QuadJetPNet)/sum_base * 100, 2), err_tot_QuadJetPNet)) + print(sum_inclusive, sum_ttjet, sum_QuadJetPNet) + print("Total ttjet/QuadJetPNet overlap: {} +/- {}%".format(sum_overlap, err_overlap)) + ticks_locs, ticks_labels = plt.yticks() + print(ticks_locs, ticks_labels) +# set labels to absolute values and with integer representation + plt.yticks(ticks_locs, [int(abs(tick)) for tick in ticks_locs]) + + plt.xlabel(r"m$_{HH}$ [GeV]") + plt.ylabel("Gain [%]") + plt.legend() + hep.cms.label(lumi="27.8", com="13.6") + plt.savefig("orthogonal_gains_{}_QuadJetPNet_preEE_11p4_withOffline_pf75.png".format(args.channels[0])) + + # if args.channels[0] == "tautau": + # channel_inclusive = file['NoBaseMETTTJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_met = file['NoBaseMETNoTTJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_TTJet = file['NoBaseNoMETTTJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # err_inclusive = np.sqrt( (1/channel_base.values() * channel_inclusive.errors()) ** 2 + (channel_inclusive.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_met = np.sqrt( (1/channel_base.values() * channel_base_plus_met.errors()) ** 2 + (channel_base_plus_met.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_TTJet = np.sqrt( (1/channel_base.values() * channel_base_plus_TTJet.errors()) ** 2 + (channel_base_plus_TTJet.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # sum_base = channel_base.values().sum() + # err_tot_base = channel_base.variances().sum() + # sum_inclusive = channel_inclusive.values().sum() + # err_tot_inclusive = channel_inclusive.variances().sum() + # sum_met = channel_base_plus_met.values().sum() + # err_tot_omet = channel_base_plus_met.variances().sum() + # sum_TTJet = channel_base_plus_TTJet.values().sum() + # err_tot_oTTJet = channel_base_plus_TTJet.variances().sum() + # err_tot_met = np.sqrt((err_tot_inclusive + err_tot_omet)/(sum_base**2) + (sum_inclusive + sum_met) ** 2 * err_tot_base / (sum_base ** 4)) + # err_tot_TTJet = np.sqrt((err_tot_inclusive + err_tot_oTTJet)/(sum_base**2) + (sum_inclusive + sum_TTJet) ** 2 * err_tot_base /(sum_base ** 4)) + # err_tot_met = round(err_tot_met * 100, 2) + # err_tot_TTJet = round(err_tot_TTJet * 100, 2) + # sum_overlap = sum_inclusive/(sum_inclusive + sum_met + sum_TTJet) + # sum_overlap = round(sum_overlap * 100, 2) + # err_overlap = np.sqrt((err_tot_inclusive)/((sum_inclusive + sum_met + sum_TTJet) ** 2) + (sum_inclusive ** 2)/((sum_inclusive + sum_met + sum_TTJet) ** 4)*(err_tot_inclusive + err_tot_omet + err_tot_oTTJet)) + # err_overlap = round(err_overlap * 100, 2) + + # plt.figure(1) + # plt.bar(x=ax, height=(channel_base_plus_met.values()/channel_base.values()) * 100, bottom= (channel_inclusive.values()/channel_base.values()) * 100,width=xerr_r * 2, yerr=err_base_plus_met * 100, color="#fff", edgecolor=petroff6[0], ecolor=petroff6[0], label="MET & !TTJet") + + # plt.bar(x=ax, height=-(channel_base_plus_TTJet.values()/channel_base.values()) * 100, bottom=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_base_plus_TTJet * 100, label="TTJet & !MET", color="#fff", edgecolor=petroff6[1], ecolor=petroff6[1]) + # plt.bar(x=ax, height=(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2], label="MET & TTJet") + # plt.bar(x=ax, height=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2]) + # plt.axhline(y=0, color='black', linestyle='-') + # # plt.text(250, -30.8, "Total Tau Gain: {} +/- {}%".format(round((sum_inclusive + sum_tau)/sum_base * 100, 2), err_tot_tau)) + # print("Total MET Gain: {} +/- {}%".format(round((sum_inclusive + sum_met)/sum_base * 100, 2), err_tot_met)) + # print("Total TTJet Gain: {} +/- {}%".format(round((sum_inclusive + sum_TTJet)/sum_base * 100, 2), err_tot_TTJet)) + # print("Total MET/TTJet overlap: {} +/- {}%".format(sum_overlap, err_overlap)) + # ticks_locs, ticks_labels = plt.yticks() + # print(ticks_locs, ticks_labels) + # plt.xlabel(r"m$_{HH}$ [GeV]") + # plt.ylabel("Gain [%]") + # plt.legend() + # hep.cms.label(lumi="9.8", com="13.6") + # plt.savefig("orthogonal_gains_{}_ttjet.png".format(args.channels[0])) + + # channel_inclusive = file['NoBaseTauTTJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_tau = file['NoBaseTauNoTTJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_TTJet = file['NoBaseNoTauTTJet_baseline_{}_genHH_mass'.format(args.channels[0])] + # err_inclusive = np.sqrt( (1/channel_base.values() * channel_inclusive.errors()) ** 2 + (channel_inclusive.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_tau = np.sqrt( (1/channel_base.values() * channel_base_plus_tau.errors()) ** 2 + (channel_base_plus_tau.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_TTJet = np.sqrt( (1/channel_base.values() * channel_base_plus_TTJet.errors()) ** 2 + (channel_base_plus_TTJet.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # sum_base = channel_base.values().sum() + # err_tot_base = channel_base.variances().sum() + # sum_inclusive = channel_inclusive.values().sum() + # err_tot_inclusive = channel_inclusive.variances().sum() + # sum_tau = channel_base_plus_tau.values().sum() + # err_tot_otau = channel_base_plus_tau.variances().sum() + # sum_TTJet = channel_base_plus_TTJet.values().sum() + # err_tot_oTTJet = channel_base_plus_TTJet.variances().sum() + # err_tot_tau = np.sqrt((err_tot_inclusive + err_tot_otau)/(sum_base**2) + (sum_inclusive + sum_tau) ** 2 * err_tot_base / (sum_base ** 4)) + # err_tot_TTJet = np.sqrt((err_tot_inclusive + err_tot_oTTJet)/(sum_base**2) + (sum_inclusive + sum_TTJet) ** 2 * err_tot_base /(sum_base ** 4)) + # err_tot_tau = round(err_tot_tau * 100, 2) + # err_tot_TTJet = round(err_tot_TTJet * 100, 2) + # sum_overlap = sum_inclusive/(sum_inclusive + sum_tau + sum_TTJet) + # sum_overlap = round(sum_overlap * 100, 2) + # err_overlap = np.sqrt((err_tot_inclusive)/((sum_inclusive + sum_tau + sum_TTJet) ** 2) + (sum_inclusive ** 2)/((sum_inclusive + sum_tau + sum_TTJet) ** 4)*(err_tot_inclusive + err_tot_otau + err_tot_oTTJet)) + # err_overlap = round(err_overlap * 100, 2) + + # plt.figure(2) + # plt.bar(x=ax, height=(channel_base_plus_tau.values()/channel_base.values()) * 100, bottom= (channel_inclusive.values()/channel_base.values()) * 100,width=xerr_r * 2, yerr=err_base_plus_tau * 100, color="#fff", edgecolor=petroff6[0], ecolor=petroff6[0], label="Tau & !TauTauJet") + + # plt.bar(x=ax, height=-(channel_base_plus_TTJet.values()/channel_base.values()) * 100, bottom=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_base_plus_TTJet * 100, label="TauTauJet & !Tau", color="#fff", edgecolor=petroff6[1], ecolor=petroff6[1]) + # plt.bar(x=ax, height=(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2], label="Tau & TauTauJet") + # plt.bar(x=ax, height=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2]) + # plt.axhline(y=0, color='black', linestyle='-') + # # plt.text(250, -30.8, "Total Tau Gain: {} +/- {}%".format(round((sum_inclusive + sum_tau)/sum_base * 100, 2), err_tot_tau)) + # print("Total Tau Gain: {} +/- {}%".format(round((sum_inclusive + sum_tau)/sum_base * 100, 2), err_tot_met)) + # print("Total TTJet Gain: {} +/- {}%".format(round((sum_inclusive + sum_TTJet)/sum_base * 100, 2), err_tot_TTJet)) + # print("Total Tau/TTJet overlap: {} +/- {}%".format(sum_overlap, err_overlap)) + # ticks_locs, ticks_labels = plt.yticks() + # print(ticks_locs, ticks_labels) + # plt.xlabel(r"m$_{HH}$ [GeV]") + # plt.ylabel("Gain [%]") + # plt.legend() + # hep.cms.label(lumi="9.8", com="13.6") + # plt.savefig("orthogonal_gains_{}_tau_ttjet.png".format(args.channels[0])) + # # print(ratio.axes[0]) + + # # elif args.channels[0] == "mutau": + # channel_inclusive = file['NoBaseMETMu50_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_met = file['NoBaseMETNoMu50_baseline_{}_genHH_mass'.format(args.channels[0])] + # channel_base_plus_Mu50 = file['NoBaseNoMETMu50_baseline_{}'.format(args.channels[0])] + # err_inclusive = np.sqrt( (1/channel_base.values() * channel_inclusive.errors()) ** 2 + (channel_inclusive.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_met = np.sqrt( (1/channel_base.values() * channel_base_plus_met.errors()) ** 2 + (channel_base_plus_met.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_Mu50 = np.sqrt( (1/channel_base.values() * channel_base_plus_Mu50.errors()) ** 2 + (channel_base_plus_Mu50.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # sum_base = channel_base.values().sum() + # err_tot_base = channel_base.variances().sum() + # sum_inclusive = channel_inclusive.values().sum() + # err_tot_inclusive = channel_inclusive.variances().sum() + # sum_met = channel_base_plus_met.values().sum() + # err_tot_omet = channel_base_plus_met.variances().sum() + # sum_Mu50 = channel_base_plus_Mu50.values().sum() + # err_tot_oMu50 = channel_base_plus_Mu50.variances().sum() + # err_tot_met = np.sqrt((err_tot_inclusive + err_tot_omet)/(sum_base**2) + (sum_inclusive + sum_met) ** 2 * err_tot_base / (sum_base ** 4)) + # err_tot_Mu50 = np.sqrt((err_tot_inclusive + err_tot_oMu50)/(sum_base**2) + (sum_inclusive + sum_Mu50) ** 2 * err_tot_base /(sum_base ** 4)) + # sum_overlap = sum_inclusive/(sum_inclusive + sum_met + sum_Mu50) + # sum_overlap = round(sum_overlap * 100, 2) + # err_overlap = np.sqrt((err_tot_inclusive)/((sum_inclusive + sum_met + sum_Mu50) ** 2) + (sum_inclusive ** 2)/((sum_inclusive + sum_met + sum_Mu50) ** 4)*(err_tot_inclusive + err_tot_omet + err_tot_oMu50)) + # err_tot_met = round(err_tot_met * 100, 2) + # err_tot_Mu50 = round(err_tot_Mu50 * 100, 2) + # err_overlap = round(err_overlap * 100, 2) + + # plt.figure(1) + # plt.bar(x=ax, height=(channel_base_plus_met.values()/channel_base.values()) * 100, bottom= (channel_inclusive.values()/channel_base.values()) * 100,width=xerr_r * 2, yerr=err_base_plus_met * 100, color="#fff", edgecolor=petroff6[0], ecolor=petroff6[0], label="MET & !Mu50") + + # plt.bar(x=ax, height=-(channel_base_plus_Mu50.values()/channel_base.values()) * 100, bottom=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_base_plus_Mu50 * 100, label="Mu50 & !MET", color="#fff", edgecolor=petroff6[1], ecolor=petroff6[1]) + # plt.bar(x=ax, height=(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2], label="MET & Mu50") + # plt.bar(x=ax, height=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2]) + # plt.axhline(y=0, color='black', linestyle='-') + # # plt.text(250, -30.8, "Total Tau Gain: {} +/- {}%".format(round((sum_inclusive + sum_tau)/sum_base * 100, 2), err_tot_tau)) + + + # print("Total MET Gain: {} +/- {}%".format(round((sum_inclusive + sum_met)/sum_base * 100, 2), err_tot_met)) + # print("Total Mu50 Gain: {} +/- {}%".format(round((sum_inclusive + sum_Mu50)/sum_base * 100, 2), err_tot_Mu50)) + # print("Total MET/Mu50 overlap: {} +/- {}%".format(sum_overlap, err_overlap)) + # ticks_locs, ticks_labels = plt.yticks() + # print(ticks_locs, ticks_labels) + # plt.xlabel(r"m$_{HH}$ [GeV]") + # plt.ylabel("Gain [%]") + # plt.legend() + # hep.cms.label(lumi="9.8", com="13.6") + # plt.savefig("orthogonal_gains_{}_Mu50.png".format(args.channels[0])) + + # channel_inclusive = file['NoBaseTauMu50_baseline_{}'.format(args.channels[0])] + # channel_base_plus_tau = file['NoBaseTauNoMu50_baseline_{}'.format(args.channels[0])] + # channel_base_plus_Mu50 = file['NoBaseNoTauMu50_baseline_{}'.format(args.channels[0])] + # err_inclusive = np.sqrt( (1/channel_base.values() * channel_inclusive.errors()) ** 2 + (channel_inclusive.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_tau = np.sqrt( (1/channel_base.values() * channel_base_plus_tau.errors()) ** 2 + (channel_base_plus_tau.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # err_base_plus_Mu50 = np.sqrt( (1/channel_base.values() * channel_base_plus_Mu50.errors()) ** 2 + (channel_base_plus_Mu50.values()/(channel_base.values() ** 2) * channel_base.errors()) ** 2 ) + # sum_base = channel_base.values().sum() + # err_tot_base = channel_base.variances().sum() + # sum_inclusive = channel_inclusive.values().sum() + # err_tot_inclusive = channel_inclusive.variances().sum() + # sum_tau = channel_base_plus_tau.values().sum() + # err_tot_otau = channel_base_plus_tau.variances().sum() + # sum_Mu50 = channel_base_plus_Mu50.values().sum() + # err_tot_oMu50 = channel_base_plus_Mu50.variances().sum() + # err_tot_tau = np.sqrt((err_tot_inclusive + err_tot_otau)/(sum_base**2) + (sum_inclusive + sum_tau) ** 2 * err_tot_base / (sum_base ** 4)) + # err_tot_Mu50 = np.sqrt((err_tot_inclusive + err_tot_oMu50)/(sum_base**2) + (sum_inclusive + sum_Mu50) ** 2 * err_tot_base /(sum_base ** 4)) + # err_tot_tau = round(err_tot_tau * 100, 2) + # err_tot_Mu50 = round(err_tot_Mu50 * 100, 2) + # sum_overlap = sum_inclusive/(sum_inclusive + sum_tau + sum_Mu50) + # sum_overlap = round(sum_overlap * 100, 2) + # err_overlap = np.sqrt((err_tot_inclusive)/((sum_inclusive + sum_tau + sum_Mu50) ** 2) + (sum_inclusive ** 2)/((sum_inclusive + sum_tau + sum_Mu50) ** 4)*(err_tot_inclusive + err_tot_otau + err_tot_oMu50)) + # err_overlap = round(err_overlap * 100, 2) + + # plt.figure(2) + # plt.bar(x=ax, height=(channel_base_plus_tau.values()/channel_base.values()) * 100, bottom= (channel_inclusive.values()/channel_base.values()) * 100,width=xerr_r * 2, yerr=err_base_plus_tau * 100, color="#fff", edgecolor=petroff6[0], ecolor=petroff6[0], label="Tau & !Mu50") + + # plt.bar(x=ax, height=-(channel_base_plus_Mu50.values()/channel_base.values()) * 100, bottom=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_base_plus_Mu50 * 100, label="Mu50 & !Tau", color="#fff", edgecolor=petroff6[1], ecolor=petroff6[1]) + # plt.bar(x=ax, height=(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2], label="Tau & Mu50") + # plt.bar(x=ax, height=-(channel_inclusive.values()/channel_base.values()) * 100, width=xerr_r * 2, yerr=err_inclusive * 100, color="#fff", edgecolor=petroff6[2], ecolor=petroff6[2]) + # plt.axhline(y=0, color='black', linestyle='-') + # # plt.text(250, -30.8, "Total Tau Gain: {} +/- {}%".format(round((sum_inclusive + sum_tau)/sum_base * 100, 2), err_tot_tau)) + # print("Total Tau Gain: {} +/- {}%".format(round((sum_inclusive + sum_tau)/sum_base * 100, 2), err_tot_met)) + # print("Total Mu50 Gain: {} +/- {}%".format(round((sum_inclusive + sum_Mu50)/sum_base * 100, 2), err_tot_Mu50)) + # print("Total Tau/Mu50 overlap: {} +/- {}%".format(sum_overlap, err_overlap)) + # ticks_locs, ticks_labels = plt.yticks() + # print(ticks_locs, ticks_labels) + # plt.xlabel(r"m$_{HH}$ [GeV]") + # plt.ylabel("Gain [%]") + # plt.legend() + # hep.cms.label(lumi="9.8", com="13.6") + # plt.savefig("orthogonal_gains_{}_tau_Mu50.png".format(args.channels[0])) + return + channels = args.channels + linear_x = [k for k in range(1,2)] + edges_x = [k-0.5 for k in range(1,2)] + [1.5] + ptcuts = {chn: utils.get_ptcuts(chn, args.year) for chn in args.channels} + + nevents, errors = dd(lambda: dd(dict)), dd(lambda: dd(dict)) + ratios, eratios = dd(lambda: dd(dict)), dd(lambda: dd(dict)) + + for adir in main_dir: + dRstr = str(args.deltaR).replace('.', 'p') + if len(args.channels) == 1: + output_name = os.path.join(base_dir, 'trigger_gains_{}_{}_DR{}'.format(args.channels[0], + args.year, dRstr)) + elif len(args.channels) == 2: + output_name = os.path.join(base_dir, 'trigger_gains_{}_{}_{}_DR{}'.format(*args.channels[:2], + args.year, dRstr)) + elif len(args.channels) == 3: + output_name = os.path.join(base_dir, 'trigger_gains_all_{}_DR{}'.format(args.year, dRstr)) + if args.bigtau: + output_name += "_BIGTAU" + output_name += ".html" + output_file(output_name) + print('Saving file {}.'.format(output_name)) + + for chn in channels: + md = adir[chn] + in_base = os.path.join(base_dir, md) + + nevents[md][chn]['base'], nevents[md][chn]['met'], nevents[md][chn]['tau'] = [], [], [] + ratios[md][chn]['two'], ratios[md][chn]['met'], ratios[md][chn]['tau'] = [], [], [] + eratios[md][chn]['two'], eratios[md][chn]['met'], eratios[md][chn]['tau'] = [], [], [] + errors[md][chn] = [] + + outname = get_outname( chn, [str(x) for x in args.region_cuts], + [str(x) for x in ptcuts[chn]], str(args.met_turnon), + args.bigtau) + + print(outname) + with open(outname, "rb") as f: + ahistos = pickle.load(f) + + #all regions summed + sum_base_tot = round(ahistos["Base"]["legacy"]["baseline"].values().sum() + + ahistos["Base"]["tau"]["baseline"].values().sum() + + ahistos["Base"]["met"]["baseline"].values().sum()) + print(ahistos["Base"]["legacy"]["baseline"].values()) + #legacy region + l1 = lambda x : round(x["legacy"]["baseline"].values().sum(), 2) + sum_base = l1(ahistos["Base"]) + sum_vbf = l1(ahistos["VBF"]) + sum_met = l1(ahistos["NoBaseMET"]) + sum_only_tau = l1(ahistos["NoBaseNoMETTau"]) + sum_tau = l1(ahistos["NoBaseTau"]) + sum_basekin = l1(ahistos["LegacyKin"]) + w2_basekin = ahistos["METKin"]["legacy"]["baseline"].variances().sum() + + #MET region + l2 = lambda x : round(x["met"]["baseline"].values().sum(), 2) + sum_metkin = l2(ahistos["METKin"]) + w2_metkin = ahistos["METKin"]["met"]["baseline"].variances().sum() + + #Single Tau region + l3 = lambda x : round(x["tau"]["baseline"].values().sum(), 2) + sum_taukin = l3(ahistos["TauKin"]) + w2_taukin = ahistos["TauKin"]["tau"]["baseline"].variances().sum() + + #hypothetical VBF region + sum_vbfkin = l2(ahistos["VBFKin"]) + l3(ahistos["VBFKin"]) + + nevents[md][chn]['base'].append(sum_basekin) + nevents[md][chn]['met'].append(sum_basekin + sum_metkin) + nevents[md][chn]['tau'].append(sum_basekin + sum_metkin + sum_taukin) + + rat_met_num = sum_basekin + sum_metkin + rat_met_all = rat_met_num / sum_base_tot + + rat_tau_num = sum_basekin + sum_taukin + rat_tau_all = rat_tau_num / sum_base_tot + + rat_all_num = sum_basekin + sum_taukin + sum_metkin + rat_all = rat_all_num / sum_base_tot + + ratios[md][chn]['met'].append(rat_met_all) + ratios[md][chn]['tau'].append(rat_tau_all) + ratios[md][chn]['two'].append(rat_all) + + e_metkin = np.sqrt(w2_metkin) + e_taukin = np.sqrt(w2_taukin) + e_basekin = np.sqrt(w2_basekin) + + e_tau_num = np.sqrt(w2_taukin + w2_basekin) + e_met_num = np.sqrt(w2_metkin + w2_basekin) + e_all_num = np.sqrt(w2_metkin + w2_taukin + w2_basekin) + print(rat_tau_num, rat_met_num, rat_all_num) + + eratios[md][chn]['tau'].append(rat_tau_all * np.sqrt(e_tau_num**2/rat_tau_num**2 + 1/sum_base_tot)) + eratios[md][chn]['met'].append(rat_met_all * np.sqrt(e_met_num**2/rat_met_num**2 + 1/sum_base_tot)) + eratios[md][chn]['two'].append(rat_all * np.sqrt(e_all_num**2/rat_all_num**2 + 1/sum_base_tot)) + errors[md][chn].append(e_all_num) + + json_name = 'data_' + chn + '_' + json_name += ('bigtau' if args.bigtau else 'standard') + '.json' + with open(json_name, 'w', encoding='utf-8') as json_obj: + json_data = {"vals": {chn: nevents[adir[chn]][chn]['tau'] for chn in channels}} + json_data.update({"errs": {chn: errors[adir[chn]][chn] for chn in channels}}) + json.dump(json_data, json_obj, ensure_ascii=False, indent=4) + + opt_points = dict(size=8) + opt_line = dict(width=1.5) + colors = ('green', 'blue', 'red', 'brown') + styles = ('solid', 'dashed', 'dotdash') + legends = {'base': 'Legacy', + 'met': 'MET', 'tau': 'Single Tau', + 'two': 'MET + Single Tau', 'vbf': 'VBF'} + + x_str = [str(k) for k in [0]] + xticks = linear_x[:] + yticks = [x for x in range(0,110,5)] + shift_one = {'met': [-0.15, 0., 0.15], 'tau': [-0.20, -0.05, 0.1], + 'vbf': [-0.10, 0.05, 0.20]} + shift_both = {'met': [-0.15, 0., 0.15], 'two': [-0.20, -0.05, 0.1]} + shift_kin = {'met': [-0.09, 0., 0.15], 'tau': [0.03, -0.05, 0.1], + 'two': [-0.03, 0.05, 0.20], 'vbf': [0.09, 0.1, 0.25]} + + for adir in main_dir: + print(adir) + p_opt = dict(width=800, height=400, x_axis_label='x', y_axis_label='y') + p1 = figure(title='Event number (' + pp(channels[0]) + ')', y_axis_type="linear", **p_opt) + p2 = figure(title='Acceptance Gain (' + pp(channels[0]) + ')', **p_opt) if len(channels)==1 else figure(**p_opt) + + p1.yaxis.axis_label = 'Weighted number of events' + p2.yaxis.axis_label = 'Trigger acceptance gain (w.r.t. trigger baseline) [%]' + pics = (p1, p2) + for p in pics: + set_fig(p) + + for ichn,chn in enumerate(channels): + md = adir[chn] + print(md) + + p1.quad(top=nevents[md][chn]["base"], bottom=0, + left=edges_x[:-1], right=edges_x[1:], + legend_label=legends["base"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), + fill_color="dodgerblue", line_color="black") + p1.quad(top=nevents[md][chn]["met"], bottom=nevents[md][chn]["base"], + left=edges_x[:-1], right=edges_x[1:], + legend_label=legends["met"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), + fill_color="green", line_color="black") + p1.quad(top=nevents[md][chn]["tau"], bottom=nevents[md][chn]["met"], + left=edges_x[:-1], right=edges_x[1:], + legend_label=legends["tau"]+(' ('+pp(chn)+')' if len(channels)>1 else ''), + fill_color="red", line_color="black") + print(md) + for itd,td in enumerate(('met', 'tau', 'two')): + p2.circle([x+shift_kin[td][ichn] for x in linear_x], + [(x-1)*100. for x in ratios[md][chn][td]], + color=colors[itd], fill_alpha=1., **opt_points) + p2.line([x+shift_kin[td][ichn] for x in linear_x], + [(x-1)*100. for x in ratios[md][chn][td]], + color=colors[itd], line_dash=styles[ichn], + legend_label=legends[td]+(' ('+pp(chn)+')' if len(channels)>1 else ''), **opt_line) + p2.multi_line( + [(x+shift_kin[td][ichn],x+shift_kin[td][ichn]) for x in linear_x], + [((y-1)*100-(x*50.),(y-1)*100+(x*50.)) for x,y in zip(eratios[md][chn][td],ratios[md][chn][td])], + color=colors[itd], **opt_line) + + p1.legend.location = 'top_right' + p2.legend.location = 'top_left' + for p in pics: + p.xaxis[0].ticker = xticks + p.xgrid[0].ticker = xticks + p.xgrid.grid_line_alpha = 0.2 + p.xgrid.grid_line_color = 'black' + # p.yaxis[0].ticker = yticks + # p.ygrid[0].ticker = yticks + p.ygrid.grid_line_alpha = 0.2 + p.ygrid.grid_line_color = 'black' + + p.xaxis.axis_label = "m(X) [GeV]" + + p.xaxis.major_label_overrides = dict(zip(linear_x,x_str)) + + p.legend.click_policy='hide' + + p.output_backend = 'svg' + #export_svg(p, filename='line_graph.svg') + + g = gridplot([[p] for p in pics]) + print(g) + save(g, title=md) + export_png(g, filename="line_graph.png") + +if __name__ == '__main__': + desc = "Produce plots of trigger gain VS resonance mass.\n" + desc += "Uses the output of test_trigger_regions.py." + desc += "When running on many channels, one should keep in mind each channel has different pT cuts." + desc += "This might imply moving sub-folders (produced by the previous script) around." + parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) + + # parser.add_argument('--masses', required=True, nargs='+', type=str, + # help='Resonance mass') + parser.add_argument('--channels', required=True, nargs='+', type=str, + choices=('etau', 'mutau', 'tautau'), + help='Select the channel over which the workflow will be run.' ) + parser.add_argument('--year', required=True, type=str, choices=('2016', '2017', '2018', '2022'), + help='Select the year over which the workflow will be run.' ) + parser.add_argument('--deltaR', type=float, default=0.5, help='DeltaR between the two leptons.') + parser.add_argument('--bigtau', action='store_true', + help='Consider a larger single tau region, reducing the ditau one.') + parser.add_argument('--met_turnon', required=False, type=str, default=180, + help='MET trigger turnon cut [GeV].' ) + parser.add_argument('--region_cuts', required=False, type=float, nargs=2, default=(190, 190), + help='High/low regions pT1 and pT2 selection cuts [GeV].' ) + + args = utils.parse_args(parser) + + base_dir = '/t3home/fbilandz/TriggerScaleFactors/' + main_dir = [{"etau": "Region_Spin2_190_190_PT_33_25_35_DR_{}_TURNON_200_190".format(args.deltaR), + "mutau": "mutau_190_190_DR_0.5_PT_25_21_32_TURNON_180", + "tautau": "Region_Spin2_190_190_PT_40_40_DR_{}_TURNON_200_190".format(args.deltaR)}, + ] + + main(args) diff --git a/tests/trigger_regions_run3.py b/tests/trigger_regions_run3.py new file mode 100644 index 0000000..0edfbe4 --- /dev/null +++ b/tests/trigger_regions_run3.py @@ -0,0 +1,967 @@ +# coding: utf-8 + +_all_ = [ 'test_trigger_regions' ] + +import os +import sys +parent_dir = os.path.abspath(__file__ + 2 * '/..') +sys.path.insert(0, parent_dir) +import argparse +import glob +import multiprocessing +import itertools as it +import csv +import numpy as np +import h5py +from collections import defaultdict as dd +import importlib + +import inclusion +from inclusion import selection +from inclusion.config import main +from inclusion.utils import utils + +import ROOT +import hist +import pickle +import uproot + +from bokeh.plotting import figure, output_file, save +from bokeh.models import Range1d, Label + +tau = '\u03C4' +mu = '\u03BC' +pm = '\u00B1' +ditau = tau+tau + +def contamination_save(savepath, label, c1, c2, e1, e2, mode='w'): + with h5py.File(os.path.join(savepath, label + '.hdf5'), mode) as f: + dset = f.create_dataset(label, (4, len(c1)), dtype='f') + dset[0, :] = c1 + dset[1, :] = c2 + dset[2, :] = e1 + dset[3, :] = e2 + dset.cols = ['contamination ' + ditau + '(%)', + 'contamination ' + ditau + ' MET (%)', + 'uncertainty' + ditau, + 'uncertainty' + ditau + ' MET'] + +def stats_save(savepath, label, c1, e1, mode='w'): + with h5py.File(os.path.join(savepath, label + '.hdf5'), mode) as f: + dset = f.create_dataset(label+'_stats', (2, len(c1)), dtype='f') + dset[0, :] = c1 + dset[1, :] = e1 + dset.cols = ['stats', + 'uncertainty'] + +def rec_dd(): + return dd(rec_dd) + +def square_diagram(c_legacy_trg, c_met_trg, c_tau_trg, channel, + ptcuts, text, notext=False, bigtau=False): + base = {'etau': 'e+e'+tau, 'mutau': mu+'+'+mu+tau, 'tautau': ditau} + output_file(text['out']) + print('Saving file {}'.format(text['out'])) + + topr = 9.9 + shft = 0.1 + start, b1, b2, b3 = 0.0, 2, 2.5, 7.5 + xgap = b1+shft# else b2+shft + + p = figure(title='m(X)=0GeV', width=600, height=400, + tools='save') + p.x_range = Range1d(0, 11.5) + p.y_range = Range1d(0, 10) + p.outline_line_color = None + p.toolbar.logo = None + p.xgrid.grid_line_color = None + p.ygrid.grid_line_color = None + p.xaxis.ticker = [b1,b3]#else [b1, b2, b3] + p.xaxis.major_label_overrides = ({b1: str(ptcuts[0]), b3: str(regcuts[0])}) + + for aaxis in (p.xaxis, p.yaxis): + aaxis.axis_label_text_font_size = "12pt" + aaxis.axis_label_text_font_style = "normal" + aaxis.major_label_text_font_size = "11pt" + + p.yaxis.axis_label_standoff = 0 + p.yaxis.ticker = [b1, b3]# else [b1, b2, b3] + if len(ptcuts) > 1: + p.yaxis.major_label_overrides = ({b1: str(ptcuts[0]), b3: str(regcuts[1])}) + else: + p.yaxis.major_label_overrides = ({b1: str(ptcuts[0])}) + + p.xaxis.axis_label = r'\(p_T(\tau_1) [GeV]\)' + p.yaxis.axis_label = r'\(p_T(\tau_2) [GeV]\)' + + # add a square renderer with a size, color, and alpha + polyg_opt = dict(alpha=0.3) + p.multi_polygons(color='green', + xs=[[[[xgap,b3-shft,b3-shft,xgap]]]] if bigtau else [[[[xgap,topr,topr,xgap]]]], + ys=[[[[b3-shft,b3-shft,xgap,xgap]]]] if bigtau else [[[[topr,topr,xgap,xgap]]]], + legend_label=base[channel], **polyg_opt) + + p.legend.title = 'Regions' + p.legend.title_text_font_style = 'bold' + p.legend.border_line_color = None + p.legend.background_fill_color = 'white' + p.legend.click_policy = 'hide' + + label_opt = dict(x_units='data', y_units='data', text_font_size='10pt') + + gain = (100*float(c_met_trg['legacy']+c_tau_trg['legacy']) / + (c_legacy_trg['legacy']+c_met_trg['legacy']+c_tau_trg['legacy'])) + gain = str(round(gain,2)) + if not notext: + stats_ditau = {'legacy': Label(x=b1+0.3, y=b1+1.5, text=ditau+': '+str(c_legacy_trg['legacy']), + text_color='black', **label_opt), + 'met': Label(x=b1+0.3, y=b1+1.1, text='met && !'+ditau+': '+str(c_met_trg['legacy']), + text_color='black', **label_opt), + 'tau': Label(x=b1+0.3, y=b1+0.7, text=tau+' && !met && !'+ditau+': '+str(c_tau_trg['legacy']), + text_color='black', **label_opt), + 'gain': Label(x=b1+0.3, y=b1+0.3, text='Gain: '+gain+'%', + text_color='blue', **label_opt),} + for key,elem in stats_ditau.items(): + p.add_layout(elem) + + + try: + if c_met_trg['met']+c_tau_trg['met']+c_legacy_trg['met'] == 0.0: + raise ZeroDivisionError + contam_by_tau = (100*(float(c_tau_trg['met'])+c_legacy_trg['met']) / + (c_met_trg['met']+c_tau_trg['met']+c_legacy_trg['met'])) + contam_by_tau = str(round(contam_by_tau,2)) + except ZeroDivisionError: + contam_by_tau = '0' + if not notext: + stats_met = {'met': Label(x=b1+0.3, y=1.3, text='met: '+str(c_met_trg['met']), + text_color='black', **label_opt), + 'tau': Label(x=b1+0.3, y=0.5, text=tau+' && !'+ditau+' && !met: '+str(c_tau_trg['met']), + text_color='black', **label_opt), + 'legacy': Label(x=b1+0.3, y=0.9, text=ditau+' && !met: '+str(c_legacy_trg['met']), + text_color='black', **label_opt), + 'contamination': Label(x=b1+0.3, y=0.1, text='Contam.: '+contam_by_tau+'%', + text_color='blue', **label_opt),} + for key,elem in stats_met.items(): + if key != 'contamination': + p.add_layout(elem) + + + num_ditau = float(c_legacy_trg['tau']) + num_both = num_ditau + float(c_met_trg['tau']) + den = float(c_met_trg['tau']+c_tau_trg['tau']+c_legacy_trg['tau']) + if num_ditau == 0 or num_both == 0: + contam_ditau = '0' + contam_both = '0' + err_ditau = '0' + err_both = '0' + else: + contam_ditau = 100*num_ditau/den + contam_both = 100*num_both/den + + enum_ditau = np.sqrt(c_legacy_trg['tau']) + eden_ditau = enum_ditau + np.sqrt(c_met_trg['tau']) + np.sqrt(c_tau_trg['tau']) + enum_both = np.sqrt(c_met_trg['tau']) + np.sqrt(c_legacy_trg['tau']) + eden_both = enum_both + np.sqrt(c_tau_trg['tau']) + err_ditau = contam_ditau * np.sqrt(enum_ditau**2/num_ditau**2 + eden_ditau**2/den**2) + err_both = contam_both * np.sqrt(enum_both**2/num_both**2 + eden_both**2/den**2) + + contam_ditau = str(round(contam_ditau,2)) + contam_both = str(round(contam_both,2)) + err_ditau = str(round(err_ditau,2)) + err_both = str(round(err_both,2)) + + if not notext: + stats_tau = {'tau': Label(x=b3+0.2, y=1.3, text=tau+': '+str(c_tau_trg['tau']), + text_color='black', **label_opt), + 'met': Label(x=b3+0.2, y=0.5, text='met && !'+ditau+' && !'+tau+': '+str(c_met_trg['tau']), + text_color='black', **label_opt), + 'legacy': Label(x=b3+0.2, y=0.9, text=ditau+' && !'+tau+': '+str(c_legacy_trg['tau']), + text_color='black', **label_opt), + 'contamination_both': Label(x=b3+0.2, y=0.1, + text='Contam.: ('+str(contam_both)+pm+str(err_both)+')%', + text_color='blue', **label_opt),} + for key,elem in stats_tau.items(): + if key != 'contamination_both': + p.add_layout(elem) + + line_opt = dict(color='black', line_dash='dashed', line_width=2) + p.line(x=[b1,b1], y=[start, topr+shft], **line_opt) + p.line(x=[start,topr+shft], y=[b1,b1], **line_opt) + p.line(x=[b3,b3], y=[start,topr+shft], **line_opt) + p.line(x=[start,topr+shft], y=[b3,b3], **line_opt) + + p.output_backend = 'svg' + save(p) + return contam_ditau, contam_both, err_ditau, err_both + +def get_outname(channel, bigtau): + utils.create_single_dir('data') + + name = "" + if bigtau: + name += '_BIGTAU' + name += '_all.root' + + s = 'data/regions_preEE_12p11-old-way-pf75{}'.format(name) + return s + +def set_plot_definitions(): + ROOT.gROOT.SetBatch(ROOT.kTRUE) + ROOT.gStyle.SetOptStat(ROOT.kFALSE) + ret = {'XTitleSize' : 0.045, + 'YTitleSize' : 0.045, + 'LineWidth' : 2, + 'FrameLineWidth' : 1, + } + return ret + +def match_trigger_object(off_eta, off_phi, obj_id, TrigObj_id, TrigObj_filterBits, TrigObj_eta, TrigObj_phi, bits): + for iobj in range(len(TrigObj_id)): + if TrigObj_id[iobj] != obj_id: + continue + dPhi = off_phi - TrigObj_phi[iobj] + dEta = off_eta - TrigObj_eta[iobj] + delR2 = dPhi * dPhi + dEta * dEta + if delR2 > 0.25: #0.5 * 0.5 + continue + # matched_bits = True + # for bit in bits: + # if (TrigObj_filterBits[iobj] & (1<= regcuts[0] and eta1_trg) or (ent.dau2_pt >= regcuts[1] and eta2_trg) + # leg = ent.dau1_pt >= ptcuts[0] and ent.dau2_pt >= ptcuts[1] and eta1_trg and eta2_trg and not tau + + # elif channel == "etau" and year == "2016": + # tau = ent.dau2_pt >= regcuts[1] and eta2_trg + # leg = ent.dau1_pt >= ptcuts[0] and eta1_trg and not tau + + # else: #mutau or etau non-2016 + # single_lepton_validity = ent.dau1_pt >= ptcuts[0] and eta1_sel[channel] + # cross_lepton_validity = ent.dau1_pt >= ptcuts[1] and eta1_trg and ent.dau2_pt >= ptcuts[2] and eta2_trg + + # tau = ent.dau2_pt >= regcuts[1] and eta2_trg + # leg = (single_lepton_validity or cross_lepton_validity) and not tau + + if channel == "tautau": + leg = True + tau = False + # leg = ent.dau1_pt >= ptcuts[0] and ent.dau2_pt >= ptcuts[1] and eta1_trg and eta2_trg + # tau = ((ent.dau1_pt >= regcuts[0] and eta1_trg) or (ent.dau2_pt >= regcuts[1] and eta2_trg)) and not leg + + elif channel == "etau" and year == "2016": + leg = ent.dau1_pt >= ptcuts[0] and eta1_trg + tau = ent.dau2_pt >= regcuts[1] and eta2_trg and not leg + + else: #mutau or etau non-2016 + leg = True + tau = False + # leg = sel.check_bit(main.trig_map[year]['IsoMu24']['mc']) or sel.check_bit(main.trig_map[year]['IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1']['mc']) + # tau = not leg and sel.check_bit(main.trig_map[year]['LooseDeepTauPFTauHPS180_L2NN_eta2p1']['mc']) + # single_lepton_validity = ent.dau1_pt >= ptcuts[0] and eta1_sel[channel] + # cross_lepton_validity = ent.dau1_pt >= ptcuts[1] and eta1_trg and ent.dau2_pt >= ptcuts[2] and eta2_trg + # single_tau_validity = ent.dau2_pt >= regcuts[1] and eta2_trg + + # leg = single_lepton_validity or cross_lepton_validity + # tau = single_tau_validity and not leg + + met = not leg and not tau and sel.check_bit(main.trig_map[year]['PFMETNoMu120_PFMHTNoMu120_IDTight']['mc']) + + # only one True: non-overlapping regions + assert int(leg)+int(met)+int(tau)<=1 + + return leg, met, tau + +def pass_offline_selection(triggers, entry): + res = False + for trigger in triggers: + trigger_pass = entry[trigger] + if not trigger_pass: + continue + if trigger == "HLT_IsoMu24" and entry['pairType'] == 0: + res = res or (entry['dau1_pt'] > 25 and abs(entry['dau1_eta']) < 2.4) + elif trigger == "HLT_IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1" and entry['pairType'] == 0: + res = res or (entry['dau1_pt'] > 21 and entry['dau2_pt'] > 32) + elif trigger == "HLT_Ele30_WPTight_Gsf" and entry['pairType'] == 1: + res = res or (entry['dau1_pt'] > 32 and abs(entry['dau1_eta']) < 2.4) + elif trigger == "HLT_Ele24_eta2p1_WPTight_Gsf_LooseDeepTauPFTauHPS30_eta2p1_CrossL1" and entry['pairType'] == 1: + res = res or (entry['dau1_pt'] > 26 and entry['dau2_pt'] > 35) + elif trigger == "HLT_DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1" and entry['pairType'] == 2: + res = res or (entry['dau1_pt'] > 40 and entry['dau2_pt'] > 40) + elif trigger == "HLT_DoubleTightChargedIsoPFTauHPS35_Trk1_eta2p1" and entry['pairType'] == 2: + res = res or (entry['dau1_pt'] > 40 and entry['dau2_pt'] > 40) + elif trigger == "HLT_LooseDeepTauPFTauHPS180_L2NN_eta2p1": + if entry['pairType'] == 2: + res = res or (entry['dau1_pt'] > 190 or entry['dau2_pt'] > 190) + else: + res = res or entry['dau2_pt'] > 190 + elif trigger == "HLT_PFMETNoMu120_PFMHTNoMu120_IDTight": + res = res or entry["MET_pt"] > 150 + elif trigger == "HLT_Mu50": + res = res or (entry['dau1_pt'] > 51 and abs(entry['dau1_eta']) < 2.4) + elif trigger == "HLT_DoubleMediumDeepTauPFTauHPS30_L2NN_eta2p1_PFJet60" and entry['pairType'] == 2: + res = res or (entry['dau1_pt'] > 35 and entry['dau2_pt'] > 35) + elif trigger == "HLT_DoubleMediumDeepTauPFTauHPS30_L2NN_eta2p1_PFJet75" and entry['pairType'] == 2: + res = res or (entry['dau1_pt'] > 35 and entry['dau2_pt'] > 35 and (entry['bjet1_pt'] > 75 or entry['bjet2_pt'] > 75)) + elif trigger == "HLT_QuadPFJet70_50_40_30_PFBTagParticleNet_2BTagSum0p65": + if entry['pairType'] == 2: + res = res or (abs(entry['dau1_eta']) < 2.1 and abs(entry['dau2_eta']) < 2.1) + else: + res = res or (abs(entry['dau1_eta']) < 2.4 and abs(entry['dau2_eta']) < 2.1) + return res + + +def pass_triggers(triggers, entry): + res = False + for trigger in triggers: + res = res or entry[trigger] + return res + + +def trigger_regions(indir, channel, year, deltaR): + outname = get_outname(channel, + args.bigtau) + config_module = importlib.import_module(args.configuration) + + + if channel == 'etau' or channel == 'mutau': + iso1 = range(0, 8.1, 8/24) + elif channel == 'tautau': + iso1 = range(0, 401, 8) + pNet_dist = [x/12. for x in range(13)] + binning.update({ + 'genHH_mass': ([250, 300, 350, 400, 450, 500, 550, 600, 675, 800, 1000, 1600],), + 'dau1_iso': (iso1,), + 'dau1_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'dau2_iso': (iso1,), + 'dau2_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'dau1_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + 'dau2_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + 'dau1_tauIdVSjet': ([0, 1, 2, 3, 4, 5, 6, 7], ), + 'dau2_tauIdVSjet': ([0, 1, 2, 3, 4, 5, 6, 7], ), + 'bjet1_pNet': (pNet_dist,), + 'bjet2_pNet': (pNet_dist,), + 'bjet1_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'bjet2_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), + 'bjet1_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + 'bjet2_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), + }) + + # full_sample = {0: 'GluGluToRadionToHHTo2B2Tau_M-' + sample + '_', + # 2: 'GluGluToBulkGravitonToHHTo2B2Tau_M-' + sample + '_'}[spin] + + norphans, ntotal = ({k:0 for k in categories} for _ in range(2)) + + ahistos = rec_dd() + for cat in categories: + for chn in ['mutau', 'etau', 'tautau']: + for htype in htypes: + for i in binning.keys(): + ahistos[htype][cat][chn][i] = ( + hist.Hist.new.Variable(*binning[i], name=i) + .Weight() + ) + + t_in = ROOT.TChain('Events') + glob_files = glob.glob( os.path.join(indir, 'data_*.root') ) + if len(glob_files) < 1: + raise RuntimeError("No files!") + for f in glob_files: + t_in.Add(f) + t_in.SetBranchStatus('*', 0) + _entries, mc_corrections = utils.define_used_tree_variables(cut=config_module.custom_cut) + + _entries += tuple([ x + mc_corrections[x] for x in mc_corrections]) + for ientry in _entries: + t_in.SetBranchStatus(ientry, 1) + + for entry in t_in: + # this is slow: do it once only + entries = utils.dot_dict({x: getattr(entry, x) for x in _entries}) + + if entries.pairType == -1: + continue + + if entries.pairType == 0 or entries.pairType == 1: + if entries.isOS != 1 or entries.dau2_tauIdVSjet < 5: + continue + if entries.pairType == 2: + if entries.isOS != 1 or entries.dau2_tauIdVSjet < 5 or entries.dau1_tauIdVSjet < 5: + continue + + sel = selection.EventSelection(entries, year=year, isdata=False, configuration=config_module) + # in_legacy, in_met, in_tau = which_region(entries, year, ptcuts, regcuts, channel, sel, + # bigtau=args.bigtau) + + pass_base_mu = pass_triggers(triggers['mutau'], entries) + pass_base_e = pass_triggers(triggers['etau'], entries) + pass_base_tau = pass_offline_selection(triggers['tautau'], entries) + # pass_alt_base_tau = pass_offline_selection(('HLT_DoubleTightChargedIsoPFTauHPS35_Trk1_eta2p1',), entries) + pass_met = pass_offline_selection(('HLT_PFMETNoMu120_PFMHTNoMu120_IDTight',), entries) + pass_tau = pass_offline_selection(('HLT_LooseDeepTauPFTauHPS180_L2NN_eta2p1',), entries) + # pass_mu50 = pass_offline_selection(('HLT_Mu50',), entries) + # pass_ele28 = pass_offline_selection(('HLT_Ele28_eta2p1_WPTight_Gsf_HT150', ), entries) + pass_ttjet = pass_offline_selection(("HLT_DoubleMediumDeepTauPFTauHPS30_L2NN_eta2p1_PFJet60",), entries) + # pass_4jets_deepJet = pass_triggers(("HLT_QuadPFJet103_88_75_15_DoublePFBTagDeepJet_1p3_7p7_VBF1", ), entries) + pass_4jets_pNet = pass_triggers(("HLT_QuadPFJet70_50_40_35_PFBTagParticleNet_2BTagSum0p65", ), entries) + # pass_4jets_match = match_trigger_objects_QuadJet(entries) if pass_4jets_pNet else False + cuts = { + 'All': True, + 'BaseMu' : pass_base_mu, + 'BaseE' : pass_base_e, + 'BaseTau' : pass_base_tau and entries.dau1_pt > 40 and entries.dau2_pt > 40, + # 'AltBaseTau' : pass_alt_base_tau, + # 'BaseTauNoAltBaseTau': pass_base_tau and not pass_alt_base_tau, + # 'NoBaseTauAltBaseTau': not pass_base_tau and pass_alt_base_tau, + # 'BaseTauAltBaseTau': pass_base_tau and pass_alt_base_tau, + # 'MET' : pass_met, + # 'Tau' : pass_tau, + # 'VBF' : False, + # 'BaseMuMET' : pass_base_mu and pass_met, + # 'BaseEMET' : pass_base_e and pass_met, + # 'BaseTauMET' : pass_base_tau and pass_met, + # 'BaseMuORMET' : pass_base_mu or pass_met, + # 'BaseEORMET' : pass_base_e or pass_met, + # 'BaseTauORMET' : pass_base_tau or pass_met, + # 'BaseMuTau' : pass_base_mu and pass_tau, + # 'BaseETau' : pass_base_e and pass_tau, + # 'BaseTauTau' : pass_base_tau and pass_tau, + # 'BaseMuORTau' : pass_base_mu or pass_tau, + # 'BaseEORTau' : pass_base_e or pass_tau, + # 'BaseTauORTau' : pass_base_tau or pass_tau, + # 'BaseTauORTTJet' : pass_base_tau or pass_ttjet, + # 'BaseTauORTauORTTJet': pass_base_tau or pass_tau or pass_ttjet, + # 'METTau' : pass_met and pass_tau, + # 'METORTau' : pass_met or pass_tau, + # 'BaseMuORMETORTau' : pass_base_mu or pass_met or pass_tau, + # 'BaseEORMETORTau' : pass_base_e or pass_met or pass_tau, + # 'BaseTauORMETORTau' : pass_base_tau or pass_met or pass_tau, + # 'BaseTauORMETORTauORTTJet': pass_base_tau or pass_tau or pass_met or pass_ttjet, + # 'NoBaseMuMET' : not pass_base_mu and pass_met, + # 'BaseMuNoMET' : pass_base_mu and not pass_met, + # 'NoBaseEMET' : not pass_base_e and pass_met, + # 'BaseENoMET' : pass_base_e and not pass_met, + # 'NoBaseTauMET' : not pass_base_tau and pass_met, + # 'BaseTauNoMET' : pass_base_tau and not pass_met, + # 'NoBaseMuNoMETTau' : not pass_base_mu and not pass_met and pass_tau, + # 'NoBaseENoMETTau' : not pass_base_e and not pass_met and pass_tau, + # 'NoBaseTauNoMETTau' : not pass_base_tau and not pass_met and pass_tau, + # 'NoBaseTauTau' : not pass_base_tau and pass_tau, + # 'NoBaseMuMu50' : not pass_base_mu and pass_mu50, + # 'NoBaseMuTau' : not pass_base_mu and pass_tau, + # 'NoBaseETau' : not pass_base_e and pass_tau, + # 'NoBaseMuNoMETMu50': not pass_base_mu and not pass_met and pass_mu50, + # 'NoBaseMuNoTauMu50': not pass_base_mu and not pass_tau and pass_mu50, + # 'NoBaseMuMETNoMu50': not pass_base_mu and pass_met and not pass_mu50, + # 'NoBaseMuMETMu50': not pass_base_mu and pass_met and pass_mu50, + # 'BaseTauNoTau' : pass_base_tau and not pass_tau, + # 'NoBaseTauTTJet' : pass_ttjet and not pass_base_tau, + # 'NoBaseMETNoTau' : not pass_trg and pass_met and not pass_tau, + # 'NoBaseMETORTau': not pass_trg and (pass_met or pass_tau), + # 'NoBaseMETTau': not pass_trg and pass_met and pass_tau, + # 'NoBaseMETORTauORMu50': not pass_trg and (pass_met or pass_tau or pass_mu50), + # 'NoBaseMETORTauOREle28': not pass_trg and (pass_met or pass_tau or pass_ele28), + # 'NoBaseMETNoTTJet': not pass_trg and pass_met and not pass_ttjet, + # 'NoBaseNoMETTTJet': not pass_trg and not pass_met and pass_ttjet, + # 'NoBaseMETTTJet': not pass_trg and pass_met and pass_ttjet, + # 'NoBaseTauTTJet': not pass_trg and pass_tau and pass_ttjet, + # 'NoBaseTauNoTTJet': not pass_trg and pass_tau and not pass_ttjet, + # 'NoBaseNoTauTTJet': not pass_trg and not pass_tau and pass_ttjet, + # 'NoBaseTauMu50': not pass_trg and pass_tau and pass_mu50, + # 'NoBaseTauNoMu50': not pass_trg and pass_tau and not pass_mu50, + # 'NoBaseNoTauMu50': not pass_trg and not pass_tau and pass_mu50, + # 'BaseMETTau' : pass_trg and pass_met and pass_tau, + # 'BaseNoMETNoTau' : pass_trg and not pass_met and not pass_tau, + # 'VBFKin' : False, + # 'NoBaseMETORTauORTTJet' : not pass_trg and (pass_met or pass_tau or pass_ttjet) + # 'NoBaseMu4JetsPNet': not pass_base_mu and pass_4jets_pNet, + # 'NoBaseE4JetsPNet': not pass_base_e and pass_4jets_pNet, + # 'BaseMuOR4JetsPNet': pass_base_mu or pass_4jets_pNet, + # 'BaseEOR4JetsPNet': pass_base_e or pass_4jets_pNet, + # 'BaseTauOR4JetsPNet': pass_base_tau or pass_4jets_pNet, + # 'NoBaseTau4JetsDeepJet': not pass_base_tau and pass_4jets_deepJet, + # 'NoBaseTau4JetsPNet': not pass_base_tau and pass_4jets_pNet, + # 'NoBaseTau4JetsDeepJetNo4JetsPNet': not pass_base_tau and pass_4jets_deepJet and not pass_4jets_pNet, + # 'NoBaseTauNo4JetsDeepJet4JetsPNet': not pass_base_tau and not pass_4jets_deepJet and pass_4jets_pNet, + # 'NoBaseTau4JetsDeepJet4JetsPNet': not pass_base_tau and pass_4jets_deepJet and pass_4jets_pNet, + # 'NoBaseTauNoTTJet4JetsPNet': not pass_base_tau and not pass_ttjet and pass_4jets_pNet, + # 'NoBaseTauTTJetNo4JetsPNet': not pass_base_tau and pass_ttjet and not pass_4jets_pNet, + # 'NoBaseTauTTJet4JetsPNet': not pass_base_tau and pass_ttjet and pass_4jets_pNet, + # 'NoBaseTauORMETORTauORTTJetOR4JetsPNet': not pass_base_tau and (pass_met or pass_tau or pass_ttjet or pass_4jets_pNet), + # 'NoBaseMuNoMET4JetsPNet': not pass_base_mu and not pass_met and pass_4jets_pNet, + # 'NoBaseMuMETNo4JetsPNet': not pass_base_mu and pass_met and not pass_4jets_pNet, + # 'NoBaseMuMET4JetsPNet': not pass_base_mu and pass_met and pass_4jets_pNet, + # 'NoBaseENoMET4JetsPNet': not pass_base_e and not pass_met and pass_4jets_pNet, + # 'NoBaseEMETNo4JetsPNet': not pass_base_e and pass_met and not pass_4jets_pNet, + # 'NoBaseEMET4JetsPNet': not pass_base_e and pass_met and pass_4jets_pNet, + # 'NoBaseTauNoMET4JetsPNet': not pass_base_tau and not pass_met and pass_4jets_pNet, + # 'NoBaseTauMETNo4JetsPNet': not pass_base_tau and pass_met and not pass_4jets_pNet, + # 'NoBaseTauMET4JetsPNet': not pass_base_tau and pass_met and pass_4jets_pNet, + # 'NoBaseMuTauNo4JetsPNet': not pass_base_mu and pass_tau and not pass_4jets_pNet, + # 'NoBaseMuTau4JetsPNet': not pass_base_mu and pass_tau and pass_4jets_pNet, + # 'NoBaseENoTau4JetsPNet': not pass_base_e and not pass_tau and pass_4jets_pNet, + # 'NoBaseETauNo4JetsPNet': not pass_base_e and pass_tau and not pass_4jets_pNet, + # 'NoBaseETau4JetsPNet': not pass_base_e and pass_tau and pass_4jets_pNet, + 'NoBaseTauNoTau4JetsPNet': not pass_base_tau and not pass_tau and pass_4jets_pNet, + 'NoBaseTauTauNo4JetsPNet': not pass_base_tau and pass_tau and not pass_4jets_pNet, + 'NoBaseTauTau4JetsPNet': not pass_base_tau and pass_tau and pass_4jets_pNet, + # 'BaseEORMETORTauOR4JetsPNet': pass_base_e or pass_met or pass_tau or pass_4jets_pNet, + # 'BaseMuORMETORTauORMu50OR4JetsPNet': pass_base_mu or pass_met or pass_tau or pass_mu50 or pass_4jets_pNet, + # 'BaseTauORMETORTauORTTJetOR4JetsPNet': pass_base_tau or pass_met or pass_tau or pass_ttjet or pass_4jets_pNet, + 'BaseTauORTauTauJet': pass_base_tau or pass_ttjet, + 'BaseTauOR4JetsPNet': pass_base_tau or pass_4jets_pNet, + # 'BaseTauOR4JetsPNetMatch': pass_base_tau or pass_4jets_match, + 'BaseTauORTauTauJetOR4JetsPNet': pass_base_tau or pass_ttjet or pass_4jets_pNet, + 'BaseTauORTauTauJetNo4JetsPNet': (pass_base_tau or pass_ttjet) and not pass_4jets_pNet, + 'BaseTauORTauTauJet4JetsPNet' : (pass_base_tau or pass_ttjet) and pass_4jets_pNet, + 'NoBaseTauTauTauJetNo4JetsPNet': not pass_base_tau and pass_ttjet and not pass_4jets_pNet, + 'NoBaseTauNoTauTauJet4JetsPNet': not pass_base_tau and not pass_ttjet and pass_4jets_pNet, + 'NoBaseTauORNoTauTauJet4JetsPNet': not (pass_base_tau or pass_ttjet) and pass_4jets_pNet, + # 'BaseTauORMETORTauTauJetOR4JetsPNet': pass_base_tau or pass_met or pass_ttjet or pass_4jets_pNet, + # 'BaseTauORTauORTauTauJetOR4JetsPNet': pass_base_tau or pass_tau or pass_ttjet or pass_4jets_pNet + } + # assert htypes == list(cuts.keys()) + + w_mc = entries.genWeight + # w_pure = entries.puWweight + # # w_l1pref = entries.L1pref_weight + # w_trig = 1 # entries.trigSF + # w_idiso = entries.IdSF_deep_2d + # w_jetpu = entries.PUjetID_SF + # w_btag = entries.bTagweightReshape + + if utils.is_nan(w_mc) : w_mc=1 + # if utils.is_nan(w_pure) : w_pure=1 + # # if utils.is_nan(w_l1pref) : w_l1pref=1 + # if utils.is_nan(w_trig) : w_trig=1 + # if utils.is_nan(w_idiso) : w_idiso=1 + # if utils.is_nan(w_jetpu) : w_jetpu=1 + # if utils.is_nan(w_btag) : w_btag=1 + evt_weight = 1 # * w_btag + # if evt_weight < 0.: + # print(w_mc, w_pure, w_trig, w_idiso, w_jetpu) + tau_gen_cut = {"etau": None, "mutau": None, + "tautau": 'self.entries["isTau1real"] == 1 and self.entries["isTau2real"] == 1'} + # if not pass_trg and entries.pairType == 0: + # print(channel, entries.pairType, utils.is_channel_consistent(channel, entries.pairType)) + # if utils.is_channel_consistent(channel, entries.pairType): + # if not sel.selection_cuts(lepton_veto=True, bjets_cut=True, + # mass_cut=config_module.mass_cut, + # custom_cut=tau_gen_cut[channel]): + # print("Not pass selection cut") + # continue + + for cat in categories: + # if sel.sel_category(cat): # and entries.ditau_deltaR > deltaR: + ntotal[cat] += 1 + + if entries.pairType == 0: + chn = 'mutau' + elif entries.pairType == 1: + chn = 'etau' + elif entries.pairType == 2: + chn = 'tautau' + + # if in_met: + # reg = 'met' + # elif in_tau: + # reg = 'tau' + # elif in_legacy: + # reg = 'legacy' + # else: + # norphans[cat] += 1 + # continue + # assert reg in regions + + for key,cut in cuts.items(): + if cut: + # print(key, cat, chn) + for i in binning.keys(): + z = 0 + if 'bjet1' in i: + if 'eta' in i: + z = entries['Jet_eta'][entries['bjet1_JetIdx']] + if 'pt' in i: + z = entries['Jet_pt'][entries['bjet1_JetIdx']] + if 'pNet' in i: + z = entries['Jet_btagPNetB'][entries['bjet1_JetIdx']] + elif 'bjet2' in i: + if 'eta' in i: + z = entries['Jet_eta'][entries['bjet2_JetIdx']] + if 'pt' in i: + z = entries['Jet_pt'][entries['bjet2_JetIdx']] + if 'pNet' in i: + z = entries['Jet_btagPNetB'][entries['bjet2_JetIdx']] + else: + z = entries[i] + ahistos[key][cat][chn][i].fill(z, weight=evt_weight) + + # all MC and signal must be rescaled to get the correct number of events + # for key,_ in cuts.items(): + # for reg in regions: + # for cat in categories: + # for cc in ['mutau', 'etau', 'tautau']: + # print("lumi", utils.get_lumi(args.year), utils.total_sum_weights(glob_files[0].replace("PreprocessRDF", "PreCounter").replace("/cat_base_selection", "").replace(".root", ".json"), isdata=False)) + # ahistos[key][reg][cat][cc] *= (utils.get_lumi(args.year) / + # utils.total_sum_weights(glob_files[0].replace("PreprocessRDF", "PreCounter").replace("/cat_base_selection", "").replace(".root", ".json"), isdata=False)) + + # with open(outname, "wb") as f: + # pickle.dump(ahistos, f) + file = uproot.recreate(outname) + for key,_ in cuts.items(): + for cat in categories: + for chn in ['mutau', 'etau', 'tautau']: + for i in binning.keys(): + file[str(key) + '_' + str(cat) + '_' + str(chn) + "_" + str(i)] = ahistos[key][cat][chn][i] + + for cat in categories: + orph_frac = float(norphans[cat])/ntotal[cat] + if orph_frac > 0.1: + print('{}% orphans ({}/{}). This is unusual.'.format(orph_frac, norphans[cat], ntotal[cat])) + print(met_region) + print(tau_region) + print(legacy_region) + print('Category {} (m(X)=0GeV) had {} orphans ({}%)'.format(cat, norphans[cat], orph_frac)) + print('Raw histograms saved in {}.'.format(outname), flush=True) + +if __name__ == '__main__': + extensions = ('png',) #('png', 'pdf') + triggers = {'etau': ("HLT_Ele30_WPTight_Gsf", + "HLT_Ele24_eta2p1_WPTight_Gsf_LooseDeepTauPFTauHPS30_eta2p1_CrossL1"), + 'mutau': ('HLT_IsoMu24', 'HLT_IsoMu20_eta2p1_LooseDeepTauPFTauHPS27_eta2p1_CrossL1'), + 'tautau': ('HLT_DoubleMediumDeepTauPFTauHPS35_L2NN_eta2p1',) + } + binning = { + # 'metnomu_et': (20, 0, 450), + # 'dau1_pt': (30, 0, 450), + # 'dau1_eta': (20, -2.5, 2.5), + # 'dau2_iso': (20, 0.88, 1.005), + # 'dau2_pt': (30, 0, 400), + # 'dau2_eta': (20, -2.5, 2.5), + # 'ditau_deltaR': (30, 0.3, 1.3), + # 'dib_deltaR': (25, 0, 2.5), + # 'bH_pt': (20, 70, 600), + # 'bH_mass': (30, 0, 280), + # 'tauH_mass': (30, 0, 170), + # 'tauH_pt': (30, 0, 500), + # 'tauH_SVFIT_mass': (30, 0, 250), + # 'tauH_SVFIT_pt': (20, 200, 650), + # 'bjet1_pt': (25, 10, 600), + # 'bjet2_pt': (25, 10, 550), + # 'bjet1_eta': (20, -2.5, 2.5), + # 'bjet2_eta': (20, -2.5, 2.5), + } + variables = tuple(binning.keys()) + ('HHKin_mass', 'dau1_iso') + + categories = ('baseline',) #('baseline', 's1b1jresolvedMcut', 's2b0jresolvedMcut', 'sboostedLLMcut') + + htypes = [ + 'All', 'BaseMu', 'BaseE', 'BaseTau', 'MET', 'Tau', 'VBF', 'BaseMuMET', 'BaseEMET', + 'BaseTauMET', 'BaseMuORMET', 'BaseEORMET', 'BaseTauORMET', 'BaseMuTau', 'BaseETau', 'BaseTauTau', 'BaseMuORTau', + 'BaseEORTau', 'BaseTauORTau', 'BaseTauORTTJet', 'BaseTauORTauORTTJet', + 'METTau', 'METORTau', 'BaseMuORMETORTau', 'BaseEORMETORTau' , 'BaseTauORMETORTau', 'BaseTauORMETORTauORTTJet', 'NoBaseMuMET', + 'BaseMuNoMET', 'NoBaseEMET', 'BaseENoMET', 'NoBaseTauMET', 'BaseTauNoMET', 'NoBaseMuNoMETTau', 'NoBaseENoMETTau', 'NoBaseTauNoMETTau', 'NoBaseTauTau', 'NoBaseMuTau', 'NoBaseETau', + # 'NoBaseMuNoMETMu50', 'NoBaseMuNoTauMu50', 'NoBaseMuMETNoMu50', 'NoBaseMuMETMu50', + 'BaseTauNoTau', 'NoBaseTauTTJet', + 'NoBaseMu4JetsPNet', 'NoBaseE4JetsPNet', 'BaseMuOR4JetsPNet', 'BaseEOR4JetsPNet', 'BaseTauOR4JetsPNet', 'NoBaseTau4JetsDeepJet', 'NoBaseTau4JetsPNet', 'NoBaseTau4JetsDeepJetNo4JetsPNet', + 'NoBaseTauNo4JetsDeepJet4JetsPNet', 'NoBaseTau4JetsDeepJet4JetsPNet', + 'NoBaseTauNoTTJet4JetsPNet', 'NoBaseTauTTJetNo4JetsPNet', 'NoBaseTauTTJet4JetsPNet', 'NoBaseTauORMETORTauORTTJetOR4JetsPNet', + 'NoBaseMuNoMET4JetsPNet', 'NoBaseMuMETNo4JetsPNet', 'NoBaseMuMET4JetsPNet', 'NoBaseENoMET4JetsPNet', 'NoBaseEMETNo4JetsPNet', 'NoBaseEMET4JetsPNet', 'NoBaseTauNoMET4JetsPNet' , 'NoBaseTauMETNo4JetsPNet' , + 'NoBaseTauMET4JetsPNet', 'NoBaseMuTauNo4JetsPNet', 'NoBaseMuTau4JetsPNet', 'NoBaseENoTau4JetsPNet', 'NoBaseETauNo4JetsPNet', 'NoBaseETau4JetsPNet', 'NoBaseTauNoTau4JetsPNet', 'NoBaseTauTauNo4JetsPNet', 'NoBaseTauTau4JetsPNet', + 'BaseEORMETORTauOR4JetsPNet', 'BaseTauORMETORTauORTTJetOR4JetsPNet', + 'BaseTauORTauTauJet', 'BaseTauOR4JetsPNet', 'BaseTauORTauTauJetOR4JetsPNet', 'BaseTauORTauTauJetNo4JetsPNet', 'BaseTauORTauTauJet4JetsPNet', 'NoBaseTauTauTauJetNo4JetsPNet', + 'NoBaseTauNoTauTauJet4JetsPNet', 'BaseTauORMETORTauTauJetOR4JetsPNet', 'BaseTauORTauORTauTauJetOR4JetsPNet', 'NoBaseTauORNoTauTauJet4JetsPNet'] + + # Parse input arguments + desc = 'Producer trigger histograms.\n' + desc += "Run example: python tests/test_trigger_regions.py --indir /data_CMS/cms/alves/HHresonant_SKIMS/SKIMS_UL18_EOSv4_Signal/ --masses 400 500 600 700 800 900 1000 1250 1500 --channels ETau --met_turnon 180 --region_cuts 40 40 --copy" + parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument('--indir', required=True, type=str, + help='Full path of ROOT input file') + # parser.add_argument('--masses', required=True, nargs='+', type=str, + # help='Resonance mass') + parser.add_argument('--channel', required=True, type=str, + help='Select the channel over which the workflow will be run.' ) + parser.add_argument('--year', required=True, type=str, choices=('2016', '2016APV', '2017', '2018', '2022'), + help='Select the year over which the workflow will be run.' ) + # parser.add_argument('--spin', required=True, type=int, choices=(0, 2), + # help='Select the spin hypothesis over which the workflow will be run.' ) + parser.add_argument('--deltaR', type=float, default=0.5, help='DeltaR between the two leptons.' ) + parser.add_argument('--plot', action='store_true', + help='Reuse previously produced data for quick plot changes.') + parser.add_argument('--copy', action='store_true', + help='Copy the outputs to EOS at the end.') + parser.add_argument('--notext', action='store_true', help='Square diagram without text.') + parser.add_argument('--sequential', action='store_true', + help='Do not use the multiprocess package.') + parser.add_argument('--bigtau', action='store_true', + help='Consider a larger single tau region, reducing the ditau one.') + parser.add_argument('--met_turnon', type=float, default=180, + help='MET trigger turnon cut [GeV].' ) + parser.add_argument('--region_cuts', required=False, type=float, nargs=2, default=(190, 190), + help='High/low regions pT1 and pT2 selection cuts [GeV].' ) + parser.add_argument('--configuration', dest='configuration', required=True, + help='Name of the configuration module to use.') + args = utils.parse_args(parser) + + met_turnon = args.met_turnon + regcuts = args.region_cuts + ptcuts = utils.get_ptcuts(args.channel, args.year) + + main_dir = os.path.join(os.path.join('/t3home/', os.environ['USER'], 'TriggerScaleFactors'), + '_'.join((args.channel, *[str(x) for x in regcuts], + 'DR', str(args.deltaR), 'PT', *[str(x) for x in ptcuts], 'TURNON', + str(met_turnon)))) + if args.bigtau: + main_dir += '_BIGTAU' + + regions = ('legacy', 'met', 'tau') + + #### run main function ### + if not args.plot: + # for sample in args.masses: + trigger_regions(args.indir, args.channel, args.year, args.deltaR) + # if args.sequential: + # pass + # else: + # pool = multiprocessing.Pool(processes=6) + # pool.starmap(trigger_regions, + # zip(it.repeat(args.indir), it.repeat(args.channel), it.repeat(args.year), it.repeat(args.deltaR))) + + ########################### + + sum_stats, err_sum_stats = ([] for _ in range(2)) + contam1, contam2, contam1_errors, contam2_errors = ([] for _ in range(4)) + from_directory = os.path.join(main_dir, args.channel) + outname = get_outname(args.channel, args.bigtau) + with open(outname, "rb") as f: + ahistos = pickle.load(f) + + # write csv header, one per category + out_counts = [] + for cat in categories: + out_counts.append( os.path.join(from_directory, cat, 'counts') ) + utils.create_single_dir(out_counts[-1]) + with open(os.path.join(out_counts[-1], 'table.csv'), 'w') as f: + reader = csv.writer(f, delimiter=',', quotechar='|') + header_row = ['Region'] + header_row.extend(htypes) + reader.writerow(header_row) + + # plot histograms and fill CSV with histogram integrals + c_legacy_trg, c_met_trg, c_tau_trg = ({} for _ in range(3)) + + acounts = rec_dd() + for reg in regions: + for key,cut in ahistos.items(): + acounts[key][reg] = round(ahistos[key][reg]["baseline"].values().sum(), 2) + + # append to table, one line per region + with open(os.path.join(out_counts[categories.index(cat)], 'table.csv'), 'a') as f: + reader = csv.writer(f, delimiter=',', quotechar='|') + row = [reg] + row.extend([acounts[k][reg] for k in acounts.keys()]) + reader.writerow(row) + + if reg=='legacy': + c_legacy_trg[reg] = acounts["Base"][reg] + c_met_trg[reg] = acounts["NoBaseMET"][reg] + c_tau_trg[reg] = acounts["NoBaseNoMETTau"][reg] + elif reg=='met': + c_legacy_trg[reg] = acounts["BaseNoMET"][reg] + c_met_trg[reg] = acounts["MET"][reg] + c_tau_trg[reg] = acounts["NoBaseNoMETTau"][reg] + elif reg=='tau': + c_legacy_trg[reg] = acounts["BaseNoTau"][reg] + c_met_trg[reg] = acounts["NoBaseMETNoTau"][reg] + c_tau_trg[reg] = acounts["Tau"][reg] + + text = {'out': os.path.join(out_counts[categories.index(cat)], 'diagram.html')} + sq_res = square_diagram(c_legacy_trg, c_met_trg, c_tau_trg, args.channel, + [str(x) for x in ptcuts], text=text, notext=args.notext, + bigtau=args.bigtau) + c1, c2, e1, e2 = sq_res + contam1.append(c1) + contam2.append(c2) + contam1_errors.append(e1) + contam2_errors.append(e2) + + stats_l = [c_legacy_trg['legacy'],c_met_trg['legacy'],c_tau_trg['tau'],c_legacy_trg['tau']] + stats = sum(stats_l) + estats = sum(np.sqrt(stats_l)) + sum_stats.append(stats) + err_sum_stats.append(estats) + + contam1 = [float(x) for x in contam1] + contam2 = [float(x) for x in contam2] + contam1_errors = [float(x) for x in contam1_errors] + contam2_errors = [float(x) for x in contam2_errors] + # masses = [float(x) for x in args.masses] + contamination_save('data', '_'.join([str(x) for x in regcuts]) + '_' + args.channel, + contam1, contam2, contam1_errors, contam2_errors) + stats_save('data', '_'.join([str(x) for x in regcuts]) + '_' + args.channel, + sum_stats, err_sum_stats, mode='a') + + if args.copy: + import subprocess + to_directory = os.path.join('/eos/home-b/bfontana/www/TriggerScaleFactors', main_dir) + to_directory = os.path.join(to_directory, args.channel) + + for sample in args.masses: + sample_from = os.path.join(from_directory, sample) + print('Copying: {}\t\t--->\t{}'.format(sample_from, to_directory), flush=True) + subprocess.run(['rsync', '-ah', sample_from, to_directory]) From 1d84dadf8c4e69360fc628ec69d72cc64263c25f Mon Sep 17 00:00:00 2001 From: Filip Bilandzija Date: Thu, 14 Nov 2024 12:42:29 +0100 Subject: [PATCH 4/5] Bugfixes --- inclusion/utils/utils.py | 2 +- tests/setup_regions.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/inclusion/utils/utils.py b/inclusion/utils/utils.py index a5d99b4..0c8a6f8 100644 --- a/inclusion/utils/utils.py +++ b/inclusion/utils/utils.py @@ -198,7 +198,7 @@ def define_used_tree_variables(cut): 'dau1_eleMVAiso', 'dau1_iso', 'dau2_iso', 'dau1_eta', 'dau2_eta', 'dau1_tauIdVSjet', 'dau2_tauIdVSjet', # 'dau1_mass', 'dau2_mass', 'Jet_mass', 'Jet_btagPNetB', 'Jet_btagDeepFlavB', 'Jet_pt', 'Jet_eta', 'bjet1_JetIdx', 'bjet2_JetIdx', - # 'dau1_pt', 'dau2_pt', + 'dau1_pt', 'dau2_pt', # 'nleps', 'event', 'isQuadJetTrigger', # 'bjet1_filterbits', 'bjet2_filterbits', 'tau1_filterbits', 'tau2_filterbits' diff --git a/tests/setup_regions.py b/tests/setup_regions.py index cf297b4..a7dd85a 100644 --- a/tests/setup_regions.py +++ b/tests/setup_regions.py @@ -35,7 +35,7 @@ pm = '\u00B1' ditau = tau+tau -def get_outname(channel, bigtau): +def get_outname(channel): utils.create_single_dir('data') name = "" @@ -44,6 +44,8 @@ def get_outname(channel, bigtau): s = 'data/regions_{}'.format(name) return s +def rec_dd(): + return dd(rec_dd) def trigger_regions(indir, channel, year, outname): outname = get_outname(outname) @@ -54,7 +56,7 @@ def trigger_regions(indir, channel, year, outname): elif channel == 'tautau': iso1 = range(0, 401, 8) pNet_dist = [x/12. for x in range(13)] - binning.update({ + binning = { 'genHH_mass': ([250, 300, 350, 400, 450, 500, 550, 600, 675, 800, 1000, 1600],), 'dau1_iso': (iso1,), 'dau1_pt': ([0, 20, 30, 40, 60, 80, 100, 125, 150, 200, 250],), @@ -71,7 +73,7 @@ def trigger_regions(indir, channel, year, outname): 'bjet1_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), 'bjet2_eta': ([-3, -2.5, -2.1, -1.8, -1.5, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.5, 1.8, 2.1, 2.5, 3],), # 'triggerbits': (range(31)) - }) + } norphans, ntotal = ({k:0 for k in categories} for _ in range(2)) @@ -210,9 +212,8 @@ def trigger_regions(indir, channel, year, outname): args = utils.parse_args(parser) #### run main function ### - if not args.plot: # for sample in args.masses: - trigger_regions(args.indir, args.channel, args.year, args.outname) + trigger_regions(args.indir, args.channel, args.year, args.outname) ########################### From d52220e3c5e19946acdc01d7bd85e150bfec1b51 Mon Sep 17 00:00:00 2001 From: Filip Bilandzija Date: Thu, 14 Nov 2024 12:52:21 +0100 Subject: [PATCH 5/5] Further bugfixes --- tests/setup_regions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/setup_regions.py b/tests/setup_regions.py index a7dd85a..231b731 100644 --- a/tests/setup_regions.py +++ b/tests/setup_regions.py @@ -39,6 +39,7 @@ def get_outname(channel): utils.create_single_dir('data') name = "" + name += str(channel) name += '.root' s = 'data/regions_{}'.format(name)