From 935c934277bf3453804534dee194f2fedbee60c7 Mon Sep 17 00:00:00 2001 From: LHCb Analysis Productions Bot <lhcb-dpa-wp2-admins@cern.ch> Date: Thu, 11 Apr 2024 08:37:51 +0000 Subject: [PATCH 01/70] Add dynamic options --- ap_merger.py | 112 ++++++++++++++++++ ...darkScalar2hh_2018_MagDown_job_autoconf.py | 13 ++ ...r_darkScalar2hh_2018_MagUp_job_autoconf.py | 13 ++ 3 files changed, 138 insertions(+) create mode 100644 ap_merger.py create mode 100644 dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagDown_job_autoconf.py create mode 100644 dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagUp_job_autoconf.py diff --git a/ap_merger.py b/ap_merger.py new file mode 100644 index 0000000000..cf7dc2fac6 --- /dev/null +++ b/ap_merger.py @@ -0,0 +1,112 @@ +############################################################################### +# (c) Copyright 2022 CERN for the benefit of the LHCb Collaboration # +# # +# This software is distributed under the terms of the GNU General Public # +# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". # +# # +# In applying this licence, CERN does not waive the privileges and immunities # +# granted to it by virtue of its status as an Intergovernmental Organization # +# or submit itself to any jurisdiction. # +############################################################################### +import subprocess +import sys +import tempfile +import xml.etree.ElementTree as ET +from pathlib import Path + +# pylint: disable=import-error +from GaudiConf.LbExec.options import FileFormats +from GaudiConf.LbExec.options import Options as OptionsBase + +SUMMARY_XML_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?> +<summary xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:noNamespaceSchemaLocation="$XMLSUMMARYBASEROOT/xml/XMLSummary.xsd"> + <success>True</success> + <step>finalize</step> + <usage><stat unit="KB" useOf="MemoryMaximum">0</stat></usage> + <input> +{input_files} + </input> + <output> +{output_files} + </output> +</summary> +""" +XML_FILE_TEMPLATE = ' <file GUID="" name="{name}" status="full">{n}</file>' +ALG_TO_CODE = { + "ZLIB": 1, + "LZMA": 2, + "LZ4": 4, + "ZSTD": 5, +} + + +class Options(OptionsBase): + """Conditions""" + + input_type: FileFormats = FileFormats.ROOT + simulation: None = False + data_type: None = None + + +def read_xml_file_catalog(xml_file_catalog): + if xml_file_catalog is None: + return {} + + tree = ET.parse(xml_file_catalog) + pfn_lookup: dict[str, list[str]] = {} + for file in tree.findall("./File"): + lfns = [x.attrib.get("name") for x in file.findall("./logical/lfn")] + if len(lfns) == 0: + continue + if len(lfns) > 1: + raise NotImplementedError(lfns) + lfn = lfns[0] + pfn_lookup[f"LFN:{lfn}"] = [ + x.attrib.get("name") for x in file.findall("./physical/pfn") + ] + return pfn_lookup + + +def resolve_input_files(input_files, file_catalog): + resolved = [] + for input_file in input_files: + if input_file.startswith("LFN:"): + print("Resolved", input_file, "to", file_catalog[input_file][0]) + input_file = file_catalog[input_file][0] + resolved.append(input_file) + return resolved + + +def hadd(options: Options, compression: str = "ZSTD:4"): + file_catalog = read_xml_file_catalog(options.xml_file_catalog) + input_files = resolve_input_files(options.input_files, file_catalog) + + alg, level = compression.split(":") + flags = [f"-f{ALG_TO_CODE[alg]}{int(level):02d}"] + flags += ["-j", f"{options.n_threads}"] + flags += ["-n", f"{max(10, options.n_threads*2)}"] + + with tempfile.NamedTemporaryFile(mode="wt") as tmpfile: + tmpfile.write("\n".join(input_files)) + tmpfile.flush() + cmd = ["hadd"] + flags + [options.ntuple_file, f"@{tmpfile.name}"] + print("Running", cmd) + subprocess.run(cmd, check=True) + + summary_xml = SUMMARY_XML_TEMPLATE.format( + input_files="\n".join( + XML_FILE_TEMPLATE.format( + name=name if name.startswith("LFN:") else f"PFN:{name}", n=1 + ) + for name in options.input_files + ), + output_files=XML_FILE_TEMPLATE.format( + name=f"PFN:{options.ntuple_file}", n=len(input_files) + ), + ) + if options.xml_summary_file: + print("Writing XML summary to", options.xml_summary_file) + Path(options.xml_summary_file).write_text(summary_xml) + + print("All done") + sys.exit(0) diff --git a/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagDown_job_autoconf.py b/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagDown_job_autoconf.py new file mode 100644 index 0000000000..f079315413 --- /dev/null +++ b/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagDown_job_autoconf.py @@ -0,0 +1,13 @@ +from Configurables import DaVinci +try: + DaVinci().Turbo = False +except AttributeError: + # Older DaVinci versions don't support Turbo at all + pass + +DaVinci().InputType = 'MDST' +DaVinci().DataType = '2018' +DaVinci().Simulation = False +DaVinci().Lumi = True +from Configurables import CondDB +CondDB(LatestGlobalTagByDataType=DaVinci().DataType) \ No newline at end of file diff --git a/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagUp_job_autoconf.py b/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagUp_job_autoconf.py new file mode 100644 index 0000000000..f079315413 --- /dev/null +++ b/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagUp_job_autoconf.py @@ -0,0 +1,13 @@ +from Configurables import DaVinci +try: + DaVinci().Turbo = False +except AttributeError: + # Older DaVinci versions don't support Turbo at all + pass + +DaVinci().InputType = 'MDST' +DaVinci().DataType = '2018' +DaVinci().Simulation = False +DaVinci().Lumi = True +from Configurables import CondDB +CondDB(LatestGlobalTagByDataType=DaVinci().DataType) \ No newline at end of file -- GitLab From 80c7235ace67a97c61a258b2bb29f5af33aa6412 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 18 Jun 2024 11:31:26 +0100 Subject: [PATCH 02/70] test --- bu2kdarkscalar_darkscalar2hh/info.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 bu2kdarkscalar_darkscalar2hh/info.yaml diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml new file mode 100644 index 0000000000..a5a3712843 --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -0,0 +1,25 @@ +{%- set polarities = ["Down", "Up"]%} + +{%- for polarity in polarities %} + +Bu2KdarkScalar_darkScalar2hh_2018_Mag{{polarity}}_job: + application: DaVinci/v46r8 + wg: QEE + automatically_configure: yes + turbo: no + inform: + - eleanor.whiter@cern.ch + options: + command: + - python + files: + - job.py + - photonSource_default.py + + input: + bk_query: /LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST + + output: bu2kdarkscalar_darkscalar2hh.ROOT + + +{%- endfor %} \ No newline at end of file -- GitLab From dbf2d927f01a2900477e029b43a4aca037dd8c8b Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 18 Jun 2024 11:40:25 +0100 Subject: [PATCH 03/70] monte carlo test --- bu2kdarkscalar_darkscalar2hh/info.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index a5a3712843..7637fb7664 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -2,7 +2,7 @@ {%- for polarity in polarities %} -Bu2KdarkScalar_darkScalar2hh_2018_Mag{{polarity}}_job: +Bu2KKK_2018_Mag{{polarity}}_job: application: DaVinci/v46r8 wg: QEE automatically_configure: yes @@ -17,9 +17,12 @@ Bu2KdarkScalar_darkScalar2hh_2018_Mag{{polarity}}_job: - photonSource_default.py input: - bk_query: /LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST + bk_query: /MC/2018/Beam6500GeV-2018-MagDown-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST + + #/LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST - output: bu2kdarkscalar_darkscalar2hh.ROOT + output: B2KKK.ROOT + #bu2kdarkscalar_darkscalar2hh.ROOT {%- endfor %} \ No newline at end of file -- GitLab From a86251ffa5a26a6d55e69c3d091c2b99b71a5bb8 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 26 Jun 2024 11:16:48 +0100 Subject: [PATCH 04/70] comments --- bu2kdarkscalar_darkscalar2hh/info.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 7637fb7664..9c5aaa8adf 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -2,7 +2,7 @@ {%- for polarity in polarities %} -Bu2KKK_2018_Mag{{polarity}}_job: +Bu2KdarkScalar_darkScalar2hh_2018_Mag{{polarity}}_job: application: DaVinci/v46r8 wg: QEE automatically_configure: yes @@ -17,11 +17,12 @@ Bu2KKK_2018_Mag{{polarity}}_job: - photonSource_default.py input: - bk_query: /MC/2018/Beam6500GeV-2018-MagDown-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST - + bk_query: /LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST + #/MC/2018/Beam6500GeV-2018-MagDown-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST #/LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST - output: B2KKK.ROOT + output: bu2kdarkscalar_darkscalar2hh.ROOT + #B2KKK.ROOT #bu2kdarkscalar_darkscalar2hh.ROOT -- GitLab From 1fc25d04a67b65e2994fb39829693c8bc68b2feb Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 15 Jul 2024 13:23:18 +0100 Subject: [PATCH 05/70] adapted AP to work to process MC --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 587 ++++++++++++ bu2kdarkscalar_darkscalar2hh/Ntuple.py | 939 +++++++++++++++++++ bu2kdarkscalar_darkscalar2hh/inputData.py | 14 + bu2kdarkscalar_darkscalar2hh/job.py | 135 +++ 4 files changed, 1675 insertions(+) create mode 100644 bu2kdarkscalar_darkscalar2hh/MCEventTools.py create mode 100644 bu2kdarkscalar_darkscalar2hh/Ntuple.py create mode 100644 bu2kdarkscalar_darkscalar2hh/inputData.py create mode 100644 bu2kdarkscalar_darkscalar2hh/job.py diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py new file mode 100644 index 0000000000..e985d51e09 --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -0,0 +1,587 @@ +import ROOT as r +import GaudiPython as GP +from GaudiConf import IOHelper +from Configurables import DaVinci, TriggerTisTos, ToolSvc +from ROOT import TLorentzVector as tlv +from LoKiMC.decorators import MCPX, MCPY, MCPZ, MCE, MCID, MCABSID, MCPT, MCM, MCETA, MCP, MCPHI +from LoKiPhys.decorators import M, PX, PY, PZ, E, ID, ABSID, PROBNNk, PROBNNghost, IPCHI2, P, PT, CL, ETA, TRGHOSTPROB, PHI +from array import array +from math import sqrt + +class MCPart: + """ + Simple class for storing rec and gen mc particles. + + INPUTS + ------ + par (mandatory): particle or mc particle from TES [LHCb.Particle/LHCb.MCParticle] + dl : list of daughter particles [list] + tos : list of L0 and HLT1 TOS [list(bool)] + ipc2 : IPCHI2 parameter [float] + lpar : linked particle [MCPart] + orvrt : MCParticle origin vertex type + mother : MCParticle mother [MCPart] + + INTERNAL MEMBERS + ---------------- + par : particle or mc particle + pid : particle's PID + isrec : flag for if particle is rec or gen [bool] + p : particle's 4-vector [TLorentzVector] + dl : daughter list + tos : tos list + ipc2 : IPCHI2 parameter + lpar : linked particle + corlinked : flag (intended for Bs) for whether all daughter particles have been + linked to like particles (i.e. pi+ linked to pi+) [bool] + alllinked : flag (intended for Bs) for whether all daughter particles have + been linked to anything [bool] + orvrt : origin vertex type for LHCb.MCParticle type particles + mother : mother particles for LHCb.MCParticle type particles + clink : flag (intended for daughter particles) for whether particle has + been linked to a matching particle type [bool] + pnnk : particle's PROBNNk + pnng : particle's PROBNNghost + trgp : particle's TRGHOSTPROB + dectype : category of decay type (assigned via MCEvent.categorize()) [int] + lineage : stores shared ancestor particles (back to grandmother particle) [list(int)] + -first digit is ancestor relation to h- + -second digit is ancestor relation to h+ + -third digit is ancestor relation to K + -1: particle; 2: mother; 3: grandmother; 0: no shared relation (if only two particles share ancestor) + sl : sister list (other particles from same origin vertex) + cl : particle's confidence level + isdrmatched: flag for if a B candidate has been linked using delta r matching + pv : coordinates of associated PV [list(float)] + sv : coordinates of associated SV [list(float)] + odectype : category of other decay type [int] + bgtype : background category [int] + """ + def __init__(self, par, dl=[], tos=[0,0,0], ipc2=None, lpar=None, orvrt=None, + mother=None, clink=None, pnnk=None, pnng=None, trgp=None, dectype=None, + lineage=None, sl=[], cl=None, isdrmatched=False, pv=[], sv=[], + odectype=None, bgtype=None): + self.par = par + self.pid = int(self.par.particleID().pid()) + self.p = tlv(par.momentum().Px(),par.momentum().Py(),par.momentum().Pz(),par.momentum().E()) + self.dl = dl + self.tos = tos + self.ipc2 = ipc2 + self.lpar = lpar + self.corlinked = False + self.alllinked = False + self.orvrt = orvrt + self.mother = mother + self.clink = clink + self.pnnk = pnnk + self.pnng = pnng + self.trgp = trgp + self.dectype = dectype + self.lineage = lineage + self.sl = sl + self.cl = cl + self.isdrmatched = isdrmatched + self.pv = pv + self.sv = sv + self.odectype = odectype + self.bgtype = bgtype + + def update(self, newdp=None, newdl=None, newl0tos=0, newhlt1etos=0, newhlt1mvatos=0, + newtos=[], newipc2=None, newlpar=None, newcorlinked=0, neworvrt=None, + newmother=None, newclink=None, newpnnk=None, newpnng=None, newtrgp=None, + newdectype=None, newlineage=None, newsp=None, newsl=None, newalllinked=0, + newcl=None, newisdrmatched=None, newpv=None, newsv=None, + newodectype=None, newbgtype=None): + """ + Method for updating MCPart information. + + INPUTS + ------ + newdp : adds single daughter particle to existing daughter list [MCPart] + newdl : overwrites existing daughter list with newdl [list(MCPart)] + newl0tos : updates L0 TOS [bool] + newhlt1etos : updates HLT1Electron TOS [bool] + newhlt1mvatos: updates HLT1TrackMVA TOS [bool] + newtos : overwrites existing tos list with newtos [list(bool)] + newipc2 : updates ipc2 (float) + newlpar : updates linked particle (MCPart) + newcorlinked : updates the corlinked flag [bool] + neworvrt : updates origin vertex type + newmother : updates associated mother particle + newclink : updates clink flag [bool] + newpnnk : updates PROBNNk + newpnng : updates PROBNNghost + newtrgp : updates TRGHOSTPROB + newdectype : updates the B decay type + newlineage : updates the shared ancestor list + newsp : adds single sister particle to existing list [MCPart] + newsl : overwrites existing sister list with newsl [list(MCPart)] + newalllinked : updates the alllinked flag [bool] + newcl : updates particle's confidence level + """ + if newdp != None: + if type(newdp) == type(self): self.dl += [newdp] + else: self.dl += [MCPart(newdp)] + if newdl != None: self.dl = newdl + if newl0tos != 0: self.tos[0] = newl0tos + if newhlt1etos != 0: self.tos[1] = newhlt1etos + if newhlt1mvatos != 0: self.tos[2] = newhlt1mvatos + #if len(newtos) == 2: self.tos = newtos + if len(newtos) == 3: self.tos = newtos + if newipc2 != None: self.ipc2 = newipc2 + if newlpar != None: + if type(newlpar) == type(self): self.lpar = newlpar + else: self.lpar = MCPart(newlpar) + if newcorlinked != 0: self.corlinked = newcorlinked + if newalllinked != 0: self.alllinked = newalllinked + if neworvrt != None: self.orvrt = neworvrt + if newmother != None: + if type(newmother) == type(self): self.mother = newmother + else: self.mother = MCPart(newmother) + if newclink != None: self.clink = newclink + if newpnnk != None: self.pnnk = newpnnk + if newpnng != None: self.pnng = newpnng + if newtrgp != None: self.trgp = newtrgp + if newdectype != None: self.dectype = newdectype + if newlineage != None: + if type(newlineage) == list: self.lineage = newlineage + elif type(newlineage) == int: self.lineage += [newlineage] + if newsp != None: + if type(newsp) == type(self): self.dl += [newsp] + else: self.dl += [MCPart(newsp)] + if newsl != None: self.sl = newsl + if newcl != None: self.cl = newcl + if newisdrmatched != None: self.isdrmatched = newisdrmatched + if newpv != None: self.pv = newpv + if newsv != None: self.sv = newsv + if newodectype != None: self.odectype = newodectype + if newbgtype != None: self.bgtype = newbgtype + +class MCEvent: + """ + Class for processing MC events. + """ + def __init__(self, all_Bparticles, mctool): + """ + Initializes MCEvent class. Creates list of rec level Bparticles, adds associated + daughter particles to Bparticles daughter list. + + INPUTS + ------ + all_Bparticles : B+/B-s from the TES + mctool : IParticle2MCWeightedAssociator instance to be used + + INTERNAL MEMBERS + ---------------- + hcuts: flag for whether the applyhcuts method has been run on this instance + of MCEvent [bool] + links: flag for whether the linkpars method has been run on this instance + of MCEvent [bool] + Bparticles : list of combined Bparticles with daughter particles in daughter list [list(MCPart)] + """ + self.hcuts = False + self.links = False + self.iscategorized = False + self.Bstore = all_Bparticles + self.mctool = mctool + self.Bparticles = [] + self.dihquery = [] + for Bparticle in all_Bparticles: # loop over all the B+/B-s stored in the TES + dps = [0,0] + dpsv = None + svcoor = None + for dp in B.daughters(): # loop over the daughters of the B+/B- particle + if abs(ID(dp)) == 321: + dps[1] = MCPart(dp,pnnk=PROBNNk(dp),pnng=PROBNNghost(dp),cl=CL(dp)) + else: + hadrons = [0,0] + dpsv = dp.endVertex() + for ddp in dp.endVertex().outgoingParticlesVector(): + hadron = MCPart(ddp) + hadron.update(newpnnk=PROBNNk(ddp),newpnng=PROBNNghost(ddp),newtrgp=TRGHOSTPROB(ddp)) + if hadron.pid == 321: hadrons[0] = hadron + elif hadron.pid == -321: hadrons[1] = hadron + svcoor = [dpsv.position().X(),dpsv.position().Y(),dpsv.position().Z()] + dps[0] = MCPart(dp,dl=hadrons,sv=svcoor) + B = MCPart(Bparticle,dl=dps) + self.Bparticles += [B] + + def linkpars(self): + """ + Links the rec level daughter particles (K, h-, h+) to gen level particles. + rels refers to related particles + rel_K: related K particle + rel_hadron_minus: related h- particle + rel_hadron_plus: related h+ particle + """ + self.allreglinked = True + for Bparticle in self.Bparticles: + rels = self.mctool.relatedMCPs(Bparticle.dl[1].par) + w = 0 + rel_K = None + for rel in rels: + if rel.weight() > w: w = rel.weight(); rel_K = rel.to() + if rel_K: + Bparticle.dl[1].update(newlpar=MCPart(rel_K,orvrt=rel_K.originVertex().type())) + if rel_K.originVertex().type() != 1: + try: + rel_K.mother().originVertex().type() + Bparticle.dl[1].lpar.update(newmother=MCPart(rel_K.mother(),orvrt=rel_K.mother().originVertex().type())) + if rel_K.mother().originVertex().type() != 1: + try: + rel_K.mother().mother().originVertex().type() + Bparticle.dl[1].lpar.mother.update(newmother=MCPart(rel_K.mother().mother(),orvrt=rel_K.mother().mother().originVertex().type())) + except: + Bparticle.dl[1].lpar.mother.update(newmother=None) + except: + Bparticle.dl[1].lpar.update(newmother=None) + + if MCID(rel_K) == Bparticle.dl[1].pid: Bparticle.dl[1].update(newclink=True) + else: Bparticle.dl[1].update(newclink=False) + + rels = self.mctool.relatedMCPs(Bparticle.dl[0].dl[0].par) + w = 0 + rel_hadron_minus = None + for rel in rels: + if rel.weight() > w: w = rel.weight(); rel_hadron_minus = rel.to() + if rel_hadron_minus: + Bparticle.dl[0].dl[0].update(newlpar=MCPart(rel_hadron_minus,orvrt=rel_hadron_minus.originVertex().type())) + if rel_hadron_minus.originVertex().type() != 1: + try: + rel_hadron_minus.mother().originVertex().type() + Bparticle.dl[0].dl[0].lpar.update(newmother=MCPart(rel_hadron_minus.mother(),orvrt=rel_hadron_minus.mother().originVertex().type())) + if rel_hadron_minus.mother().originVertex().type() != 1: + try: + rel_hadron_minus.mother().mother().originVertex().type() + Bparticle.dl[0].dl[0].lpar.mother.update(newmother=MCPart(rel_hadron_minus.mother().mother(),orvrt=rel_hadron_minus.mother().mother().originVertex().type())) + except: + Bparticle.dl[0].dl[0].lpar.mother.update(newmother=None) + except: + Bparticle.dl[0].dl[0].lpar.update(newmother=None) + if MCID(rel_hadron_minus) == Bparticle.dl[0].dl[0].pid: Bparticle.dl[0].dl[0].update(newclink=True) + else: Bparticle.dl[0].dl[0].update(newclink=False) + + rels = self.mctool.relatedMCPs(Bparticle.dl[0].dl[1].par) + w = 0 + rel_hadron_plus = None + for rel in rels: + if rel.weight() > w: w = rel.weight(); rel_hadron_plus = rel.to() + if rel_hadron_plus: + Bparticle.dl[0].dl[1].update(newlpar=MCPart(rel_hadron_plus,orvrt=rel_hadron_plus.originVertex().type())) + if rel_hadron_plus.originVertex().type() != 1: + try: + rel_hadron_plus.mother().originVertex().type() + particle.dl[0].dl[1].lpar.update(newmother=MCPart(rel_hadron_plus.mother(),orvrt=rel_hadron_plus.mother().originVertex().type())) + if rel_hadron_plus.mother().originVertex().type() != 1: + try: + rel_hadron_plus.mother().mother().originVertex().type() + Bparticle.dl[0].dl[1].lpar.mother.update(newmother=MCPart(rel_hadron_plus.mother().mother(),orvrt=rel_hadron_plus.mother().mother().originVertex().type())) + except: + Bparticle.dl[0].dl[1].lpar.mother.update(newmother=None) + except: + Bparticle.dl[0].dl[1].lpar.update(newmother=None) + if MCID(rel_hadron_plus) == Bparticle.dl[0].dl[1].pid: Bparticle.dl[0].dl[1].update(newclink=True) + else: Bparticle.dl[0].dl[1].update(newclink=False) + + if rel_K and rel_hadron_minus and rel_hadron_plus: + Bparticle.update(newalllinked=True) + if MCID(rel_K) == Bparticle.dl[1].pid and MCID(rel_hadron_minus) == Bparticle.dl[0].dl[0].pid and MCID(rel_hadron_plus) == Bparticle.dl[0].dl[1].pid: Bparticle.update(newcorlinked=True) + + if Bparticle.dl[1].lpar.mother: + if Bparticle.dl[1].lpar.mother.mother: + Klin = [Bparticle.dl[1].lpar.par,Bparticle.dl[1].lpar.mother.par, Bparticle.dl[1].lpar.mother.mother.par] + else: + Klin = [Bparticle.dl[1].lpar.par,Bparticle.dl[1].lpar.mother.par] + else: Klin = [Bparticle.dl[1].lpar.par] + + if Bparticle.dl[0].dl[0].lpar.mother: + if Bparticle.dl[0].dl[0].lpar.mother.mother: + hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par,Bparticle.dl[0].dl[0].lpar.mother.par, Bparticle.dl[0].dl[0].lpar.mother.mother.par] + else: + hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par,Bparticle.dl[0].dl[0].lpar.mother.par] + else: hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par] + + if Bparticle.dl[0].dl[1].lpar.mother: + if Bparticle.dl[0].dl[1].lpar.mother.mother: + hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par, B.dl[0].dl[1].lpar.mother.mother.par] + else: + hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par] + else: hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par] + + sharelin = [] + for i in range(len(hadron_minus_lin)): + for j in range(len(hadron_plus_lin)): + for k in range(len(Klin)): + lin = 0 + if hadron_minus_lin[i] == hadron_plus_lin[j]: + lin += (i+1)*100 + (j+1)*10 + if hadron_minus_lin[i] == Klin[k]: lin += k+1; sharelin += [lin] + else: sharelin += [lin] + else: + if hadron_minus_lin[i] == Klin[k]: lin += (i+1)*100 + (k+1); sharelin += [lin] + if hadron_plus_lin[j] == Klin[k]: lin = 0; lin += (j+1)*10 + (k+1); sharelin += [lin] + B.update(newlineage=sharelin) + + for prt in B.dl[1].lpar.par.originVertex().products(): + B.dl[1].lpar.update(newsp=prt) + if B.dl[1].lpar.mother: + for prt in B.dl[1].lpar.mother.par.originVertex().products(): + B.dl[1].lpar.mother.update(newsp=prt) + + for prt in B.dl[0].dl[0].lpar.par.originVertex().products(): + Bparticle.dl[0].dl[0].lpar.update(newsp=prt) + if Bparticle.dl[0].dl[0].lpar.mother: + for prt in B.dl[0].dl[0].lpar.mother.par.originVertex().products(): + Bparticle.dl[0].dl[0].lpar.mother.update(newsp=prt) + + for prt in Bparticle.dl[0].dl[1].lpar.par.originVertex().products(): + Bparticle.dl[0].dl[1].lpar.update(newsp=prt) + if Bparticle.dl[0].dl[1].lpar.mother: + for prt in B.dl[0].dl[1].lpar.mother.par.originVertex().products(): + Bparticle.dl[0].dl[1].lpar.mother.update(newsp=prt) + elif self.allreglinked: self.allreglinked = False + self.links = True + + def deltarlink(self, mcpstore): + if not self.links: self.linkpars() + + for Bparticle in self.Bparticles: + if not Bparticle.alllinked: + for recp in [Bparticle.dl[1],Bparticle.dl[0].dl[0],Bparticle.dl[0].dl[1]]: + if not recp.lpar: + mindr = 99999999999 + parlink = None + for mcp in mcpstore: + if MCID(mcp) == recp.pid: + dphi = MCPHI(mcp) - PHI(recp.par) + deta = MCETA(mcp) - ETA(recp.par) + deltar = sqrt(dphi**2 + deta**2) + if deltar < mindr: mindr = deltar; parlink = mcp + recp.update(newlpar=MCPart(parlink,orvrt=parlink.originVertex().type())) + if MCID(parlink) == recp.pid: recp.update(newclink=True) + else: recp.update(newclink=False) + if parlink.originVertex().type() != 1: + try: + parlink.mother().originVertex().type() + recp.lpar.update(newmother=MCPart(parlink.mother(),orvrt=parlink.mother().originVertex().type())) + if parlink.mother().originVertex().type() != 1: + try: + parlink.mother().mother().originVertex().type() + recp.lpar.mother.update(newmother=MCPart(parlink.mother().mother(),orvrt=parlink.mother().mother().originVertex().type())) + except: + recp.lpar.mother.update(newmother=None) + except: + recp.lpar.update(newmother=None) + + if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: + B.update(newisdrmatched=True) + if B.dl[1].lpar.pid == B.dl[1].pid and B.dl[0].dl[0].lpar.pid == B.dl[0].dl[0].pid and B.dl[0].dl[1].lpar.pid == B.dl[0].dl[1].pid: B.update(newcorlinked=True) + + if B.dl[1].lpar.mother: + if B.dl[1].lpar.mother.mother: + Klin = [B.dl[1].lpar.par,B.dl[1].lpar.mother.par, B.dl[1].lpar.mother.mother.par] + else: + Klin = [B.dl[1].lpar.par,B.dl[1].lpar.mother.par] + else: Klin = [B.dl[1].lpar.par] + + if B.dl[0].dl[0].lpar.mother: + if B.dl[0].dl[0].lpar.mother.mother: + hadron_minus_lin = [B.dl[0].dl[0].lpar.par,B.dl[0].dl[0].lpar.mother.par, B.dl[0].dl[0].lpar.mother.mother.par] + else: + hadron_minus_lin = [B.dl[0].dl[0].lpar.par,B.dl[0].dl[0].lpar.mother.par] + else: hadron_minus_lin = [B.dl[0].dl[0].lpar.par] + + if B.dl[0].dl[1].lpar.mother: + if B.dl[0].dl[1].lpar.mother.mother: + hadron_plus_lin = [B.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par, B.dl[0].dl[1].lpar.mother.mother.par] + else: + hadron_plus_lin = [B.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par] + else: hadron_plus_lin = [B.dl[0].dl[1].lpar.par] + + sharelin = [] + for i in range(len(hadron_minus_lin)): + for j in range(len(hadron_plus_lin)): + for k in range(len(Klin)): + lin = 0 + if hadron_minus_lin[i] == hadron_plus_lin[j]: + lin += (i+1)*100 + (j+1)*10 + if hadron_minus_lin[i] == Klin[k]: lin += k+1; sharelin += [lin] + else: sharelin += [lin] + else: + if hadron_minus_lin[i] == Klin[k]: lin += (i+1)*100 + (k+1); sharelin += [lin] + if hadron_plus_lin[j] == Klin[k]: lin = 0; lin += (j+1)*10 + (k+1); sharelin += [lin] + B.update(newlineage=sharelin) + + for prt in B.dl[1].lpar.par.originVertex().products(): + B.dl[1].lpar.update(newsp=prt) + if B.dl[1].lpar.mother: + for prt in B.dl[1].lpar.mother.par.originVertex().products(): + B.dl[1].lpar.mother.update(newsp=prt) + + for prt in B.dl[0].dl[0].lpar.par.originVertex().products(): + B.dl[0].dl[0].lpar.update(newsp=prt) + if B.dl[0].dl[0].lpar.mother: + for prt in B.dl[0].dl[0].lpar.mother.par.originVertex().products(): + B.dl[0].dl[0].lpar.mother.update(newsp=prt) + + for prt in B.dl[0].dl[1].lpar.par.originVertex().products(): + B.dl[0].dl[1].lpar.update(newsp=prt) + if B.dl[0].dl[1].lpar.mother: + for prt in B.dl[0].dl[1].lpar.mother.par.originVertex().products(): + B.dl[0].dl[1].lpar.mother.update(newsp=prt) + + def corlin(self, B): + """ + Internal method to be used in cattest method for determining if an MC + candidate has been linked properly and its source type can be categorized. + """ + if B.corlinked: pass + else: return False + + K = B.dl[1] + hadron_minus = B.dl[0].dl[0] + hadron_plus = B.dl[0].dl[1] + + try: hadron_plus.lpar.mother.orvrt + except: return False + + try: hadron_minus.lpar.mother.orvrt + except: return False + + return True + + def categorize(self): + """ + Categorizes the type of decay (i.e. type of background, signal). + + SOURCE TYPES + ------------ + + 0: MC signal (B+/- -> K+/- darkScalar (darkScalar -> h- h+) ) + 1: MC normalisation (B+/- -> K+/- h- h+) + 9: there was an error in MC truth matching + """ + if not self.links: self.linkpars() + + for Bparticle in self.Bparticles: + if self.corlin(B): + K = Bparticle.dl[1] + hadron_minus = Bparticle.dl[0].dl[0] + hadron_plus = Bparticle.dl[0].dl[1] + try: fillKpid = K.lpar.mother.pid + except: fillKpid = 0 + + if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(B.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle + #if K is K+ this must be a B+, if K is K- this must be a B- + if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: B.update(newbgtype=0) + if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: B.update(newbgtype=0) + + + for dp in Bparticle.dl: + dp.update(newbgtype=Bparticle.bgtype) + for ddp in dp.dl: + ddp.update(newbgtype=Bparticle.bgtype) + + def bgtypequery(self, part): + """ + Method to match an input particle to a TES particle and return the + input particle's decay type. + + INPUTS + ------ + part: particle to be matched [LHCb Particle object] + + OUTPUTS + ------- + dectype: decay type of the input particle [int] + """ + dectype = -99 + B = None + if ID(part) == 310: + dectype = ID(part) + elif part in self.Bstore: + for Bparticle in self.Bparticles: + if part == B.par: + B = B + break + if B != None: + K = B.dl[1] + hadron_minus = B.dl[0].dl[0] + hadron_plus = B.dl[0].dl[1] + + fill_B_dectype = B.dectype # fill B decay type + fill_B_odectype = B.odectype # fill B decay type + try: hadron_minus_morvrt = em.lpar.mother.orvrt + except: hadron_minus_morvrt = 99 + try: hadron_plus_morvrt = _hadron_plus.lpar.mother.orvrt + except: hadron_plus_morvrt = 99 + K_orvrt = K.orvrt + + try: + fill_K_mother_dectype = K.lpar.mother.pid + except: + fill_K_mother_dectype.pid = 0 + if not B.corlinked or hadron_minus_morvrt == 99 or hadron_plus_morvrt == 99: + dectype = 8 + elif fill_B_dectype == 0: + dectype = 0 + else: + dectype = -33 + else: + dectype = -66 + + return dectype + + def drquery(self, part): + """ + Method to match an input particle to a TES particle and return if the + input particle's MC truth matching was done by delta R matching. + + INPUTS + ------ + part: particle to be matched [LHCb Particle object] + + OUTPUTS + ------- + drmatch: whether particle was delta R matched [int (0: no; 1: yes; 99: error)] + """ + drmatch = 99 + B = None + if ID(part) == 310: + drmatch = ID(part) + elif part in self.Bstore: + for Bparticle in self.Bparticles: + if part == Bparticle.par: + B = Bparticle + break + if B != None: + if B.isdrmatched: drmatch = 1 + else: drmatch = 0 + else: + drmatch = 9 + + return drmatch + + def mcpquery(self, part): + """ + Method to match an input particle to a TES particle and return the + input particle's MCPart object. + + INPUTS + ------ + part: particle to be matched [LHCb Particle object] + + OUTPUTS + ------- + B: MCPart object of the corresponding B/eta [MCPart] + """ + B = None + if ID(part) == 310: + B = self.Bparticles[0] + elif part in self.Bstore: + for Bparticle in self.Bparticles: + if part == Bparticle.par: + B = Bparticle + break + + return B diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py new file mode 100644 index 0000000000..cbebf68a4e --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -0,0 +1,939 @@ +"""Defines the Ntuple class for storing an ntuple.""" +import array +from collections import OrderedDict +import ctypes +from math import sqrt +import time +from typing import Any, Union + +from Configurables import DaVinci +import GaudiPython +from LoKiArrayFunctors.decorators import AMAXDOCA +import ROOT + +from dtf import dtf +import MCEventTools +from ReconstructionTools import BremOverlapChecker, SelectRhos + + +class Ntuple: + """Class for storing an ntuple.""" + + ########################################################################### + def __init__( + self, gaudi, output_filename: "str" + ): + """Initialize the ntuple.""" + # Internal tools. + #self.combinatorial_fakePhotons = combinatorial_fakePhotons + self._detTool = gaudi.detSvc()["/dd/Structure/LHCb/BeforeMagnetRegion/Velo"] + self._pvrTool = gaudi.toolsvc().create( + "GenericParticle2PVRelator<_p2PVWithIPChi2, " + "OfflineDistanceCalculatorName>/P2PVWithIPChi2", + interface="IRelatedPVFinder", + ) + self._dstTool = gaudi.toolsvc().create( + "LoKi::TrgDistanceCalculator", interface="IDistanceCalculator" + ) + self._trkTool = gaudi.toolsvc().create( + "TrackMasterExtrapolator", interface="ITrackExtrapolator" + ) + self._trgTool = [ + gaudi.toolsvc().create("L0TriggerTisTos", interface="ITriggerTisTos"), + gaudi.toolsvc().create( + "TriggerTisTos/Hlt1TriggerTisTos", interface="ITriggerTisTos" + ), + gaudi.toolsvc().create( + "TriggerTisTos/Hlt2TriggerTisTos", interface="ITriggerTisTos" + ), + gaudi.toolsvc().create( + "TriggerTisTos/Strip/PhysTriggerTisTos", interface="ITriggerTisTos" + ), + ] + self._docaTool = GaudiPython.gbl.LoKi.Particles.DOCA(0, 0, self._dstTool) + self._veloTool = ROOT.VeloMaterial() + + # Triggers. + self.tos: "list[list[str]]" = [ + ["L0Electron.*"], + ["Hlt1.*Electron.*", "Hlt1.*TrackMVA.*"], + ["Hlt2ExoticaPi0ToDiEGamma", "Hlt2ExoticaEtaToDiEGamma"], + [], + ] + self.tis: "list[list[str]]" = [[], [], ["Hlt2Topo.*"], []] + + # Stored variables. + self.saved: "dict[str, int]" = {} + self.keys: "dict[str, str]" = {} + self.pres = {"tag": 0, "trk": 1, "cal": 2} + self.nhit = 2 + self.ntuple: "OrderedDict[str, array.array | Any]" = OrderedDict() + self.tfile = ROOT.TFile(output_filename, "RECREATE", "", 207) + self.ttree = ROOT.TTree("DecayData", "DecayData") + self.pvrs = "Rec/Vertex/Primary" + if DaVinci().InputType == "MDST": + self.pvrs = "Leptonic/" + self.pvrs + vrsVrt = ["x", "y", "z", "dx", "dy", "dz"] + vrsMom = ["px", "py", "pz", "e"] + vrsTrk = [ + "pnn_e", + "pnn_mu", + "pnn_pi", + "pnn_k", + "pnn_p", + "pnn_ghost", + "ip", + "ip_chi2", + "vt_miss", + "mm", + "dmm", + "m", + "dm", + "idx_pvr", + "ecalx", + "ecaly", + "ecalz", + "veloCharge", + ] + for h in range(0, self.nhit): + vrsTrk += [f"x{h}", f"y{h}", f"z{h}", f"t{h}", f"p{h}"] + vrsTag = ( + [ + "mm", + "dmm", + "m", + "dm", + "doca", + "chi2", + "ip", + "ip_chi2", + "fd", + "fd_chi2", + "vt_tip", + "vt_d", + "withBrem", + "BremMatched", + "photonBremOverlap", + "nBremAdded_em", + "nBremAdded_ep", + "etrackOL", + ] + + [ + f"{dtf}dtf_{x}" + for dtf in ["p", "d"] + for x in ["m", "dm", "chi2"] + + vrsMom + + vrsVrt + + [f"die_{y}" for y in ["m"] + vrsMom] + ] + + [f"die_{x}" for x in ["m"] + vrsMom] + ) + + vrsMC = [] + if DaVinci().Simulation: + dps = ["em", "ep", "g"] + params = ["mpid", "orvrt", "morvrt", "mmpid", "mmorvrt"] + for dp in dps: + vrsMC += [dp + "_lpid"] + vrsMC += [dp + "_l" + param for param in vrsMom] + vrsMC += [dp + "_" + param for param in params] + vrsMC += ["bgtype", "drmatch"] + + vrsTrg = [ + f"tos{idx}" for idx in range(0, sum(len(lvl) for lvl in self.tos)) + ] + [f"tis{idx}" for idx in range(0, sum(len(lvl) for lvl in self.tis))] + vrsIdx = ["idx_pvr", "idx_prt0", "idx_prt1", "pre_prt0", "pre_prt1"] + self._vrs("tag", vrsIdx + vrsMom + ["pid"] + vrsVrt + vrsTag + vrsTrg + vrsMC) + self._vrs("trk", vrsMom + ["pid"] + vrsTrk) + self._vrs("cal", ["mm", "dmm", "m", "dm", "idx_pvr", "pid"] + vrsMom) + self._vrs("pvr", vrsVrt) + self.ntuple["pvr_n"] = array.array("i", [-1]) + self.ntuple["spd_n"] = array.array("i", [-1]) + self.ntuple["run_n"] = array.array("l", [-1]) + self.ntuple["evt_n"] = array.array("l", [-1]) + self.ntuple["evt_tck"] = array.array("l", [-1]) + # tmap = {int: "/I", long: "/L", float: "/F"} + tmap = {"i": "/I", "l": "/L", "f": "/F"} + for key, val in self.ntuple.items(): + if isinstance(val, array.array): + self.ttree.Branch(key, val, key + tmap[val.typecode]) + else: + self.ttree.Branch(key, val) + + # Cache time and size. + self.tfile.Write(self.ttree.GetName(), ROOT.TObject.kOverwrite) + self.size = self.tfile.GetBytesWritten() + self.time = time.time() + + ########################################################################### + def _vrs(self, pre: "str", vrs): + """Initialize variables with a given prefix.""" + self.keys[pre] = f"{pre}_{vrs[0]}" + tmap = {"int": ["pid", "idx", "pre", "tip", "Brem"], "bool": ["tis", "tos"]} + for var in vrs: + t = "float" + for key, k_list in tmap.items(): + if any(sub in var for sub in k_list): + t = key + break + self.ntuple[f"{pre}_{var}"] = ROOT.vector(t)() + + ########################################################################### + def _key(self, obj): + """Return the key for an object.""" + try: + trk = obj.proto().track().momentum() + except: + trk = None + try: + cal = obj.proto().calo()[0].position() + except: + cal = None + try: + vrt = obj.position() + except: + vrt = None + if trk: + return (trk.X(), trk.Y(), trk.Z()) + if cal: + return (cal.x(), cal.y(), cal.z(), cal.e()) + if vrt: + return (vrt.X(), vrt.Y(), vrt.Z()) + return (obj.momentum().Px(), obj.momentum().Py(), obj.momentum().Pz()) + + ########################################################################### + def _save(self, pre: "str", key): + """Save an object and return the index.""" + val: "Any" = self.ntuple[self.keys[pre]] + idx = val.size() - 1 + self.saved[key] = idx + return idx + + ########################################################################### + def _index(self, key): + """Return the saved index, if it exists.""" + return self.saved.get(key) + + ########################################################################### + def close(self, events): + """Close the ntuple.""" + pTime = ROOT.TParameter(float)("time", time.time() - self.time) + self.tfile.WriteTObject(self.ttree, self.ttree.GetName(), "Overwrite") + pSize = ROOT.TParameter(float)("size", self.tfile.GetBytesWritten() - self.size) + pEvts = ROOT.TParameter(int)("evts", events) + self.tfile.WriteTObject(pTime, "time", "Overwrite") + self.tfile.WriteTObject(pSize, "size", "Overwrite") + self.tfile.WriteTObject(pEvts, "evts", "Overwrite") + self.tfile.Close() + + ########################################################################### + def clear(self): + """Clear the ntuple.""" + self.saved.clear() + for val in self.ntuple.values(): + try: + val.clear() + except: + val[0] = -1 + + ########################################################################### + def fill( + self, + key: "str | None" = None, + val: "float | int | None" = None, + idx: "int | None" = None, + ): + """Fill a variable into the ntuple.""" + if key is None and val is None: + self.tfile.Cd("") + self.ttree.Fill() + elif key is not None and val is not None: + if key not in self.ntuple: + raise ValueError(f"key '{key}' not recognized") + if idx is None: + _nval: "Any" = self.ntuple[key] + _nval.push_back(val) + elif idx < len(self.ntuple[key]): + self.ntuple[key][idx] = val + else: + raise ValueError( + f"idx must be less than {len(self.ntuple[key])} for '{key}'" + f", not {idx}" + ) + else: + raise ValueError("must specify both key and val or neither") + + ########################################################################### + def _fillVrt(self, pre: "str", pos, cov: "list[list[float]] | None"): + """Fill a vertex.""" + if pos is None or cov is None: + self.fill(f"{pre}_x", -1) + self.fill(f"{pre}_y", -1) + self.fill(f"{pre}_z", -1) + self.fill(f"{pre}_dx", -1) + self.fill(f"{pre}_dy", -1) + self.fill(f"{pre}_dz", -1) + else: + self.fill(f"{pre}_x", pos.X()) + self.fill(f"{pre}_y", pos.Y()) + self.fill(f"{pre}_z", pos.Z()) + self.fill(f"{pre}_dx", sqrt(abs(cov[0][0]))) + self.fill(f"{pre}_dy", sqrt(abs(cov[1][1]))) + self.fill(f"{pre}_dz", sqrt(abs(cov[2][2]))) + + ########################################################################### + def _fillMom(self, pre: "str", mom): + """Fill momentum for a particle.""" + if mom is None: + self.fill(f"{pre}_px", -1) + self.fill(f"{pre}_py", -1) + self.fill(f"{pre}_pz", -1) + self.fill(f"{pre}_e", -1) + else: + self.fill(f"{pre}_px", mom.Px()) + self.fill(f"{pre}_py", mom.Py()) + self.fill(f"{pre}_pz", mom.Pz()) + self.fill(f"{pre}_e", mom.E()) + + ########################################################################### + def _fillMM(self, pre: "str", prt): + """Fill measured mass for a particle.""" + self.fill(f"{pre}_mm", prt.measuredMass()) + self.fill(f"{pre}_dmm", prt.measuredMassErr()) + + ########################################################################### + def _fillM(self, pre: "str", mom): + """Fill mass from a particle's momentum.""" + try: + m_class = mom.m() + m = m_class.value() + dm = m_class.error() + except: + m = -1 + dm = -1 + self.fill(f"{pre}_m", m) + self.fill(f"{pre}_dm", dm) + + ########################################################################### + def _filldieMomMass(self, pre: "str", mom): + """Fill momentum and mass for a particle.""" + if mom is None: + self.fill(f"{pre}_die_px", -1) + self.fill(f"{pre}_die_py", -1) + self.fill(f"{pre}_die_pz", -1) + self.fill(f"{pre}_die_e", -1) + self.fill(f"{pre}_die_m", -1) + else: + self.fill(f"{pre}_die_px", mom.Px()) + self.fill(f"{pre}_die_py", mom.Py()) + self.fill(f"{pre}_die_pz", mom.Pz()) + self.fill(f"{pre}_die_e", mom.E()) + self.fill(f"{pre}_die_m", mom.M()) + + ########################################################################### + def _fillgenMomPID(self, pre: "str", mcp): + """Fill generator level MC momentum and pid for a particle.""" + if mcp: + if mcp.pid == 11: + dptype = "em" + elif mcp.pid == -11: + dptype = "ep" + elif mcp.pid == 22: + dptype = "g" + else: + raise ValueError(f"mcp.pid {mcp.pid} not recognized") + mom = mcp.lpar.p + lpid = mcp.lpar.pid + try: + lpid = int(mcp.lpar.pid) + except: + lpid = 99 + self.fill(f"{pre}_{dptype}_lpx", mom.Px()) + self.fill(f"{pre}_{dptype}_lpy", mom.Py()) + self.fill(f"{pre}_{dptype}_lpz", mom.Pz()) + self.fill(f"{pre}_{dptype}_le", mom.E()) + self.fill(f"{pre}_{dptype}_lpid", lpid) + else: + for dptype in ["em", "ep", "g"]: + self.fill(f"{pre}_{dptype}_lpx", -1) + self.fill(f"{pre}_{dptype}_lpy", -1) + self.fill(f"{pre}_{dptype}_lpz", -1) + self.fill(f"{pre}_{dptype}_le", -1) + self.fill(f"{pre}_{dptype}_lpid", -1) + + ########################################################################### + def _filltrkOL(self, pre: "str", prt): + """Find number of shared track lhcbIDs between e- and e+. + + Accessed through dielectron candidate. + """ + if prt.particleID().pid() == 310: + nol = 0 + em = ep = None + for i_dp in range(len(prt.daughtersVector())): + dp = prt.daughtersVector()[i_dp] + #if dp.particleID().pid() == -321: + if dp.particleID().pid() == -321 or dp.particleID().pid() == -211: + assert em is None + em = dp + #elif dp.particleID().pid() == 321: + elif dp.particleID().pid() == 321 or dp.particleID().pid() == 211: + assert ep is None + ep = dp + assert em is not None and ep is not None, prt + for i_emid in range(len(em.proto().track().lhcbIDs())): + emid = em.proto().track().lhcbIDs()[i_emid] + for i_epid in range(len(ep.proto().track().lhcbIDs())): + epid = ep.proto().track().lhcbIDs()[i_epid] + if emid == epid: + nol += 1 + if (nol * 1.0) / len(em.proto().track().lhcbIDs()) > (nol * 1.0) / len( + ep.proto().track().lhcbIDs() + ): + olperc = (nol * 1.0) / len(em.proto().track().lhcbIDs()) + else: + olperc = (nol * 1.0) / len(ep.proto().track().lhcbIDs()) + else: + olperc = -1 + self.fill(f"{pre}_etrackOL", olperc) + + ########################################################################### + def _fillMat( + self, + pre: "str", + pvr, + pos, + dtrs: "list", + cov: "list[list[float]] | None", + year: "int", + run: "int", + ): + """Fill the material information for a particle. + + Relies on self.veloTool. + """ + if pvr and len(dtrs) == 2 and not any(x is None for x in (pos, cov)): + if year not in (2016, 2017, 2018): + raise ValueError(f"year {year} not recognized") + pvp = pvr.position() + vfv = (pos.X() - pvp.X(), pos.Y() - pvp.Y(), pos.Z() - pvp.Z()) + assert cov is not None + self.fill( + f"{pre}_vt_d", + self._veloTool.distance( + pos.X(), + pos.Y(), + pos.Z(), + vfv[0], + vfv[1], + vfv[2], + dtrs[0].Px(), + dtrs[0].Py(), + dtrs[0].Pz(), + dtrs[1].Px(), + dtrs[1].Py(), + dtrs[1].Pz(), + sqrt(abs(cov[0][0])), + sqrt(abs(cov[1][1])), + sqrt(abs(cov[2][2])), + year, + run, + ), + ) + self.fill( + f"{pre}_vt_tip", + self._veloTool.tip( + pvp.X(), pvp.Y(), pvp.Z(), vfv[0], vfv[1], vfv[2], year, run + ), + ) + else: + self.fill(f"{pre}_vt_d", -1) + self.fill(f"{pre}_vt_tip", -1) + + ########################################################################### + def _fillBrem(self, pre: "str", prt: "Any"): + """Fill the Brem information for a particle.""" + if prt.particleID().pid() == 113: + # pion/eta + assert prt.hasInfo(1) and prt.hasInfo(2) + extraInfo = prt.extraInfo() + self.fill(f"{pre}_withBrem", int(extraInfo[1])) + self.fill(f"{pre}_BremMatched", int(extraInfo[2])) + else: + # not pion/eta + assert not (prt.hasInfo(1) or prt.hasInfo(2)) + self.fill(f"{pre}_withBrem", -1) + self.fill(f"{pre}_BremMatched", -1) + BOC = BremOverlapChecker(prt) + self.fill(f"{pre}_photonBremOverlap", BOC.overlapFound()) + self.fill(f"{pre}_nBremAdded_em", BOC.nBremAdded("em")) + self.fill(f"{pre}_nBremAdded_ep", BOC.nBremAdded("ep")) + + ########################################################################### + def _fillTrg(self, pre: "str", prt): + """Fill the trigger lines for a particle.""" + # Trigger on signal decisions. + idx = 0 + for trg, decs in zip(self._trgTool, self.tos): + trg.setOfflineInput(prt) + for dec in decs: + trg.setTriggerInput(dec + "Decision") + dec = trg.tisTosTobTrigger() + self.fill(f"{pre}_tos{idx}", dec.tos()) + idx = idx + 1 + + # Trigger independent signal decisions. + idx = 0 + for trg, decs in zip(self._trgTool, self.tis): + trg.setOfflineInput(prt) + for dec in decs: + trg.setTriggerInput(dec + "Decision") + dec = trg.tisTosTobTrigger() + self.fill(f"{pre}_tis{idx}", dec.tis()) + idx = idx + 1 + + ########################################################################### + def _fillTag( + self, + pre: "str", + prt, + year: "int", + run: "int", + check=False, + mctool=None, + ): + """Fill the information for a tag.""" + # Vertex. + pvr = self._pvrTool.relatedPV(prt, self.pvrs) + mom = prt.momentum() + vrt = prt.endVertex() + cov = vrt.covMatrix() + pos = vrt.position() + if check: + if prt.particleID().pid() not in (521, -521, 310): + return False + for i_dtr in range(len(prt.daughtersVector())): + dtr = prt.daughtersVector()[i_dtr] + if not self.fillPrt(dtr, pos, year, run, mctool=mctool, check=True): + return False + if pvr == -1: + return False + return True + + pdtf = dtf(prt, pvr, True) + ddtf = dtf(prt, pvr, False) + if prt.particleID().pid() in (-521, 521): + # pion/eta + pdtf.fit() + ddtf.fit() + assert pdtf.dielectron == ddtf.dielectron + assert pdtf.dielectron is not None + dtrp = pdtf.dielectron.momentum() + elif prt.particleID().pid() == 310: + # dielectron + dtrp = prt.momentum() + else: + raise ValueError( + f"prt.particleID().pid() {prt.particleID().pid()} not recognized" + ) + + # Daughters. + daughter_momenta = [] + for idx in range(len(prt.daughtersVector())): + dtr = prt.daughtersVector()[idx] + daughter_momenta += [dtr.momentum()] + dtrPre, dtrIdx = self.fillPrt( # type: ignore + dtr, pos, year, run, mctool=mctool + ) + self.fill(f"{pre}_idx_prt{idx}", dtrIdx) + self.fill(f"{pre}_pre_prt{idx}", self.pres[dtrPre]) + # dtf (dtf_pos) not implemented for these daughters + # because fillPrt is designed to automatically determine the branch names + + self._filldieMomMass(pre, dtrp) + self._filldieMomMass(f"{pre}_pdtf", pdtf.get_die_momentum()) + self._filldieMomMass(f"{pre}_ddtf", ddtf.get_die_momentum()) + self._filltrkOL(pre, prt) + + # MC BG type + if mctool is not None: + mcprt = mctool.mcpquery(prt) + + if mcprt: + self._fillgenMomPID(pre, mcprt.dl[1]) + self._fillgenMomPID(pre, mcprt.dl[0].dl[0]) + self._fillgenMomPID(pre, mcprt.dl[0].dl[1]) + else: + self._fillgenMomPID(pre, mcprt) + + try: + emmpid = int(mcprt.dl[0].dl[0].lpar.mother.pid) + except: + emmpid = 99 + try: + epmpid = int(mcprt.dl[0].dl[1].lpar.mother.pid) + except: + epmpid = 99 + try: + gmpid = int(mcprt.dl[1].lpar.mother.pid) + except: + gmpid = 99 + + try: + emorvrt = int(mcprt.dl[0].dl[0].lpar.orvrt) + except: + emorvrt = 99 + try: + eporvrt = int(mcprt.dl[0].dl[1].lpar.orvrt) + except: + eporvrt = 99 + try: + gorvrt = int(mcprt.dl[1].lpar.orvrt) + except: + gorvrt = 99 + + try: + emmorvrt = int(mcprt.dl[0].dl[0].lpar.mother.orvrt) + except: + emmorvrt = 99 + try: + epmorvrt = int(mcprt.dl[0].dl[1].lpar.mother.orvrt) + except: + epmorvrt = 99 + try: + gmorvrt = int(mcprt.dl[1].lpar.mother.orvrt) + except: + gmorvrt = 99 + + try: + emmmpid = int(mcprt.dl[0].dl[0].lpar.mother.mother.pid) + except: + emmmpid = 99 + try: + epmmpid = int(mcprt.dl[0].dl[1].lpar.mother.mother.pid) + except: + epmmpid = 99 + try: + gmmpid = int(mcprt.dl[1].lpar.mother.mother.pid) + except: + gmmpid = 99 + + try: + emmmorvrt = int(mcprt.dl[0].dl[0].lpar.mother.mother.orvrt) + except: + emmmorvrt = 99 + try: + epmmorvrt = int(mcprt.dl[0].dl[1].lpar.mother.mother.orvrt) + except: + epmmorvrt = 99 + try: + gmmorvrt = int(mcprt.dl[1].lpar.mother.mother.orvrt) + except: + gmmorvrt = 99 + + try: + bgt = int(mcprt.bgtype) + except: + bgt = 99 + + self.fill(f"{pre}_bgtype", bgt) + try: + drm = 1 if mcprt.isdrmatched else 0 + except: + drm = -1 + self.fill(f"{pre}_drmatch", drm) + + self.fill(f"{pre}_em_mpid", emmpid) + self.fill(f"{pre}_ep_mpid", epmpid) + self.fill(f"{pre}_g_mpid", gmpid) + + self.fill(f"{pre}_em_mmpid", emmmpid) + self.fill(f"{pre}_ep_mmpid", epmmpid) + self.fill(f"{pre}_g_mmpid", gmmpid) + + self.fill(f"{pre}_em_orvrt", emorvrt) + self.fill(f"{pre}_ep_orvrt", eporvrt) + self.fill(f"{pre}_g_orvrt", gorvrt) + + self.fill(f"{pre}_em_morvrt", emmorvrt) + self.fill(f"{pre}_ep_morvrt", epmorvrt) + self.fill(f"{pre}_g_morvrt", gmorvrt) + + self.fill(f"{pre}_em_mmorvrt", emmmorvrt) + self.fill(f"{pre}_ep_mmorvrt", epmmorvrt) + self.fill(f"{pre}_g_mmorvrt", gmmorvrt) + + # Trigger. + self._fillTrg(pre, prt) + + # Vertex. + self.fill(f"{pre}_doca", AMAXDOCA("")(prt.daughters())) + self.fill(f"{pre}_chi2", vrt.chi2()) + self.fill( + f"{pre}_pdtf_chi2", -1 if pdtf.get_chi2() is None else pdtf.get_chi2() + ) + self.fill( + f"{pre}_ddtf_chi2", -1 if ddtf.get_chi2() is None else ddtf.get_chi2() + ) + self._fillVrt(pre, pos, cov) + self._fillVrt( + f"{pre}_pdtf", pdtf.get_position(), pdtf.get_position_covariance() + ) + self._fillVrt( + f"{pre}_ddtf", ddtf.get_position(), ddtf.get_position_covariance() + ) + + # Material tool. + self._fillMat(pre, pvr, pos, daughter_momenta, cov, year, run) + # fillMat not implemented for dtf b/c it's a pain to code up daughter momenta + + # Brem. + self._fillBrem(pre, prt) + + # Flight distance. + fd, fdChi2 = ctypes.c_double(-1), ctypes.c_double(-1) + if pvr and vrt: + self._dstTool.distance(vrt, pvr, fd, fdChi2) + self.fill(f"{pre}_fd", fd.value) + self.fill(f"{pre}_fd_chi2", fdChi2.value) + + # IP and momentum. + self._fillMom(pre, mom) + self._fillMom(f"{pre}_pdtf", pdtf.get_momentum()) + self._fillMom(f"{pre}_ddtf", ddtf.get_momentum()) + self._fillMM(pre, prt) + self._fillM(pre, mom) + self._fillM(f"{pre}_pdtf", pdtf.get_momentum()) + self._fillM(f"{pre}_ddtf", ddtf.get_momentum()) + ip, ipChi2 = ctypes.c_double(-1), ctypes.c_double(-1) + if pvr: + self._dstTool.distance(prt, pvr, ip, ipChi2) + self.fill(f"{pre}_ip", ip.value) + self.fill(f"{pre}_ip_chi2", ipChi2.value) + + return pvr + + ########################################################################### + def _fillTrk(self, pre: str, prt, org, year: "int", run: "int"): + """Fill the information for a track.""" + # Particle ID variables. + pid, pro, mom = prt.particleID().pid(), prt.proto(), prt.momentum() + for idx, pid in enumerate(("e", "mu", "pi", "k", "p", "ghost"), 700): + self.fill(f"{pre}_pnn_{pid}", pro.info(idx, -100)) + + # Hits. + trk, ids = pro.track(), [] + # VELO + for i_id in range(len(trk.lhcbIDs())): + idx = trk.lhcbIDs()[i_id] + if idx.isVelo(): + det = self._detTool.sensor(idx.veloID()) + ids += [(det.z(), det, idx)] + ids.sort(key=lambda x: (x[0], x[1], x[2].lhcbID())) + for hit in range(0, self.nhit): + assert hit < len(ids) + z, det, idx = ids[hit] + sns, vec = idx.veloID(), GaudiPython.gbl.LHCb.StateVector() + self._trkTool.propagate(trk, z, vec, prt.particleID()) + self.fill(f"{pre}_x{hit}", vec.x()) + self.fill(f"{pre}_y{hit}", vec.y()) + self.fill(f"{pre}_z{hit}", vec.z()) + if sns.isPhiType(): + self.fill(f"{pre}_t{hit}", det.globalPhi(sns.strip(), 0)) + self.fill(f"{pre}_p{hit}", -det.phiPitch(sns.strip())) + if sns.isRType(): + self.fill(f"{pre}_t{hit}", det.globalR(sns.strip(), 0)) + self.fill(f"{pre}_p{hit}", det.rPitch(sns.strip())) + # veloCharge + self.fill( + f"{pre}_veloCharge", + pro.info(GaudiPython.gbl.LHCb.ProtoParticle.VeloCharge, -10000.0), + ) + # ECal + em_hypo = None # calorimeter hypothesis using EmCharged + ecal_vec = GaudiPython.gbl.LHCb.StateVector() # position of interaction + for i_hypo in range(len(pro.calo())): + if ( + pro.calo()[i_hypo].hypothesis() + == GaudiPython.gbl.LHCb.CaloHypo.EmCharged + ): + assert em_hypo is None + em_hypo = pro.calo()[i_hypo] + if em_hypo: + assert em_hypo.hypothesis() == GaudiPython.gbl.LHCb.CaloHypo.EmCharged + ecal_z = ( + em_hypo.position().z() + ) # mm, Z-position where cluster parameters are estimated + self._trkTool.propagate(trk, ecal_z, ecal_vec, prt.particleID()) + else: + ecal_vec.setX(-1e9) + ecal_vec.setY(-1e9) + ecal_vec.setZ(-1e9) + self.fill(f"{pre}_ecalx", ecal_vec.x()) + self.fill(f"{pre}_ecaly", ecal_vec.y()) + self.fill(f"{pre}_ecalz", ecal_vec.z()) + + # Material tool. + self.fill( + f"{pre}_vt_miss", + self._veloTool.miss( + org.x(), + org.y(), + org.z(), + mom.Px(), + mom.Py(), + mom.Pz(), + ids[0][0], + year, + run, + ), + ) + + # IP and momentum. + pvr = self._pvrTool.relatedPV(prt, self.pvrs) + self._fillMom(pre, mom) + self._fillM(pre, mom) + self._fillMM(pre, prt) + ip, ipChi2 = ctypes.c_double(-1), ctypes.c_double(-1) + if pvr: + self._dstTool.distance(prt, pvr, ip, ipChi2) + self.fill(f"{pre}_ip", ip.value) + self.fill(f"{pre}_ip_chi2", ipChi2.value) + return pvr + + ########################################################################### + def _fillCal(self, pre: "str", prt): + """Fill the information for a calorimetry object.""" + mom = prt.momentum() + self._fillMom(pre, mom) + self._fillM(pre, mom) + self._fillMM(pre, prt) + + ########################################################################### + def fillPrt( + self, + prt, + org=None, + year=None, + run=None, + check=False, + mctool=None, + ): + """Fill the information for a particle.""" + vrt = prt.endVertex() + key = self._key(prt) + idx = self._index(key) + pre = "tag" if vrt else ("trk" if prt.charge() else "cal") + if idx is not None: + return (pre, idx) + + # Check if the particle is valid without filling. + tagargs, tagkwargs = [pre, prt, year, run], {"mctool": mctool} + if check: + assert year is not None and run is not None, (year, run) + return ( + pre == "tag" and self._fillTag(*tagargs, **tagkwargs, check=True) + ) or pre != "tag" + # Fill the particle. + if pre == "tag": + assert year is not None and run is not None, (year, run) + assert prt.particleID().pid() in (-521, 521, 310), prt.particleID().pid() + pvr = self._fillTag(*tagargs, **tagkwargs) + assert not isinstance(pvr, bool), pvr + elif pre == "trk": + assert year is not None and run is not None, (year, run) + #assert prt.particleID().pid() in (-321, 321), prt.particleID().pid() + assert prt.particleID().pid() in (-321, 321, -211, 211), prt.particleID().pid() + pvr = self._fillTrk(pre, prt, org, year, run) + elif pre == "cal": + assert prt.particleID().pid() == 22, prt.particleID().pid() + assert self._fillCal(pre, prt) is None + pvr = None + else: + raise ValueError(f"pre '{pre}' not recognized") + self.fill(f"{pre}_pid", prt.particleID().pid()) + + # Primary vertex. + if pvr: + pvrKey = self._key(pvr) + pvrIdx = self._index(pvrKey) + if pvrIdx is None: + self._fillVrt("pvr", pvr.position(), pvr.covMatrix()) + pvrIdx = self._save("pvr", pvrKey) + self.fill(f"{pre}_idx_pvr", pvrIdx) + else: + self.fill(f"{pre}_idx_pvr", -1) + return (pre, self._save(pre, key)) + + +def fill_ntuple( + ntuple: "Ntuple", + tes, + candloc_Brem: "str", + candloc_noBrem: "str", + #head: "Union[str, None]", + genTool, +): + """Assign values to the ntuple.""" + year = int(DaVinci().DataType) + + def get_particles(candloc): + try: + particles = tes[candloc] + len(particles) + size = len(particles) + #print("found {0} particles".format(size)) + except: + particles = [] + return particles + + # Fill the event. + daq = tes["DAQ/ODIN"] + rnum = daq.runNumber() + ntuple.ntuple["run_n"][0] = rnum + ntuple.ntuple["evt_n"][0] = daq.eventNumber() + ntuple.ntuple["evt_tck"][0] = daq.triggerConfigurationKey() + ntuple.ntuple["pvr_n"][0] = len(tes[ntuple.pvrs]) + ntuple.ntuple["spd_n"][0] = int( + GaudiPython.gbl.LoKi.L0.DataValue("Spd(Mult)")(tes["Trig/L0/L0DUReport"]) + ) + + # Fill the particles. + all_rhos = get_particles(candloc_Brem) + + + + #SelectRhos( + # get_particles(candloc_Brem), + # get_particles(candloc_noBrem), + # ntuple.combinatorial_fakePhotons, + # ) + if DaVinci().Simulation: + mct = MCEventTools.MCEvent(all_rhos, genTool) + mct.linkpars() + mcpstore = get_particles( + "AllStreams/MC/Particles" + if DaVinci().InputType == "MDST" + else "MC/Particles" + ) + mct.deltarlink(mcpstore) + mct.categorize() + else: + mct = None + isfilled = False + + for rho in all_rhos: + print("its working") + args, kwargs = [rho], {"year": year, "run": rnum, "mctool": mct} + + boolVal = ntuple.fillPrt(*args, **kwargs, check=True) + print("I find bool is {0}".format(boolVal)) + if ntuple.fillPrt(*args, **kwargs, check=True): + ntuple.fillPrt(*args, **kwargs) + isfilled = True + print("isfilled has been set to {0}".format(isfilled)) + + if len(all_rhos) > 0 and isfilled: + print("conditions for filling tuple met") + ntuple.fill() + ntuple.clear() diff --git a/bu2kdarkscalar_darkscalar2hh/inputData.py b/bu2kdarkscalar_darkscalar2hh/inputData.py new file mode 100644 index 0000000000..dcf6ea0974 --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/inputData.py @@ -0,0 +1,14 @@ +from GaudiConf import IOHelper + +''' +IOHelper("ROOT").inputFiles(["/home/exw330/data/00092412_00000138_1.leptonic.mdst"]) +from Configurables import DaVinci +DaVinci().DataType = "2018" +DaVinci().InputType = "MDST" + +''' +# Monte Carlo +IOHelper("ROOT").inputFiles(["/home/exw330/MC/00217278_00000014_1.AllStreams.dst"]) +from Configurables import DaVinci +DaVinci().InputType = 'DST' +DaVinci().DataType = '2018' diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py new file mode 100644 index 0000000000..6def5a0301 --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -0,0 +1,135 @@ +"""Create Ntuple.""" +import argparse +import importlib.machinery +import importlib.util +from pathlib import Path +import shutil +from typing import Union + +from Configurables import DaVinci, ToolSvc, TriggerTisTos +import GaudiPython +import ROOT + +#when running locally you need to uncomment the following lines +#but if you are running on the grid, you need to comment them +import sys +sys.path.append("../") + + +from bu2kdarkscalar_darkscalar2hh.Ntuple import fill_ntuple, Ntuple + + +# ================================================================================= +# The next lines allow configuration to be done in separate files in a gaudirun-like way +# This is needed for AnalysisProductions, but also helps us. +# You run with 'python job.py MyConfig.py' + +def parse_args() -> "tuple[Union[str, None], bool]": + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description=__file__.__doc__, + ) + parser.add_argument( + "options_paths", + nargs="+", + type=Path, + help="Additional options files to load before starting Gaudi Python", + ) + args = parser.parse_args() + + for options_path in args.options_paths: + print("Adding options file:", options_path) + # execute module + # https://stackoverflow.com/a/19011259/4655426 + loader = importlib.machinery.SourceFileLoader( + "head_particle", str(options_path.resolve()) + ) + spec = importlib.util.spec_from_loader(loader.name, loader) + assert spec is not None + mod = importlib.util.module_from_spec(spec) + loader.exec_module(mod) + + + + + return + + +# In order to use Run 1+2 GaudiPython with analysis productions the arguments +# passed to the script need to be executed to set up input data and other state + +parse_args() +# ================================================================================= +# Set up material tool +print("About to compile velo material tool") +ROOT.gErrorIgnoreLevel = ROOT.kError +this_dir = Path(__file__).parent +if ROOT.gROOT.LoadMacro(str(this_dir / "velo.C+")) < 0: + raise RuntimeError("Failed to load velo material tool.") +print("Compiled tool") + +# ================================================================================= +try: + # use ProdConf if available, as with an AnalysisProduction + from ProdConf import ProdConf + + NOfEvents = ProdConf().NOfEvents + # The script also needs to inspect ProdConf to determine the output filename + output_filename = f"{ProdConf().OutputFilePrefix}.{ProdConf().OutputFileTypes[0]}" + # FIXME: This shouldn't be needed but is, for AnalysisProductions to run + shutil.copy(this_dir / "pars.root", Path.cwd()) +except ModuleNotFoundError: + NOfEvents = DaVinci().EvtMax + output_filename = DaVinci().TupleFile + +# FIXME: Turn off lumi tuple or it overwrites ntuple. Cleverer solution possible +DaVinci().Lumi = False + +# ================================================================================= +# Configuring DiEMaker-based particle reconstruction +#candloc_Brem, candloc_noBrem = diemaker(combinatorial_fakePhotons) +#print("candloc_Brem is {0} and candloc_noBrem is {1}".format(candloc_Brem, candloc_noBrem)) + +candloc_KK_Brem = "/Event/Leptonic/Phys/B2KX2KKDarkBosonLine/Particles" +candloc_KK_noBrem = "/Event/Leptonic/Phys/B2KX2KKDarkBosonLine/Particles" + +candloc_PiPi_Brem = "/Event/Leptonic/Phys/B2KX2PiPiDarkBosonLine/Particles" +candloc_PiPi_noBrem = "/Event/Leptonic/Phys/B2KX2PiPiDarkBosonLine/Particles" + +# ================================================================================= +# TisTos configuration. +for stage in ("Hlt1", "Hlt2", "Strip/Phys"): + ToolSvc().addTool(TriggerTisTos, stage + "TriggerTisTos") + tool = getattr(ToolSvc(), stage + "TriggerTisTos") + tool.HltDecReportsLocation = f"/Event/{stage}/DecReports" + tool.HltSelReportsLocation = f"/Event/{stage}/SelReports" + +# ================================================================================= +# Run. +gaudi = GaudiPython.AppMgr() +tes = gaudi.evtsvc() +ntuple = Ntuple(gaudi, output_filename) +# Setting up tools for MC BG categorizing +genTool = ( + gaudi.toolsvc().create( + "DaVinciSmartAssociator", interface="IParticle2MCWeightedAssociator" + ) + if DaVinci().Simulation + else None +) + +event = 0 +while (NOfEvents == -1) or (event < NOfEvents): + gaudi.run(1) + if not bool(tes["/Event"]): + break + event += 1 + #print("found evt") + + fill_ntuple(ntuple, tes, candloc_KK_Brem, candloc_KK_noBrem, genTool) + fill_ntuple(ntuple, tes, candloc_PiPi_Brem, candloc_PiPi_noBrem, genTool) + + +# Close. +ntuple.close(event) -- GitLab From 45d4103ef7d9f8f0e5341b584afbfc86567edb8f Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 15 Jul 2024 15:21:43 +0100 Subject: [PATCH 06/70] editing to work for mc --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 146 +++++++++------------- bu2kdarkscalar_darkscalar2hh/dtf.py | 108 ++++++++++++++++ bu2kdarkscalar_darkscalar2hh/info.yaml | 15 ++- bu2kdarkscalar_darkscalar2hh/inputData.py | 1 + bu2kdarkscalar_darkscalar2hh/job.py | 16 +-- 5 files changed, 181 insertions(+), 105 deletions(-) create mode 100644 bu2kdarkscalar_darkscalar2hh/dtf.py diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index cbebf68a4e..77d38e36f8 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -11,9 +11,9 @@ import GaudiPython from LoKiArrayFunctors.decorators import AMAXDOCA import ROOT -from dtf import dtf +from dtf import dtf #dtf: decay tree fitter import MCEventTools -from ReconstructionTools import BremOverlapChecker, SelectRhos +#from ReconstructionTools import BremOverlapChecker, SelectRhos class Ntuple: @@ -55,8 +55,8 @@ class Ntuple: # Triggers. self.tos: "list[list[str]]" = [ - ["L0Electron.*"], - ["Hlt1.*Electron.*", "Hlt1.*TrackMVA.*"], + ["L0Hadron.*"], + ["Hlt1.*Hadron.*", "Hlt1.*TrackMVA.*"], ["Hlt2ExoticaPi0ToDiEGamma", "Hlt2ExoticaEtaToDiEGamma"], [], ] @@ -111,11 +111,6 @@ class Ntuple: "fd_chi2", "vt_tip", "vt_d", - "withBrem", - "BremMatched", - "photonBremOverlap", - "nBremAdded_em", - "nBremAdded_ep", "etrackOL", ] + [ @@ -124,15 +119,15 @@ class Ntuple: for x in ["m", "dm", "chi2"] + vrsMom + vrsVrt - + [f"die_{y}" for y in ["m"] + vrsMom] + + [f"dih_{y}" for y in ["m"] + vrsMom] ] - + [f"die_{x}" for x in ["m"] + vrsMom] + + [f"dih_{x}" for x in ["m"] + vrsMom] ) vrsMC = [] if DaVinci().Simulation: - dps = ["em", "ep", "g"] - params = ["mpid", "orvrt", "morvrt", "mmpid", "mmorvrt"] + dps = ["hm", "hp", "K"] + params = ["mpid", "orvrt", "morvrt", "mmpid", "mmorvrt"] # mpid: mother pid, orvrt: origin vertex, morvrt: mother origin vertex, mmpid: mother mother pid, mmorvrt: mother mother origin vertex for dp in dps: vrsMC += [dp + "_lpid"] vrsMC += [dp + "_l" + param for param in vrsMom] @@ -315,29 +310,29 @@ class Ntuple: self.fill(f"{pre}_dm", dm) ########################################################################### - def _filldieMomMass(self, pre: "str", mom): + def _filldihMomMass(self, pre: "str", mom): """Fill momentum and mass for a particle.""" if mom is None: - self.fill(f"{pre}_die_px", -1) - self.fill(f"{pre}_die_py", -1) - self.fill(f"{pre}_die_pz", -1) - self.fill(f"{pre}_die_e", -1) - self.fill(f"{pre}_die_m", -1) + self.fill(f"{pre}_dih_px", -1) + self.fill(f"{pre}_dih_py", -1) + self.fill(f"{pre}_dih_pz", -1) + self.fill(f"{pre}_dih_e", -1) + self.fill(f"{pre}_dih_m", -1) else: - self.fill(f"{pre}_die_px", mom.Px()) - self.fill(f"{pre}_die_py", mom.Py()) - self.fill(f"{pre}_die_pz", mom.Pz()) - self.fill(f"{pre}_die_e", mom.E()) - self.fill(f"{pre}_die_m", mom.M()) + self.fill(f"{pre}_dih_px", mom.Px()) + self.fill(f"{pre}_dih_py", mom.Py()) + self.fill(f"{pre}_dih_pz", mom.Pz()) + self.fill(f"{pre}_dih_e", mom.E()) + self.fill(f"{pre}_dih_m", mom.M()) ########################################################################### def _fillgenMomPID(self, pre: "str", mcp): """Fill generator level MC momentum and pid for a particle.""" - if mcp: - if mcp.pid == 11: - dptype = "em" - elif mcp.pid == -11: - dptype = "ep" + if mcp: # mcp: monte carlo particle + if mcp.pid == -321 or mcp.pid == -211: + dptype = "hm" # dptype: daughter particle type + elif mcp.pid == +321 or mcp.pid == +211: + dptype = "hp" elif mcp.pid == 22: dptype = "g" else: @@ -354,7 +349,7 @@ class Ntuple: self.fill(f"{pre}_{dptype}_le", mom.E()) self.fill(f"{pre}_{dptype}_lpid", lpid) else: - for dptype in ["em", "ep", "g"]: + for dptype in ["hm", "hp", "g"]: self.fill(f"{pre}_{dptype}_lpx", -1) self.fill(f"{pre}_{dptype}_lpy", -1) self.fill(f"{pre}_{dptype}_lpz", -1) @@ -365,37 +360,37 @@ class Ntuple: def _filltrkOL(self, pre: "str", prt): """Find number of shared track lhcbIDs between e- and e+. - Accessed through dielectron candidate. + Accessed through dihadron candidate. """ - if prt.particleID().pid() == 310: + if prt.particleID().pid() == 310: #310 is K short nol = 0 - em = ep = None + hm = hp = None for i_dp in range(len(prt.daughtersVector())): dp = prt.daughtersVector()[i_dp] #if dp.particleID().pid() == -321: if dp.particleID().pid() == -321 or dp.particleID().pid() == -211: - assert em is None - em = dp + assert hm is None + hm = dp #elif dp.particleID().pid() == 321: elif dp.particleID().pid() == 321 or dp.particleID().pid() == 211: - assert ep is None - ep = dp - assert em is not None and ep is not None, prt - for i_emid in range(len(em.proto().track().lhcbIDs())): - emid = em.proto().track().lhcbIDs()[i_emid] - for i_epid in range(len(ep.proto().track().lhcbIDs())): - epid = ep.proto().track().lhcbIDs()[i_epid] - if emid == epid: + assert hp is None + hp = dp + assert hm is not None and hp is not None, prt + for i_hmid in range(len(hm.proto().track().lhcbIDs())): + hmid = hm.proto().track().lhcbIDs()[i_hmid] + for i_hpid in range(len(hp.proto().track().lhcbIDs())): + hpid = hp.proto().track().lhcbIDs()[i_hpid] + if hmid == hpid: nol += 1 - if (nol * 1.0) / len(em.proto().track().lhcbIDs()) > (nol * 1.0) / len( - ep.proto().track().lhcbIDs() + if (nol * 1.0) / len(hm.proto().track().lhcbIDs()) > (nol * 1.0) / len( + hp.proto().track().lhcbIDs() ): - olperc = (nol * 1.0) / len(em.proto().track().lhcbIDs()) + olperc = (nol * 1.0) / len(hm.proto().track().lhcbIDs()) else: - olperc = (nol * 1.0) / len(ep.proto().track().lhcbIDs()) + olperc = (nol * 1.0) / len(hp.proto().track().lhcbIDs()) else: olperc = -1 - self.fill(f"{pre}_etrackOL", olperc) + self.fill(f"{pre}_htrackOL", olperc) ########################################################################### def _fillMat( @@ -450,25 +445,6 @@ class Ntuple: self.fill(f"{pre}_vt_d", -1) self.fill(f"{pre}_vt_tip", -1) - ########################################################################### - def _fillBrem(self, pre: "str", prt: "Any"): - """Fill the Brem information for a particle.""" - if prt.particleID().pid() == 113: - # pion/eta - assert prt.hasInfo(1) and prt.hasInfo(2) - extraInfo = prt.extraInfo() - self.fill(f"{pre}_withBrem", int(extraInfo[1])) - self.fill(f"{pre}_BremMatched", int(extraInfo[2])) - else: - # not pion/eta - assert not (prt.hasInfo(1) or prt.hasInfo(2)) - self.fill(f"{pre}_withBrem", -1) - self.fill(f"{pre}_BremMatched", -1) - BOC = BremOverlapChecker(prt) - self.fill(f"{pre}_photonBremOverlap", BOC.overlapFound()) - self.fill(f"{pre}_nBremAdded_em", BOC.nBremAdded("em")) - self.fill(f"{pre}_nBremAdded_ep", BOC.nBremAdded("ep")) - ########################################################################### def _fillTrg(self, pre: "str", prt): """Fill the trigger lines for a particle.""" @@ -523,14 +499,13 @@ class Ntuple: pdtf = dtf(prt, pvr, True) ddtf = dtf(prt, pvr, False) if prt.particleID().pid() in (-521, 521): - # pion/eta pdtf.fit() ddtf.fit() - assert pdtf.dielectron == ddtf.dielectron - assert pdtf.dielectron is not None - dtrp = pdtf.dielectron.momentum() + assert pdtf.dihadron == ddtf.dihadron + assert pdtf.dihadron is not None + dtrp = pdtf.dihadron.momentum() elif prt.particleID().pid() == 310: - # dielectron + # dihadron candidate dtrp = prt.momentum() else: raise ValueError( @@ -551,8 +526,8 @@ class Ntuple: # because fillPrt is designed to automatically determine the branch names self._filldieMomMass(pre, dtrp) - self._filldieMomMass(f"{pre}_pdtf", pdtf.get_die_momentum()) - self._filldieMomMass(f"{pre}_ddtf", ddtf.get_die_momentum()) + self._filldieMomMass(f"{pre}_pdtf", pdtf.get_dih_momentum()) + self._filldieMomMass(f"{pre}_ddtf", ddtf.get_dih_momentum()) self._filltrkOL(pre, prt) # MC BG type @@ -869,8 +844,8 @@ class Ntuple: def fill_ntuple( ntuple: "Ntuple", tes, - candloc_Brem: "str", - candloc_noBrem: "str", + candloc: "str", + #candloc_noBrem: "str", #head: "Union[str, None]", genTool, ): @@ -882,7 +857,7 @@ def fill_ntuple( particles = tes[candloc] len(particles) size = len(particles) - #print("found {0} particles".format(size)) + print("found {0} particles".format(size)) except: particles = [] return particles @@ -899,17 +874,10 @@ def fill_ntuple( ) # Fill the particles. - all_rhos = get_particles(candloc_Brem) - + all_Bs = get_particles(candloc) - - #SelectRhos( - # get_particles(candloc_Brem), - # get_particles(candloc_noBrem), - # ntuple.combinatorial_fakePhotons, - # ) if DaVinci().Simulation: - mct = MCEventTools.MCEvent(all_rhos, genTool) + mct = MCEventTools.MCEvent(all_Bs, genTool) # mct: monte carlo tool mct.linkpars() mcpstore = get_particles( "AllStreams/MC/Particles" @@ -922,9 +890,9 @@ def fill_ntuple( mct = None isfilled = False - for rho in all_rhos: + for B in all_Bs: print("its working") - args, kwargs = [rho], {"year": year, "run": rnum, "mctool": mct} + args, kwargs = [B], {"year": year, "run": rnum, "mctool": mct} boolVal = ntuple.fillPrt(*args, **kwargs, check=True) print("I find bool is {0}".format(boolVal)) @@ -933,7 +901,7 @@ def fill_ntuple( isfilled = True print("isfilled has been set to {0}".format(isfilled)) - if len(all_rhos) > 0 and isfilled: + if len(all_Bs) > 0 and isfilled: print("conditions for filling tuple met") ntuple.fill() ntuple.clear() diff --git a/bu2kdarkscalar_darkscalar2hh/dtf.py b/bu2kdarkscalar_darkscalar2hh/dtf.py new file mode 100644 index 0000000000..826477d4e8 --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/dtf.py @@ -0,0 +1,108 @@ +"""Defines dtf class for B -> K h+ h- ( h is K or pi) decays.""" +from typing import Any + +import GaudiPython + + +class dtf: + """Create a DecayTreeFitter and extract some information.""" + + def __init__(self, particle: "Any", relatedPV: "Any", prompt: "bool"): + self.particle = particle + self.relatedPV = relatedPV + self.prompt = prompt + + self._dtf = None # the DecayTreeFitter (must be stored for persistency) + self._dtf_mom = None # the fitted B momentum + self._dtf_cov = None # the covariance matrix of the fitted position + self._dtf_pos = None # the fitted vertex position + self._dtf_chi2 = None # the chi2 of the fit + self._dtf_dtrp = None # the fitted dihadron momentum + + self.dihadron = None # the dihadron + + def fit(self): + """Fit the B and extract some information.""" + self._get_daughter() + self._set_prompt() + self._get_dtf() + self._reset_prompt() + params = [ + self._dtf_mom, + self._dtf_cov, + self._dtf_pos, + self._dtf_chi2, + self._dtf_dtrp, + ] + truth = [x is None for x in params] + assert all(truth) or not any(truth), truth + + def _get_daughter(self): + """Find the daughter whose PID == 310. + + Sets `self.dihadron`. + """ + for i_fdtr in range(len(self.particle.daughtersVector())): + _fdtr = self.particle.daughtersVector()[i_fdtr] + if _fdtr.particleID().pid() == 310: + assert self.dihadron is None + self.dihadron = _fdtr + assert self.dihadron is not None, self.particle + + def _set_prompt(self): + """Change the dihadron PID to 10111 if `self.prompt` is True.""" + if self.prompt: + assert self.dihadron is not None + assert self.dihadron.particleID().pid() == 310 + self.dihadron.setParticleID(GaudiPython.gbl.LHCb.ParticleID(10111)) + assert self.dihadron.particleID().pid() == 10111 + + def _get_dtf(self): + """Create the DecayTreeFitter and extract some information. + + Only fills information if `bool(self.relatedPV)` + and the fit is successful + and the dihadron mass error from the fit is not 0. + """ + if self.relatedPV: + self._dtf = GaudiPython.gbl.DecayTreeFitter.Fitter( + self.particle, self.relatedPV + ) + self._dtf.fit() + if self._dtf.status() == self._dtf.Success: + dtf_params = self._dtf.fitParams(self.particle) + dtf_momentum = dtf_params.momentum() + if dtf_momentum.m().error() != 0: + self._dtf_mom = dtf_momentum + self._dtf_chi2 = self._dtf.chiSquare() + self._dtf_cov = dtf_params.posCovMatrix() + self._dtf_pos = dtf_params.position() + self._dtf_dtrp = self._dtf.getFitted(self.dihadron).momentum() + + def _reset_prompt(self): + """Ensure the dihadron's PID is 310.""" + assert self.dihadron is not None + if self.prompt: + assert self.dihadron.particleID().pid() == 10111 + self.dihadron.setParticleID(GaudiPython.gbl.LHCb.ParticleID(310)) + assert self.dihadron.particleID().pid() == 310 + + def get_momentum(self): + """Return the fitted B momentum.""" + return self._dtf_mom + + def get_position_covariance(self): + """Return the covariance matrix of the fitted position.""" + return self._dtf_cov + + def get_position(self): + """Return the fitted vertex position.""" + return self._dtf_pos + + def get_chi2(self): + """Return the chi2 of the fit.""" + return self._dtf_chi2 + + def get_dih_momentum(self): + """Return the fitted dihadron momentum.""" + return self._dtf_dtrp diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 9c5aaa8adf..cee77310d1 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -14,16 +14,19 @@ Bu2KdarkScalar_darkScalar2hh_2018_Mag{{polarity}}_job: - python files: - job.py - - photonSource_default.py input: - bk_query: /LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST - #/MC/2018/Beam6500GeV-2018-MagDown-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST - #/LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST + bk_query: /LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p3/90000000/LEPTONIC.MDST + # For MC + # /MC/2018/Beam6500GeV-2018-MagDown-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST + # For data + # /LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST output: bu2kdarkscalar_darkscalar2hh.ROOT - #B2KKK.ROOT - #bu2kdarkscalar_darkscalar2hh.ROOT + # For MC + # B2KKK.ROOT + # For data + # bu2kdarkscalar_darkscalar2hh.ROOT {%- endfor %} \ No newline at end of file diff --git a/bu2kdarkscalar_darkscalar2hh/inputData.py b/bu2kdarkscalar_darkscalar2hh/inputData.py index dcf6ea0974..22ef3691c8 100644 --- a/bu2kdarkscalar_darkscalar2hh/inputData.py +++ b/bu2kdarkscalar_darkscalar2hh/inputData.py @@ -1,6 +1,7 @@ from GaudiConf import IOHelper ''' +# Real Data IOHelper("ROOT").inputFiles(["/home/exw330/data/00092412_00000138_1.leptonic.mdst"]) from Configurables import DaVinci DaVinci().DataType = "2018" diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 6def5a0301..891ccc6b81 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -87,15 +87,11 @@ except ModuleNotFoundError: DaVinci().Lumi = False # ================================================================================= -# Configuring DiEMaker-based particle reconstruction -#candloc_Brem, candloc_noBrem = diemaker(combinatorial_fakePhotons) -#print("candloc_Brem is {0} and candloc_noBrem is {1}".format(candloc_Brem, candloc_noBrem)) -candloc_KK_Brem = "/Event/Leptonic/Phys/B2KX2KKDarkBosonLine/Particles" -candloc_KK_noBrem = "/Event/Leptonic/Phys/B2KX2KKDarkBosonLine/Particles" +#candidate locations +candloc_KK = "/Event/Leptonic/Phys/B2KX2KKDarkBosonLine/Particles" +candloc_PiPi = "/Event/Leptonic/Phys/B2KX2PiPiDarkBosonLine/Particles" -candloc_PiPi_Brem = "/Event/Leptonic/Phys/B2KX2PiPiDarkBosonLine/Particles" -candloc_PiPi_noBrem = "/Event/Leptonic/Phys/B2KX2PiPiDarkBosonLine/Particles" # ================================================================================= # TisTos configuration. @@ -125,10 +121,10 @@ while (NOfEvents == -1) or (event < NOfEvents): if not bool(tes["/Event"]): break event += 1 - #print("found evt") + print("found evt") - fill_ntuple(ntuple, tes, candloc_KK_Brem, candloc_KK_noBrem, genTool) - fill_ntuple(ntuple, tes, candloc_PiPi_Brem, candloc_PiPi_noBrem, genTool) + fill_ntuple(ntuple, tes, candloc_KK, genTool) + fill_ntuple(ntuple, tes, candloc_PiPi, genTool) # Close. -- GitLab From c52c9ac531aaf8e5254e993f57788fe44b678937 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 16 Jul 2024 12:42:31 +0100 Subject: [PATCH 07/70] adapting for mc --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 168 +++++++++---------- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 24 ++- bu2kdarkscalar_darkscalar2hh/job.py | 3 +- 3 files changed, 104 insertions(+), 91 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index e985d51e09..7cf5bd0cdd 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -163,8 +163,8 @@ class MCEvent: """ def __init__(self, all_Bparticles, mctool): """ - Initializes MCEvent class. Creates list of rec level Bparticles, adds associated - daughter particles to Bparticles daughter list. + Initializes MCEvent class. Creates list of rec level B particles, adds associated + daughter particles to all_Bparticles daughter list. INPUTS ------ @@ -177,7 +177,7 @@ class MCEvent: of MCEvent [bool] links: flag for whether the linkpars method has been run on this instance of MCEvent [bool] - Bparticles : list of combined Bparticles with daughter particles in daughter list [list(MCPart)] + Bparticles : list of combined all_Bparticles with daughter particles in daughter list [list(MCPart)] """ self.hcuts = False self.links = False @@ -186,7 +186,7 @@ class MCEvent: self.mctool = mctool self.Bparticles = [] self.dihquery = [] - for Bparticle in all_Bparticles: # loop over all the B+/B-s stored in the TES + for B in all_Bparticles: # loop over all the B+/B-s stored in the TES dps = [0,0] dpsv = None svcoor = None @@ -203,7 +203,7 @@ class MCEvent: elif hadron.pid == -321: hadrons[1] = hadron svcoor = [dpsv.position().X(),dpsv.position().Y(),dpsv.position().Z()] dps[0] = MCPart(dp,dl=hadrons,sv=svcoor) - B = MCPart(Bparticle,dl=dps) + B = MCPart(B,dl=dps) self.Bparticles += [B] def linkpars(self): @@ -271,7 +271,7 @@ class MCEvent: if rel_hadron_plus.originVertex().type() != 1: try: rel_hadron_plus.mother().originVertex().type() - particle.dl[0].dl[1].lpar.update(newmother=MCPart(rel_hadron_plus.mother(),orvrt=rel_hadron_plus.mother().originVertex().type())) + Bparticle.dl[0].dl[1].lpar.update(newmother=MCPart(rel_hadron_plus.mother(),orvrt=rel_hadron_plus.mother().originVertex().type())) if rel_hadron_plus.mother().originVertex().type() != 1: try: rel_hadron_plus.mother().mother().originVertex().type() @@ -303,9 +303,9 @@ class MCEvent: if Bparticle.dl[0].dl[1].lpar.mother: if Bparticle.dl[0].dl[1].lpar.mother.mother: - hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par, B.dl[0].dl[1].lpar.mother.mother.par] + hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,Bparticle.dl[0].dl[1].lpar.mother.par, Bparticle.dl[0].dl[1].lpar.mother.mother.par] else: - hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par] + hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,Bparticle.dl[0].dl[1].lpar.mother.par] else: hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par] sharelin = [] @@ -320,24 +320,24 @@ class MCEvent: else: if hadron_minus_lin[i] == Klin[k]: lin += (i+1)*100 + (k+1); sharelin += [lin] if hadron_plus_lin[j] == Klin[k]: lin = 0; lin += (j+1)*10 + (k+1); sharelin += [lin] - B.update(newlineage=sharelin) + Bparticle.update(newlineage=sharelin) - for prt in B.dl[1].lpar.par.originVertex().products(): - B.dl[1].lpar.update(newsp=prt) - if B.dl[1].lpar.mother: - for prt in B.dl[1].lpar.mother.par.originVertex().products(): - B.dl[1].lpar.mother.update(newsp=prt) + for prt in Bparticle.dl[1].lpar.par.originVertex().products(): + Bparticle.dl[1].lpar.update(newsp=prt) + if Bparticle.dl[1].lpar.mother: + for prt in Bparticle.dl[1].lpar.mother.par.originVertex().products(): + Bparticle.dl[1].lpar.mother.update(newsp=prt) - for prt in B.dl[0].dl[0].lpar.par.originVertex().products(): + for prt in Bparticle.dl[0].dl[0].lpar.par.originVertex().products(): Bparticle.dl[0].dl[0].lpar.update(newsp=prt) if Bparticle.dl[0].dl[0].lpar.mother: - for prt in B.dl[0].dl[0].lpar.mother.par.originVertex().products(): + for prt in Bparticle.dl[0].dl[0].lpar.mother.par.originVertex().products(): Bparticle.dl[0].dl[0].lpar.mother.update(newsp=prt) for prt in Bparticle.dl[0].dl[1].lpar.par.originVertex().products(): Bparticle.dl[0].dl[1].lpar.update(newsp=prt) if Bparticle.dl[0].dl[1].lpar.mother: - for prt in B.dl[0].dl[1].lpar.mother.par.originVertex().products(): + for prt in Bparticle.dl[0].dl[1].lpar.mother.par.originVertex().products(): Bparticle.dl[0].dl[1].lpar.mother.update(newsp=prt) elif self.allreglinked: self.allreglinked = False self.links = True @@ -373,30 +373,30 @@ class MCEvent: except: recp.lpar.update(newmother=None) - if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: - B.update(newisdrmatched=True) - if B.dl[1].lpar.pid == B.dl[1].pid and B.dl[0].dl[0].lpar.pid == B.dl[0].dl[0].pid and B.dl[0].dl[1].lpar.pid == B.dl[0].dl[1].pid: B.update(newcorlinked=True) + if Bparticle.dl[1].lpar and Bparticle.dl[0].dl[0].lpar and Bparticle.dl[0].dl[1].lpar: + Bparticle.update(newisdrmatched=True) + if Bparticle.dl[1].lpar.pid == Bparticle.dl[1].pid and Bparticle.dl[0].dl[0].lpar.pid == Bparticle.dl[0].dl[0].pid and Bparticle.dl[0].dl[1].lpar.pid == Bparticle.dl[0].dl[1].pid: Bparticle.update(newcorlinked=True) - if B.dl[1].lpar.mother: - if B.dl[1].lpar.mother.mother: - Klin = [B.dl[1].lpar.par,B.dl[1].lpar.mother.par, B.dl[1].lpar.mother.mother.par] + if Bparticle.dl[1].lpar.mother: + if Bparticle.dl[1].lpar.mother.mother: + Klin = [Bparticle.dl[1].lpar.par,Bparticle.dl[1].lpar.mother.par, Bparticle.dl[1].lpar.mother.mother.par] else: - Klin = [B.dl[1].lpar.par,B.dl[1].lpar.mother.par] - else: Klin = [B.dl[1].lpar.par] + Klin = [Bparticle.dl[1].lpar.par,Bparticle.dl[1].lpar.mother.par] + else: Klin = [Bparticle.dl[1].lpar.par] - if B.dl[0].dl[0].lpar.mother: - if B.dl[0].dl[0].lpar.mother.mother: - hadron_minus_lin = [B.dl[0].dl[0].lpar.par,B.dl[0].dl[0].lpar.mother.par, B.dl[0].dl[0].lpar.mother.mother.par] + if Bparticle.dl[0].dl[0].lpar.mother: + if Bparticle.dl[0].dl[0].lpar.mother.mother: + hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par,Bparticle.dl[0].dl[0].lpar.mother.par, Bparticle.dl[0].dl[0].lpar.mother.mother.par] else: - hadron_minus_lin = [B.dl[0].dl[0].lpar.par,B.dl[0].dl[0].lpar.mother.par] - else: hadron_minus_lin = [B.dl[0].dl[0].lpar.par] + hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par,Bparticle.dl[0].dl[0].lpar.mother.par] + else: hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par] - if B.dl[0].dl[1].lpar.mother: - if B.dl[0].dl[1].lpar.mother.mother: - hadron_plus_lin = [B.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par, B.dl[0].dl[1].lpar.mother.mother.par] + if Bparticle.dl[0].dl[1].lpar.mother: + if Bparticle.dl[0].dl[1].lpar.mother.mother: + hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,Bparticle.dl[0].dl[1].lpar.mother.par, Bparticle.dl[0].dl[1].lpar.mother.mother.par] else: - hadron_plus_lin = [B.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par] - else: hadron_plus_lin = [B.dl[0].dl[1].lpar.par] + hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,Bparticle.dl[0].dl[1].lpar.mother.par] + else: hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par] sharelin = [] for i in range(len(hadron_minus_lin)): @@ -410,25 +410,25 @@ class MCEvent: else: if hadron_minus_lin[i] == Klin[k]: lin += (i+1)*100 + (k+1); sharelin += [lin] if hadron_plus_lin[j] == Klin[k]: lin = 0; lin += (j+1)*10 + (k+1); sharelin += [lin] - B.update(newlineage=sharelin) - - for prt in B.dl[1].lpar.par.originVertex().products(): - B.dl[1].lpar.update(newsp=prt) - if B.dl[1].lpar.mother: - for prt in B.dl[1].lpar.mother.par.originVertex().products(): - B.dl[1].lpar.mother.update(newsp=prt) - - for prt in B.dl[0].dl[0].lpar.par.originVertex().products(): - B.dl[0].dl[0].lpar.update(newsp=prt) - if B.dl[0].dl[0].lpar.mother: - for prt in B.dl[0].dl[0].lpar.mother.par.originVertex().products(): - B.dl[0].dl[0].lpar.mother.update(newsp=prt) - - for prt in B.dl[0].dl[1].lpar.par.originVertex().products(): - B.dl[0].dl[1].lpar.update(newsp=prt) - if B.dl[0].dl[1].lpar.mother: - for prt in B.dl[0].dl[1].lpar.mother.par.originVertex().products(): - B.dl[0].dl[1].lpar.mother.update(newsp=prt) + Bparticle.update(newlineage=sharelin) + + for prt in Bparticle.dl[1].lpar.par.originVertex().products(): + Bparticle.dl[1].lpar.update(newsp=prt) + if Bparticle.dl[1].lpar.mother: + for prt in Bparticle.dl[1].lpar.mother.par.originVertex().products(): + Bparticle.dl[1].lpar.mother.update(newsp=prt) + + for prt in Bparticle.dl[0].dl[0].lpar.par.originVertex().products(): + Bparticle.dl[0].dl[0].lpar.update(newsp=prt) + if Bparticle.dl[0].dl[0].lpar.mother: + for prt in Bparticle.dl[0].dl[0].lpar.mother.par.originVertex().products(): + Bparticle.dl[0].dl[0].lpar.mother.update(newsp=prt) + + for prt in Bparticle.dl[0].dl[1].lpar.par.originVertex().products(): + Bparticle.dl[0].dl[1].lpar.update(newsp=prt) + if Bparticle.dl[0].dl[1].lpar.mother: + for prt in Bparticle.dl[0].dl[1].lpar.mother.par.originVertex().products(): + Bparticle.dl[0].dl[1].lpar.mother.update(newsp=prt) def corlin(self, B): """ @@ -464,17 +464,17 @@ class MCEvent: if not self.links: self.linkpars() for Bparticle in self.Bparticles: - if self.corlin(B): + if self.corlin(Bparticle): K = Bparticle.dl[1] hadron_minus = Bparticle.dl[0].dl[0] hadron_plus = Bparticle.dl[0].dl[1] try: fillKpid = K.lpar.mother.pid except: fillKpid = 0 - if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(B.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle + if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(Bparticle.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle #if K is K+ this must be a B+, if K is K- this must be a B- - if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: B.update(newbgtype=0) - if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: B.update(newbgtype=0) + if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: Bparticle.update(newbgtype=0) + if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: Bparticle.update(newbgtype=0) for dp in Bparticle.dl: @@ -496,24 +496,24 @@ class MCEvent: dectype: decay type of the input particle [int] """ dectype = -99 - B = None + Bparticle = None if ID(part) == 310: dectype = ID(part) elif part in self.Bstore: - for Bparticle in self.Bparticles: + for B in self.Bparticles: if part == B.par: - B = B + Bparticle = B break - if B != None: - K = B.dl[1] - hadron_minus = B.dl[0].dl[0] - hadron_plus = B.dl[0].dl[1] - - fill_B_dectype = B.dectype # fill B decay type - fill_B_odectype = B.odectype # fill B decay type - try: hadron_minus_morvrt = em.lpar.mother.orvrt + if Bparticle != None: + K = Bparticle.dl[1] + hadron_minus = Bparticle.dl[0].dl[0] + hadron_plus = Bparticle.dl[0].dl[1] + + fill_B_dectype = Bparticle.dectype # fill B decay type + fill_B_odectype = Bparticle.odectype # fill B decay type + try: hadron_minus_morvrt = hadron_minus.lpar.mother.orvrt except: hadron_minus_morvrt = 99 - try: hadron_plus_morvrt = _hadron_plus.lpar.mother.orvrt + try: hadron_plus_morvrt = hadron_plus.lpar.mother.orvrt except: hadron_plus_morvrt = 99 K_orvrt = K.orvrt @@ -521,7 +521,7 @@ class MCEvent: fill_K_mother_dectype = K.lpar.mother.pid except: fill_K_mother_dectype.pid = 0 - if not B.corlinked or hadron_minus_morvrt == 99 or hadron_plus_morvrt == 99: + if not Bparticle.corlinked or hadron_minus_morvrt == 99 or hadron_plus_morvrt == 99: dectype = 8 elif fill_B_dectype == 0: dectype = 0 @@ -546,16 +546,16 @@ class MCEvent: drmatch: whether particle was delta R matched [int (0: no; 1: yes; 99: error)] """ drmatch = 99 - B = None + Bparticle = None if ID(part) == 310: drmatch = ID(part) elif part in self.Bstore: - for Bparticle in self.Bparticles: - if part == Bparticle.par: - B = Bparticle + for B in self.Bparticles: + if part == B.par: + Bparticle = B break - if B != None: - if B.isdrmatched: drmatch = 1 + if Bparticle != None: + if Bparticle.isdrmatched: drmatch = 1 else: drmatch = 0 else: drmatch = 9 @@ -573,15 +573,15 @@ class MCEvent: OUTPUTS ------- - B: MCPart object of the corresponding B/eta [MCPart] + Bparticle: MCPart object of the corresponding B/eta [MCPart] """ - B = None + Bparticle = None if ID(part) == 310: - B = self.Bparticles[0] + Bparticle = self.Bparticles[0] elif part in self.Bstore: - for Bparticle in self.Bparticles: - if part == Bparticle.par: - B = Bparticle + for B in self.Bparticles: + if part == B.par: + Bparticle = B break - return B + return Bparticle diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 77d38e36f8..6073387e3e 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -525,9 +525,9 @@ class Ntuple: # dtf (dtf_pos) not implemented for these daughters # because fillPrt is designed to automatically determine the branch names - self._filldieMomMass(pre, dtrp) - self._filldieMomMass(f"{pre}_pdtf", pdtf.get_dih_momentum()) - self._filldieMomMass(f"{pre}_ddtf", ddtf.get_dih_momentum()) + self._filldihMomMass(pre, dtrp) + self._filldihMomMass(f"{pre}_pdtf", pdtf.get_dih_momentum()) + self._filldihMomMass(f"{pre}_ddtf", ddtf.get_dih_momentum()) self._filltrkOL(pre, prt) # MC BG type @@ -812,13 +812,12 @@ class Ntuple: # Fill the particle. if pre == "tag": assert year is not None and run is not None, (year, run) - assert prt.particleID().pid() in (-521, 521, 310), prt.particleID().pid() + assert prt.particleID().pid() in (-521, 521, 310), prt.particleID().pid() # B-, B+, dark scalar pvr = self._fillTag(*tagargs, **tagkwargs) assert not isinstance(pvr, bool), pvr elif pre == "trk": assert year is not None and run is not None, (year, run) - #assert prt.particleID().pid() in (-321, 321), prt.particleID().pid() - assert prt.particleID().pid() in (-321, 321, -211, 211), prt.particleID().pid() + assert prt.particleID().pid() in (-321, 321, -211, 211), prt.particleID().pid() # K-, K+, pi-, pi+ pvr = self._fillTrk(pre, prt, org, year, run) elif pre == "cal": assert prt.particleID().pid() == 22, prt.particleID().pid() @@ -855,10 +854,12 @@ def fill_ntuple( def get_particles(candloc): try: particles = tes[candloc] + #print(particles) len(particles) size = len(particles) print("found {0} particles".format(size)) except: + print("no particles found") particles = [] return particles @@ -873,11 +874,18 @@ def fill_ntuple( GaudiPython.gbl.LoKi.L0.DataValue("Spd(Mult)")(tes["Trig/L0/L0DUReport"]) ) + print(candloc) + # Fill the particles. all_Bs = get_particles(candloc) + print(f"length of all bs is {len(all_Bs)}") + + if DaVinci().Simulation: + print("yay its working for mc") mct = MCEventTools.MCEvent(all_Bs, genTool) # mct: monte carlo tool + print(mct) mct.linkpars() mcpstore = get_particles( "AllStreams/MC/Particles" @@ -888,8 +896,12 @@ def fill_ntuple( mct.categorize() else: mct = None + + isfilled = False + print('moving onto for loop') + for B in all_Bs: print("its working") args, kwargs = [B], {"year": year, "run": rnum, "mctool": mct} diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 891ccc6b81..b24f45abd0 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -124,7 +124,8 @@ while (NOfEvents == -1) or (event < NOfEvents): print("found evt") fill_ntuple(ntuple, tes, candloc_KK, genTool) - fill_ntuple(ntuple, tes, candloc_PiPi, genTool) + print("filled ntuple KK") + #fill_ntuple(ntuple, tes, candloc_PiPi, genTool) # Close. -- GitLab From 5273acff6a5504da6b04637ee390c5ec6f6f8315 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 24 Jul 2024 16:22:44 +0100 Subject: [PATCH 08/70] edited to work for mc --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 342 ++++++++++--------- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 187 ++++------ bu2kdarkscalar_darkscalar2hh/README.md | 47 +++ bu2kdarkscalar_darkscalar2hh/hh_KK.py | 5 + bu2kdarkscalar_darkscalar2hh/hh_PiPi.py | 5 + bu2kdarkscalar_darkscalar2hh/info.yaml | 6 +- bu2kdarkscalar_darkscalar2hh/job.py | 42 ++- 7 files changed, 337 insertions(+), 297 deletions(-) create mode 100644 bu2kdarkscalar_darkscalar2hh/README.md create mode 100644 bu2kdarkscalar_darkscalar2hh/hh_KK.py create mode 100644 bu2kdarkscalar_darkscalar2hh/hh_PiPi.py diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 7cf5bd0cdd..f42bd97f38 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -4,7 +4,7 @@ from GaudiConf import IOHelper from Configurables import DaVinci, TriggerTisTos, ToolSvc from ROOT import TLorentzVector as tlv from LoKiMC.decorators import MCPX, MCPY, MCPZ, MCE, MCID, MCABSID, MCPT, MCM, MCETA, MCP, MCPHI -from LoKiPhys.decorators import M, PX, PY, PZ, E, ID, ABSID, PROBNNk, PROBNNghost, IPCHI2, P, PT, CL, ETA, TRGHOSTPROB, PHI +from LoKiPhys.decorators import M, PX, PY, PZ, E, ID, ABSID, PROBNNk, PROBNNpi, PROBNNghost, IPCHI2, P, PT, CL, ETA, TRGHOSTPROB, PHI from array import array from math import sqrt @@ -32,15 +32,16 @@ class MCPart: tos : tos list ipc2 : IPCHI2 parameter lpar : linked particle - corlinked : flag (intended for Bs) for whether all daughter particles have been + corlinked : flag (intended for all_Bs) for whether all daughter particles have been linked to like particles (i.e. pi+ linked to pi+) [bool] - alllinked : flag (intended for Bs) for whether all daughter particles have + alllinked : flag (intended for all_Bs) for whether all daughter particles have been linked to anything [bool] orvrt : origin vertex type for LHCb.MCParticle type particles mother : mother particles for LHCb.MCParticle type particles clink : flag (intended for daughter particles) for whether particle has been linked to a matching particle type [bool] pnnk : particle's PROBNNk + ppnpi : particle's PROBNNpi pnng : particle's PROBNNghost trgp : particle's TRGHOSTPROB dectype : category of decay type (assigned via MCEvent.categorize()) [int] @@ -51,14 +52,14 @@ class MCPart: -1: particle; 2: mother; 3: grandmother; 0: no shared relation (if only two particles share ancestor) sl : sister list (other particles from same origin vertex) cl : particle's confidence level - isdrmatched: flag for if a B candidate has been linked using delta r matching + isdrmatched: flag for if a all_B candidate has been linked using delta r matching pv : coordinates of associated PV [list(float)] sv : coordinates of associated SV [list(float)] odectype : category of other decay type [int] bgtype : background category [int] """ def __init__(self, par, dl=[], tos=[0,0,0], ipc2=None, lpar=None, orvrt=None, - mother=None, clink=None, pnnk=None, pnng=None, trgp=None, dectype=None, + mother=None, clink=None, pnnk=None, pnnpi=None, pnng=None, trgp=None, dectype=None, lineage=None, sl=[], cl=None, isdrmatched=False, pv=[], sv=[], odectype=None, bgtype=None): self.par = par @@ -74,6 +75,7 @@ class MCPart: self.mother = mother self.clink = clink self.pnnk = pnnk + self.pnnpi = pnnpi self.pnng = pnng self.trgp = trgp self.dectype = dectype @@ -86,9 +88,9 @@ class MCPart: self.odectype = odectype self.bgtype = bgtype - def update(self, newdp=None, newdl=None, newl0tos=0, newhlt1etos=0, newhlt1mvatos=0, + def update(self, newdp=None, newdl=None, newl0tos=0, newhlt1htos=0, newhlt1mvatos=0, newtos=[], newipc2=None, newlpar=None, newcorlinked=0, neworvrt=None, - newmother=None, newclink=None, newpnnk=None, newpnng=None, newtrgp=None, + newmother=None, newclink=None, newpnnk=None, newpnnpi=None, newpnng=None, newtrgp=None, newdectype=None, newlineage=None, newsp=None, newsl=None, newalllinked=0, newcl=None, newisdrmatched=None, newpv=None, newsv=None, newodectype=None, newbgtype=None): @@ -100,7 +102,7 @@ class MCPart: newdp : adds single daughter particle to existing daughter list [MCPart] newdl : overwrites existing daughter list with newdl [list(MCPart)] newl0tos : updates L0 TOS [bool] - newhlt1etos : updates HLT1Electron TOS [bool] + newhlt1htos : updates HLT1Hlectron TOS [bool] newhlt1mvatos: updates HLT1TrackMVA TOS [bool] newtos : overwrites existing tos list with newtos [list(bool)] newipc2 : updates ipc2 (float) @@ -110,9 +112,10 @@ class MCPart: newmother : updates associated mother particle newclink : updates clink flag [bool] newpnnk : updates PROBNNk + newpnnpi : updates PROBNNpi newpnng : updates PROBNNghost newtrgp : updates TRGHOSTPROB - newdectype : updates the B decay type + newdectype : updates the all_B decay type newlineage : updates the shared ancestor list newsp : adds single sister particle to existing list [MCPart] newsl : overwrites existing sister list with newsl [list(MCPart)] @@ -124,7 +127,7 @@ class MCPart: else: self.dl += [MCPart(newdp)] if newdl != None: self.dl = newdl if newl0tos != 0: self.tos[0] = newl0tos - if newhlt1etos != 0: self.tos[1] = newhlt1etos + if newhlt1htos != 0: self.tos[1] = newhlt1htos if newhlt1mvatos != 0: self.tos[2] = newhlt1mvatos #if len(newtos) == 2: self.tos = newtos if len(newtos) == 3: self.tos = newtos @@ -140,6 +143,7 @@ class MCPart: else: self.mother = MCPart(newmother) if newclink != None: self.clink = newclink if newpnnk != None: self.pnnk = newpnnk + if newpnnpi != None: self.pnnpi = newpnnpi if newpnng != None: self.pnng = newpnng if newtrgp != None: self.trgp = newtrgp if newdectype != None: self.dectype = newdectype @@ -161,14 +165,14 @@ class MCEvent: """ Class for processing MC events. """ - def __init__(self, all_Bparticles, mctool): + def __init__(self, all_Bs, mctool): """ - Initializes MCEvent class. Creates list of rec level B particles, adds associated - daughter particles to all_Bparticles daughter list. + Initializes MCEvent class. Creates list of rec level all_Bs, adds associated + daughter particles to all_Bs daughter list. INPUTS ------ - all_Bparticles : B+/B-s from the TES + all_Bs : B+/B-s from the TES mctool : IParticle2MCWeightedAssociator instance to be used INTERNAL MEMBERS @@ -177,136 +181,145 @@ class MCEvent: of MCEvent [bool] links: flag for whether the linkpars method has been run on this instance of MCEvent [bool] - Bparticles : list of combined all_Bparticles with daughter particles in daughter list [list(MCPart)] + Bs : list of combined all_Bs with daughter particles in daughter list [list(MCPart)] """ self.hcuts = False self.links = False self.iscategorized = False - self.Bstore = all_Bparticles + self.Bstore = all_Bs self.mctool = mctool - self.Bparticles = [] + self.Bs = [] self.dihquery = [] - for B in all_Bparticles: # loop over all the B+/B-s stored in the TES + for all_B in all_Bs: # loop over all the B+/B-s stored in the TES dps = [0,0] dpsv = None svcoor = None - for dp in B.daughters(): # loop over the daughters of the B+/B- particle + for dp in all_B.daughters(): # loop over the daughters of the B+/B- particle if abs(ID(dp)) == 321: - dps[1] = MCPart(dp,pnnk=PROBNNk(dp),pnng=PROBNNghost(dp),cl=CL(dp)) + dps[1] = MCPart(dp,pnnk=PROBNNk(dp),pnnpi=PROBNNpi(dp),pnng=PROBNNghost(dp),cl=CL(dp)) else: hadrons = [0,0] dpsv = dp.endVertex() for ddp in dp.endVertex().outgoingParticlesVector(): hadron = MCPart(ddp) - hadron.update(newpnnk=PROBNNk(ddp),newpnng=PROBNNghost(ddp),newtrgp=TRGHOSTPROB(ddp)) + hadron.update(newpnnk=PROBNNk(ddp),newpnnpi=PROBNNpi(ddp),newpnng=PROBNNghost(ddp),newtrgp=TRGHOSTPROB(ddp)) + print(hadron.pid) if hadron.pid == 321: hadrons[0] = hadron elif hadron.pid == -321: hadrons[1] = hadron svcoor = [dpsv.position().X(),dpsv.position().Y(),dpsv.position().Z()] dps[0] = MCPart(dp,dl=hadrons,sv=svcoor) - B = MCPart(B,dl=dps) - self.Bparticles += [B] + B = MCPart(all_B,dl=dps) + self.Bs += [B] + print(f"self.Bs is {self.Bs}") def linkpars(self): """ - Links the rec level daughter particles (K, h-, h+) to gen level particles. + Links the reconstruction level daughter particles (K, h-, h+) to gen level particles. rels refers to related particles rel_K: related K particle rel_hadron_minus: related h- particle rel_hadron_plus: related h+ particle + dl: daughter list """ self.allreglinked = True - for Bparticle in self.Bparticles: - rels = self.mctool.relatedMCPs(Bparticle.dl[1].par) + for B in self.Bs: + rels = self.mctool.relatedMCPs(B.dl[1].par) w = 0 rel_K = None for rel in rels: if rel.weight() > w: w = rel.weight(); rel_K = rel.to() if rel_K: - Bparticle.dl[1].update(newlpar=MCPart(rel_K,orvrt=rel_K.originVertex().type())) + B.dl[1].update(newlpar=MCPart(rel_K,orvrt=rel_K.originVertex().type())) if rel_K.originVertex().type() != 1: try: rel_K.mother().originVertex().type() - Bparticle.dl[1].lpar.update(newmother=MCPart(rel_K.mother(),orvrt=rel_K.mother().originVertex().type())) + B.dl[1].lpar.update(newmother=MCPart(rel_K.mother(),orvrt=rel_K.mother().originVertex().type())) if rel_K.mother().originVertex().type() != 1: try: rel_K.mother().mother().originVertex().type() - Bparticle.dl[1].lpar.mother.update(newmother=MCPart(rel_K.mother().mother(),orvrt=rel_K.mother().mother().originVertex().type())) + B.dl[1].lpar.mother.update(newmother=MCPart(rel_K.mother().mother(),orvrt=rel_K.mother().mother().originVertex().type())) except: - Bparticle.dl[1].lpar.mother.update(newmother=None) + B.dl[1].lpar.mother.update(newmother=None) except: - Bparticle.dl[1].lpar.update(newmother=None) - - if MCID(rel_K) == Bparticle.dl[1].pid: Bparticle.dl[1].update(newclink=True) - else: Bparticle.dl[1].update(newclink=False) - - rels = self.mctool.relatedMCPs(Bparticle.dl[0].dl[0].par) + B.dl[1].lpar.update(newmother=None) + + if MCID(rel_K) == B.dl[1].pid: B.dl[1].update(newclink=True) + else: B.dl[1].update(newclink=False) + + print(f"type of B dl dl is {type(B.dl[0].dl[0])}") + print(f"type of B is {type(B)}") + print(f"type is {type(B.dl[0])}") + print(f"B dl is {B.dl}") + print(f"B dl dl is {B.dl[0].dl}") + print(f"B is {B}") + rels = self.mctool.relatedMCPs(B.dl[0].dl[0].par) w = 0 rel_hadron_minus = None for rel in rels: if rel.weight() > w: w = rel.weight(); rel_hadron_minus = rel.to() if rel_hadron_minus: - Bparticle.dl[0].dl[0].update(newlpar=MCPart(rel_hadron_minus,orvrt=rel_hadron_minus.originVertex().type())) + B.dl[0].dl[0].update(newlpar=MCPart(rel_hadron_minus,orvrt=rel_hadron_minus.originVertex().type())) if rel_hadron_minus.originVertex().type() != 1: try: rel_hadron_minus.mother().originVertex().type() - Bparticle.dl[0].dl[0].lpar.update(newmother=MCPart(rel_hadron_minus.mother(),orvrt=rel_hadron_minus.mother().originVertex().type())) + B.dl[0].dl[0].lpar.update(newmother=MCPart(rel_hadron_minus.mother(),orvrt=rel_hadron_minus.mother().originVertex().type())) if rel_hadron_minus.mother().originVertex().type() != 1: try: rel_hadron_minus.mother().mother().originVertex().type() - Bparticle.dl[0].dl[0].lpar.mother.update(newmother=MCPart(rel_hadron_minus.mother().mother(),orvrt=rel_hadron_minus.mother().mother().originVertex().type())) + B.dl[0].dl[0].lpar.mother.update(newmother=MCPart(rel_hadron_minus.mother().mother(),orvrt=rel_hadron_minus.mother().mother().originVertex().type())) except: - Bparticle.dl[0].dl[0].lpar.mother.update(newmother=None) + B.dl[0].dl[0].lpar.mother.update(newmother=None) except: - Bparticle.dl[0].dl[0].lpar.update(newmother=None) - if MCID(rel_hadron_minus) == Bparticle.dl[0].dl[0].pid: Bparticle.dl[0].dl[0].update(newclink=True) - else: Bparticle.dl[0].dl[0].update(newclink=False) + B.dl[0].dl[0].lpar.update(newmother=None) + if MCID(rel_hadron_minus) == B.dl[0].dl[0].pid: B.dl[0].dl[0].update(newclink=True) + else: B.dl[0].dl[0].update(newclink=False) - rels = self.mctool.relatedMCPs(Bparticle.dl[0].dl[1].par) + rels = self.mctool.relatedMCPs(B.dl[0].dl[1].par) w = 0 rel_hadron_plus = None for rel in rels: if rel.weight() > w: w = rel.weight(); rel_hadron_plus = rel.to() if rel_hadron_plus: - Bparticle.dl[0].dl[1].update(newlpar=MCPart(rel_hadron_plus,orvrt=rel_hadron_plus.originVertex().type())) + B.dl[0].dl[1].update(newlpar=MCPart(rel_hadron_plus,orvrt=rel_hadron_plus.originVertex().type())) if rel_hadron_plus.originVertex().type() != 1: try: rel_hadron_plus.mother().originVertex().type() - Bparticle.dl[0].dl[1].lpar.update(newmother=MCPart(rel_hadron_plus.mother(),orvrt=rel_hadron_plus.mother().originVertex().type())) + B.dl[0].dl[1].lpar.update(newmother=MCPart(rel_hadron_plus.mother(),orvrt=rel_hadron_plus.mother().originVertex().type())) if rel_hadron_plus.mother().originVertex().type() != 1: try: rel_hadron_plus.mother().mother().originVertex().type() - Bparticle.dl[0].dl[1].lpar.mother.update(newmother=MCPart(rel_hadron_plus.mother().mother(),orvrt=rel_hadron_plus.mother().mother().originVertex().type())) + B.dl[0].dl[1].lpar.mother.update(newmother=MCPart(rel_hadron_plus.mother().mother(),orvrt=rel_hadron_plus.mother().mother().originVertex().type())) except: - Bparticle.dl[0].dl[1].lpar.mother.update(newmother=None) + B.dl[0].dl[1].lpar.mother.update(newmother=None) except: - Bparticle.dl[0].dl[1].lpar.update(newmother=None) - if MCID(rel_hadron_plus) == Bparticle.dl[0].dl[1].pid: Bparticle.dl[0].dl[1].update(newclink=True) - else: Bparticle.dl[0].dl[1].update(newclink=False) + B.dl[0].dl[1].lpar.update(newmother=None) + if MCID(rel_hadron_plus) == B.dl[0].dl[1].pid: B.dl[0].dl[1].update(newclink=True) + else: B.dl[0].dl[1].update(newclink=False) if rel_K and rel_hadron_minus and rel_hadron_plus: - Bparticle.update(newalllinked=True) - if MCID(rel_K) == Bparticle.dl[1].pid and MCID(rel_hadron_minus) == Bparticle.dl[0].dl[0].pid and MCID(rel_hadron_plus) == Bparticle.dl[0].dl[1].pid: Bparticle.update(newcorlinked=True) + B.update(newalllinked=True) + if MCID(rel_K) == B.dl[1].pid and MCID(rel_hadron_minus) == B.dl[0].dl[0].pid and MCID(rel_hadron_plus) == B.dl[0].dl[1].pid: B.update(newcorlinked=True) - if Bparticle.dl[1].lpar.mother: - if Bparticle.dl[1].lpar.mother.mother: - Klin = [Bparticle.dl[1].lpar.par,Bparticle.dl[1].lpar.mother.par, Bparticle.dl[1].lpar.mother.mother.par] + if B.dl[1].lpar.mother: + if B.dl[1].lpar.mother.mother: + Klin = [B.dl[1].lpar.par,B.dl[1].lpar.mother.par, B.dl[1].lpar.mother.mother.par] else: - Klin = [Bparticle.dl[1].lpar.par,Bparticle.dl[1].lpar.mother.par] - else: Klin = [Bparticle.dl[1].lpar.par] + Klin = [B.dl[1].lpar.par,B.dl[1].lpar.mother.par] + else: Klin = [B.dl[1].lpar.par] - if Bparticle.dl[0].dl[0].lpar.mother: - if Bparticle.dl[0].dl[0].lpar.mother.mother: - hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par,Bparticle.dl[0].dl[0].lpar.mother.par, Bparticle.dl[0].dl[0].lpar.mother.mother.par] + if B.dl[0].dl[0].lpar.mother: + if B.dl[0].dl[0].lpar.mother.mother: + hadron_minus_lin = [B.dl[0].dl[0].lpar.par,B.dl[0].dl[0].lpar.mother.par, B.dl[0].dl[0].lpar.mother.mother.par] else: - hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par,Bparticle.dl[0].dl[0].lpar.mother.par] - else: hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par] + hadron_minus_lin = [B.dl[0].dl[0].lpar.par,B.dl[0].dl[0].lpar.mother.par] + else: hadron_minus_lin = [B.dl[0].dl[0].lpar.par] - if Bparticle.dl[0].dl[1].lpar.mother: - if Bparticle.dl[0].dl[1].lpar.mother.mother: - hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,Bparticle.dl[0].dl[1].lpar.mother.par, Bparticle.dl[0].dl[1].lpar.mother.mother.par] + if B.dl[0].dl[1].lpar.mother: + if B.dl[0].dl[1].lpar.mother.mother: + hadron_plus_lin = [B.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par, B.dl[0].dl[1].lpar.mother.mother.par] else: - hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,Bparticle.dl[0].dl[1].lpar.mother.par] - else: hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par] + hadron_plus_lin = [B.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par] + else: hadron_plus_lin = [B.dl[0].dl[1].lpar.par] sharelin = [] for i in range(len(hadron_minus_lin)): @@ -320,43 +333,48 @@ class MCEvent: else: if hadron_minus_lin[i] == Klin[k]: lin += (i+1)*100 + (k+1); sharelin += [lin] if hadron_plus_lin[j] == Klin[k]: lin = 0; lin += (j+1)*10 + (k+1); sharelin += [lin] - Bparticle.update(newlineage=sharelin) - - for prt in Bparticle.dl[1].lpar.par.originVertex().products(): - Bparticle.dl[1].lpar.update(newsp=prt) - if Bparticle.dl[1].lpar.mother: - for prt in Bparticle.dl[1].lpar.mother.par.originVertex().products(): - Bparticle.dl[1].lpar.mother.update(newsp=prt) - - for prt in Bparticle.dl[0].dl[0].lpar.par.originVertex().products(): - Bparticle.dl[0].dl[0].lpar.update(newsp=prt) - if Bparticle.dl[0].dl[0].lpar.mother: - for prt in Bparticle.dl[0].dl[0].lpar.mother.par.originVertex().products(): - Bparticle.dl[0].dl[0].lpar.mother.update(newsp=prt) - - for prt in Bparticle.dl[0].dl[1].lpar.par.originVertex().products(): - Bparticle.dl[0].dl[1].lpar.update(newsp=prt) - if Bparticle.dl[0].dl[1].lpar.mother: - for prt in Bparticle.dl[0].dl[1].lpar.mother.par.originVertex().products(): - Bparticle.dl[0].dl[1].lpar.mother.update(newsp=prt) + B.update(newlineage=sharelin) + + for prt in B.dl[1].lpar.par.originVertex().products(): + B.dl[1].lpar.update(newsp=prt) + if B.dl[1].lpar.mother: + for prt in B.dl[1].lpar.mother.par.originVertex().products(): + B.dl[1].lpar.mother.update(newsp=prt) + + for prt in B.dl[0].dl[0].lpar.par.originVertex().products(): + B.dl[0].dl[0].lpar.update(newsp=prt) + if B.dl[0].dl[0].lpar.mother: + for prt in B.dl[0].dl[0].lpar.mother.par.originVertex().products(): + B.dl[0].dl[0].lpar.mother.update(newsp=prt) + + for prt in B.dl[0].dl[1].lpar.par.originVertex().products(): + B.dl[0].dl[1].lpar.update(newsp=prt) + if B.dl[0].dl[1].lpar.mother: + for prt in B.dl[0].dl[1].lpar.mother.par.originVertex().products(): + B.dl[0].dl[1].lpar.mother.update(newsp=prt) elif self.allreglinked: self.allreglinked = False self.links = True def deltarlink(self, mcpstore): if not self.links: self.linkpars() - for Bparticle in self.Bparticles: - if not Bparticle.alllinked: - for recp in [Bparticle.dl[1],Bparticle.dl[0].dl[0],Bparticle.dl[0].dl[1]]: + for B in self.Bs: + if not B.alllinked: + #print("not all Bs are linked") + for recp in [B.dl[1],B.dl[0].dl[0],B.dl[0].dl[1]]: if not recp.lpar: mindr = 99999999999 parlink = None for mcp in mcpstore: + #print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") if MCID(mcp) == recp.pid: dphi = MCPHI(mcp) - PHI(recp.par) deta = MCETA(mcp) - ETA(recp.par) deltar = sqrt(dphi**2 + deta**2) if deltar < mindr: mindr = deltar; parlink = mcp + """ print(f"deltar is {deltar}") + print(f"mindr is {mindr}") + print(f"parlink is {parlink}") """ recp.update(newlpar=MCPart(parlink,orvrt=parlink.originVertex().type())) if MCID(parlink) == recp.pid: recp.update(newclink=True) else: recp.update(newclink=False) @@ -373,30 +391,30 @@ class MCEvent: except: recp.lpar.update(newmother=None) - if Bparticle.dl[1].lpar and Bparticle.dl[0].dl[0].lpar and Bparticle.dl[0].dl[1].lpar: - Bparticle.update(newisdrmatched=True) - if Bparticle.dl[1].lpar.pid == Bparticle.dl[1].pid and Bparticle.dl[0].dl[0].lpar.pid == Bparticle.dl[0].dl[0].pid and Bparticle.dl[0].dl[1].lpar.pid == Bparticle.dl[0].dl[1].pid: Bparticle.update(newcorlinked=True) + if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: + B.update(newisdrmatched=True) + if B.dl[1].lpar.pid == B.dl[1].pid and B.dl[0].dl[0].lpar.pid == B.dl[0].dl[0].pid and B.dl[0].dl[1].lpar.pid == B.dl[0].dl[1].pid: B.update(newcorlinked=True) - if Bparticle.dl[1].lpar.mother: - if Bparticle.dl[1].lpar.mother.mother: - Klin = [Bparticle.dl[1].lpar.par,Bparticle.dl[1].lpar.mother.par, Bparticle.dl[1].lpar.mother.mother.par] + if B.dl[1].lpar.mother: + if B.dl[1].lpar.mother.mother: + Klin = [B.dl[1].lpar.par,B.dl[1].lpar.mother.par, B.dl[1].lpar.mother.mother.par] else: - Klin = [Bparticle.dl[1].lpar.par,Bparticle.dl[1].lpar.mother.par] - else: Klin = [Bparticle.dl[1].lpar.par] + Klin = [B.dl[1].lpar.par,B.dl[1].lpar.mother.par] + else: Klin = [B.dl[1].lpar.par] - if Bparticle.dl[0].dl[0].lpar.mother: - if Bparticle.dl[0].dl[0].lpar.mother.mother: - hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par,Bparticle.dl[0].dl[0].lpar.mother.par, Bparticle.dl[0].dl[0].lpar.mother.mother.par] + if B.dl[0].dl[0].lpar.mother: + if B.dl[0].dl[0].lpar.mother.mother: + hadron_minus_lin = [B.dl[0].dl[0].lpar.par,B.dl[0].dl[0].lpar.mother.par, B.dl[0].dl[0].lpar.mother.mother.par] else: - hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par,Bparticle.dl[0].dl[0].lpar.mother.par] - else: hadron_minus_lin = [Bparticle.dl[0].dl[0].lpar.par] + hadron_minus_lin = [B.dl[0].dl[0].lpar.par,B.dl[0].dl[0].lpar.mother.par] + else: hadron_minus_lin = [B.dl[0].dl[0].lpar.par] - if Bparticle.dl[0].dl[1].lpar.mother: - if Bparticle.dl[0].dl[1].lpar.mother.mother: - hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,Bparticle.dl[0].dl[1].lpar.mother.par, Bparticle.dl[0].dl[1].lpar.mother.mother.par] + if B.dl[0].dl[1].lpar.mother: + if B.dl[0].dl[1].lpar.mother.mother: + hadron_plus_lin = [B.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par, B.dl[0].dl[1].lpar.mother.mother.par] else: - hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par,Bparticle.dl[0].dl[1].lpar.mother.par] - else: hadron_plus_lin = [Bparticle.dl[0].dl[1].lpar.par] + hadron_plus_lin = [B.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par] + else: hadron_plus_lin = [B.dl[0].dl[1].lpar.par] sharelin = [] for i in range(len(hadron_minus_lin)): @@ -410,25 +428,25 @@ class MCEvent: else: if hadron_minus_lin[i] == Klin[k]: lin += (i+1)*100 + (k+1); sharelin += [lin] if hadron_plus_lin[j] == Klin[k]: lin = 0; lin += (j+1)*10 + (k+1); sharelin += [lin] - Bparticle.update(newlineage=sharelin) - - for prt in Bparticle.dl[1].lpar.par.originVertex().products(): - Bparticle.dl[1].lpar.update(newsp=prt) - if Bparticle.dl[1].lpar.mother: - for prt in Bparticle.dl[1].lpar.mother.par.originVertex().products(): - Bparticle.dl[1].lpar.mother.update(newsp=prt) - - for prt in Bparticle.dl[0].dl[0].lpar.par.originVertex().products(): - Bparticle.dl[0].dl[0].lpar.update(newsp=prt) - if Bparticle.dl[0].dl[0].lpar.mother: - for prt in Bparticle.dl[0].dl[0].lpar.mother.par.originVertex().products(): - Bparticle.dl[0].dl[0].lpar.mother.update(newsp=prt) - - for prt in Bparticle.dl[0].dl[1].lpar.par.originVertex().products(): - Bparticle.dl[0].dl[1].lpar.update(newsp=prt) - if Bparticle.dl[0].dl[1].lpar.mother: - for prt in Bparticle.dl[0].dl[1].lpar.mother.par.originVertex().products(): - Bparticle.dl[0].dl[1].lpar.mother.update(newsp=prt) + B.update(newlineage=sharelin) + + for prt in B.dl[1].lpar.par.originVertex().products(): + B.dl[1].lpar.update(newsp=prt) + if B.dl[1].lpar.mother: + for prt in B.dl[1].lpar.mother.par.originVertex().products(): + B.dl[1].lpar.mother.update(newsp=prt) + + for prt in B.dl[0].dl[0].lpar.par.originVertex().products(): + B.dl[0].dl[0].lpar.update(newsp=prt) + if B.dl[0].dl[0].lpar.mother: + for prt in B.dl[0].dl[0].lpar.mother.par.originVertex().products(): + B.dl[0].dl[0].lpar.mother.update(newsp=prt) + + for prt in B.dl[0].dl[1].lpar.par.originVertex().products(): + B.dl[0].dl[1].lpar.update(newsp=prt) + if B.dl[0].dl[1].lpar.mother: + for prt in B.dl[0].dl[1].lpar.mother.par.originVertex().products(): + B.dl[0].dl[1].lpar.mother.update(newsp=prt) def corlin(self, B): """ @@ -463,24 +481,24 @@ class MCEvent: """ if not self.links: self.linkpars() - for Bparticle in self.Bparticles: - if self.corlin(Bparticle): - K = Bparticle.dl[1] - hadron_minus = Bparticle.dl[0].dl[0] - hadron_plus = Bparticle.dl[0].dl[1] + for B in self.Bs: + if self.corlin(B): + K = B.dl[1] + hadron_minus = B.dl[0].dl[0] + hadron_plus = B.dl[0].dl[1] try: fillKpid = K.lpar.mother.pid except: fillKpid = 0 - if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(Bparticle.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle + if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(B.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle #if K is K+ this must be a B+, if K is K- this must be a B- - if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: Bparticle.update(newbgtype=0) - if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: Bparticle.update(newbgtype=0) + if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: B.update(newbgtype=0) + if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: B.update(newbgtype=0) - for dp in Bparticle.dl: - dp.update(newbgtype=Bparticle.bgtype) + for dp in B.dl: + dp.update(newbgtype=B.bgtype) for ddp in dp.dl: - ddp.update(newbgtype=Bparticle.bgtype) + ddp.update(newbgtype=B.bgtype) def bgtypequery(self, part): """ @@ -496,21 +514,21 @@ class MCEvent: dectype: decay type of the input particle [int] """ dectype = -99 - Bparticle = None + B = None if ID(part) == 310: dectype = ID(part) elif part in self.Bstore: - for B in self.Bparticles: - if part == B.par: - Bparticle = B + for all_B in self.Bs: + if part == all_B.par: + B = all_B break - if Bparticle != None: - K = Bparticle.dl[1] - hadron_minus = Bparticle.dl[0].dl[0] - hadron_plus = Bparticle.dl[0].dl[1] + if B != None: + K = B.dl[1] + hadron_minus = B.dl[0].dl[0] + hadron_plus = B.dl[0].dl[1] - fill_B_dectype = Bparticle.dectype # fill B decay type - fill_B_odectype = Bparticle.odectype # fill B decay type + fill_B_dectype = B.dectype # fill B decay type + fill_B_odectype = B.odectype # fill B decay type try: hadron_minus_morvrt = hadron_minus.lpar.mother.orvrt except: hadron_minus_morvrt = 99 try: hadron_plus_morvrt = hadron_plus.lpar.mother.orvrt @@ -521,7 +539,7 @@ class MCEvent: fill_K_mother_dectype = K.lpar.mother.pid except: fill_K_mother_dectype.pid = 0 - if not Bparticle.corlinked or hadron_minus_morvrt == 99 or hadron_plus_morvrt == 99: + if not B.corlinked or hadron_minus_morvrt == 99 or hadron_plus_morvrt == 99: dectype = 8 elif fill_B_dectype == 0: dectype = 0 @@ -546,16 +564,16 @@ class MCEvent: drmatch: whether particle was delta R matched [int (0: no; 1: yes; 99: error)] """ drmatch = 99 - Bparticle = None + B = None if ID(part) == 310: drmatch = ID(part) elif part in self.Bstore: - for B in self.Bparticles: - if part == B.par: - Bparticle = B + for all_B in self.Bs: + if part == all_B.par: + B = all_B break - if Bparticle != None: - if Bparticle.isdrmatched: drmatch = 1 + if B != None: + if B.isdrmatched: drmatch = 1 else: drmatch = 0 else: drmatch = 9 @@ -573,15 +591,15 @@ class MCEvent: OUTPUTS ------- - Bparticle: MCPart object of the corresponding B/eta [MCPart] + B: MCPart object of the corresponding B/eta [MCPart] """ - Bparticle = None + B = None if ID(part) == 310: - Bparticle = self.Bparticles[0] + B = self.Bs[0] elif part in self.Bstore: - for B in self.Bparticles: - if part == B.par: - Bparticle = B + for all_B in self.Bs: + if part == all_B.par: + B = all_B break - return Bparticle + return B diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 6073387e3e..da3e3e1ea7 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -13,8 +13,6 @@ import ROOT from dtf import dtf #dtf: decay tree fitter import MCEventTools -#from ReconstructionTools import BremOverlapChecker, SelectRhos - class Ntuple: """Class for storing an ntuple.""" @@ -25,18 +23,17 @@ class Ntuple: ): """Initialize the ntuple.""" # Internal tools. - #self.combinatorial_fakePhotons = combinatorial_fakePhotons - self._detTool = gaudi.detSvc()["/dd/Structure/LHCb/BeforeMagnetRegion/Velo"] + self._detTool = gaudi.detSvc()["/dd/Structure/LHCb/BeforeMagnetRegion/Velo"] # detector tool self._pvrTool = gaudi.toolsvc().create( "GenericParticle2PVRelator<_p2PVWithIPChi2, " "OfflineDistanceCalculatorName>/P2PVWithIPChi2", - interface="IRelatedPVFinder", + interface="IRelatedPVFinder", #primary vertex relator tool ) self._dstTool = gaudi.toolsvc().create( - "LoKi::TrgDistanceCalculator", interface="IDistanceCalculator" + "LoKi::TrgDistanceCalculator", interface="IDistanceCalculator" # distance tool ) self._trkTool = gaudi.toolsvc().create( - "TrackMasterExtrapolator", interface="ITrackExtrapolator" + "TrackMasterExtrapolator", interface="ITrackExtrapolator" #track tool ) self._trgTool = [ gaudi.toolsvc().create("L0TriggerTisTos", interface="ITriggerTisTos"), @@ -49,7 +46,7 @@ class Ntuple: gaudi.toolsvc().create( "TriggerTisTos/Strip/PhysTriggerTisTos", interface="ITriggerTisTos" ), - ] + ] # trigger tools self._docaTool = GaudiPython.gbl.LoKi.Particles.DOCA(0, 0, self._dstTool) self._veloTool = ROOT.VeloMaterial() @@ -65,7 +62,7 @@ class Ntuple: # Stored variables. self.saved: "dict[str, int]" = {} self.keys: "dict[str, str]" = {} - self.pres = {"tag": 0, "trk": 1, "cal": 2} + self.pres = {"tag": 0, "trk": 1} self.nhit = 2 self.ntuple: "OrderedDict[str, array.array | Any]" = OrderedDict() self.tfile = ROOT.TFile(output_filename, "RECREATE", "", 207) @@ -90,9 +87,6 @@ class Ntuple: "m", "dm", "idx_pvr", - "ecalx", - "ecaly", - "ecalz", "veloCharge", ] for h in range(0, self.nhit): @@ -111,7 +105,7 @@ class Ntuple: "fd_chi2", "vt_tip", "vt_d", - "etrackOL", + "htrackOL", ] + [ f"{dtf}dtf_{x}" @@ -140,7 +134,6 @@ class Ntuple: vrsIdx = ["idx_pvr", "idx_prt0", "idx_prt1", "pre_prt0", "pre_prt1"] self._vrs("tag", vrsIdx + vrsMom + ["pid"] + vrsVrt + vrsTag + vrsTrg + vrsMC) self._vrs("trk", vrsMom + ["pid"] + vrsTrk) - self._vrs("cal", ["mm", "dmm", "m", "dm", "idx_pvr", "pid"] + vrsMom) self._vrs("pvr", vrsVrt) self.ntuple["pvr_n"] = array.array("i", [-1]) self.ntuple["spd_n"] = array.array("i", [-1]) @@ -164,7 +157,7 @@ class Ntuple: def _vrs(self, pre: "str", vrs): """Initialize variables with a given prefix.""" self.keys[pre] = f"{pre}_{vrs[0]}" - tmap = {"int": ["pid", "idx", "pre", "tip", "Brem"], "bool": ["tis", "tos"]} + tmap = {"int": ["pid", "idx", "pre", "tip"], "bool": ["tis", "tos"]} for var in vrs: t = "float" for key, k_list in tmap.items(): @@ -180,18 +173,12 @@ class Ntuple: trk = obj.proto().track().momentum() except: trk = None - try: - cal = obj.proto().calo()[0].position() - except: - cal = None try: vrt = obj.position() except: vrt = None if trk: return (trk.X(), trk.Y(), trk.Z()) - if cal: - return (cal.x(), cal.y(), cal.z(), cal.e()) if vrt: return (vrt.X(), vrt.Y(), vrt.Z()) return (obj.momentum().Px(), obj.momentum().Py(), obj.momentum().Pz()) @@ -333,8 +320,9 @@ class Ntuple: dptype = "hm" # dptype: daughter particle type elif mcp.pid == +321 or mcp.pid == +211: dptype = "hp" - elif mcp.pid == 22: + elif mcp.pid == 22: #photon - this is topologically mapped to a bachelor akaon for our analysis but how should I deal with this? dptype = "g" + # check that the mother is a K short else: raise ValueError(f"mcp.pid {mcp.pid} not recognized") mom = mcp.lpar.p @@ -343,7 +331,7 @@ class Ntuple: lpid = int(mcp.lpar.pid) except: lpid = 99 - self.fill(f"{pre}_{dptype}_lpx", mom.Px()) + self.fill(f"{pre}_{dptype}_lpx", mom.Px()) # l denotes that it is variable associated to a linked particle self.fill(f"{pre}_{dptype}_lpy", mom.Py()) self.fill(f"{pre}_{dptype}_lpz", mom.Pz()) self.fill(f"{pre}_{dptype}_le", mom.E()) @@ -358,20 +346,18 @@ class Ntuple: ########################################################################### def _filltrkOL(self, pre: "str", prt): - """Find number of shared track lhcbIDs between e- and e+. + """Find number of shared track lhcbIDs between h- and h+. Accessed through dihadron candidate. """ if prt.particleID().pid() == 310: #310 is K short nol = 0 - hm = hp = None + hm = hp = None # hm: hadron (pi or K) minus, hp: hadron plus for i_dp in range(len(prt.daughtersVector())): dp = prt.daughtersVector()[i_dp] - #if dp.particleID().pid() == -321: if dp.particleID().pid() == -321 or dp.particleID().pid() == -211: assert hm is None hm = dp - #elif dp.particleID().pid() == 321: elif dp.particleID().pid() == 321 or dp.particleID().pid() == 211: assert hp is None hp = dp @@ -410,7 +396,7 @@ class Ntuple: if pvr and len(dtrs) == 2 and not any(x is None for x in (pos, cov)): if year not in (2016, 2017, 2018): raise ValueError(f"year {year} not recognized") - pvp = pvr.position() + pvp = pvr.position() # pvp: primary vertex position vfv = (pos.X() - pvp.X(), pos.Y() - pvp.Y(), pos.Z() - pvp.Z()) assert cov is not None self.fill( @@ -515,7 +501,7 @@ class Ntuple: # Daughters. daughter_momenta = [] for idx in range(len(prt.daughtersVector())): - dtr = prt.daughtersVector()[idx] + dtr = prt.daughtersVector()[idx] #daughter daughter_momenta += [dtr.momentum()] dtrPre, dtrIdx = self.fillPrt( # type: ignore dtr, pos, year, run, mctool=mctool @@ -542,65 +528,65 @@ class Ntuple: self._fillgenMomPID(pre, mcprt) try: - emmpid = int(mcprt.dl[0].dl[0].lpar.mother.pid) + hmmpid = int(mcprt.dl[0].dl[0].lpar.mother.pid) #hmmpid: hadron minus mother pid except: - emmpid = 99 + hmmpid = 99 try: - epmpid = int(mcprt.dl[0].dl[1].lpar.mother.pid) + hpmpid = int(mcprt.dl[0].dl[1].lpar.mother.pid) except: - epmpid = 99 + hpmpid = 99 try: gmpid = int(mcprt.dl[1].lpar.mother.pid) except: gmpid = 99 try: - emorvrt = int(mcprt.dl[0].dl[0].lpar.orvrt) + hmorvrt = int(mcprt.dl[0].dl[0].lpar.orvrt) except: - emorvrt = 99 + hmorvrt = 99 try: - eporvrt = int(mcprt.dl[0].dl[1].lpar.orvrt) + hporvrt = int(mcprt.dl[0].dl[1].lpar.orvrt) except: - eporvrt = 99 + hporvrt = 99 try: gorvrt = int(mcprt.dl[1].lpar.orvrt) except: gorvrt = 99 try: - emmorvrt = int(mcprt.dl[0].dl[0].lpar.mother.orvrt) + hmmorvrt = int(mcprt.dl[0].dl[0].lpar.mother.orvrt) except: - emmorvrt = 99 + hmmorvrt = 99 try: - epmorvrt = int(mcprt.dl[0].dl[1].lpar.mother.orvrt) + hpmorvrt = int(mcprt.dl[0].dl[1].lpar.mother.orvrt) except: - epmorvrt = 99 + hpmorvrt = 99 try: gmorvrt = int(mcprt.dl[1].lpar.mother.orvrt) except: gmorvrt = 99 try: - emmmpid = int(mcprt.dl[0].dl[0].lpar.mother.mother.pid) + hmmmpid = int(mcprt.dl[0].dl[0].lpar.mother.mother.pid) except: - emmmpid = 99 + hmmmpid = 99 try: - epmmpid = int(mcprt.dl[0].dl[1].lpar.mother.mother.pid) + hpmmpid = int(mcprt.dl[0].dl[1].lpar.mother.mother.pid) except: - epmmpid = 99 + hpmmpid = 99 try: gmmpid = int(mcprt.dl[1].lpar.mother.mother.pid) except: gmmpid = 99 try: - emmmorvrt = int(mcprt.dl[0].dl[0].lpar.mother.mother.orvrt) + hmmmorvrt = int(mcprt.dl[0].dl[0].lpar.mother.mother.orvrt) except: - emmmorvrt = 99 + hmmmorvrt = 99 try: - epmmorvrt = int(mcprt.dl[0].dl[1].lpar.mother.mother.orvrt) + hpmmorvrt = int(mcprt.dl[0].dl[1].lpar.mother.mother.orvrt) except: - epmmorvrt = 99 + hpmmorvrt = 99 try: gmmorvrt = int(mcprt.dl[1].lpar.mother.mother.orvrt) except: @@ -618,25 +604,25 @@ class Ntuple: drm = -1 self.fill(f"{pre}_drmatch", drm) - self.fill(f"{pre}_em_mpid", emmpid) - self.fill(f"{pre}_ep_mpid", epmpid) - self.fill(f"{pre}_g_mpid", gmpid) + self.fill(f"{pre}_hm_mpid", hmmpid) + self.fill(f"{pre}_hp_mpid", hpmpid) + self.fill(f"{pre}_K_mpid", gmpid) - self.fill(f"{pre}_em_mmpid", emmmpid) - self.fill(f"{pre}_ep_mmpid", epmmpid) - self.fill(f"{pre}_g_mmpid", gmmpid) + self.fill(f"{pre}_hm_mmpid", hmmmpid) + self.fill(f"{pre}_hp_mmpid", hpmmpid) + self.fill(f"{pre}_K_mmpid", gmmpid) - self.fill(f"{pre}_em_orvrt", emorvrt) - self.fill(f"{pre}_ep_orvrt", eporvrt) - self.fill(f"{pre}_g_orvrt", gorvrt) + self.fill(f"{pre}_hm_orvrt", hmorvrt) + self.fill(f"{pre}_hp_orvrt", hporvrt) + self.fill(f"{pre}_K_orvrt", gorvrt) - self.fill(f"{pre}_em_morvrt", emmorvrt) - self.fill(f"{pre}_ep_morvrt", epmorvrt) - self.fill(f"{pre}_g_morvrt", gmorvrt) + self.fill(f"{pre}_hm_morvrt", hmmorvrt) + self.fill(f"{pre}_hp_morvrt", hpmorvrt) + self.fill(f"{pre}_K_morvrt", gmorvrt) - self.fill(f"{pre}_em_mmorvrt", emmmorvrt) - self.fill(f"{pre}_ep_mmorvrt", epmmorvrt) - self.fill(f"{pre}_g_mmorvrt", gmmorvrt) + self.fill(f"{pre}_hm_mmorvrt", hmmmorvrt) + self.fill(f"{pre}_hp_mmorvrt", hpmmorvrt) + self.fill(f"{pre}_K_mmorvrt", gmmorvrt) # Trigger. self._fillTrg(pre, prt) @@ -662,9 +648,6 @@ class Ntuple: self._fillMat(pre, pvr, pos, daughter_momenta, cov, year, run) # fillMat not implemented for dtf b/c it's a pain to code up daughter momenta - # Brem. - self._fillBrem(pre, prt) - # Flight distance. fd, fdChi2 = ctypes.c_double(-1), ctypes.c_double(-1) if pvr and vrt: @@ -724,29 +707,6 @@ class Ntuple: f"{pre}_veloCharge", pro.info(GaudiPython.gbl.LHCb.ProtoParticle.VeloCharge, -10000.0), ) - # ECal - em_hypo = None # calorimeter hypothesis using EmCharged - ecal_vec = GaudiPython.gbl.LHCb.StateVector() # position of interaction - for i_hypo in range(len(pro.calo())): - if ( - pro.calo()[i_hypo].hypothesis() - == GaudiPython.gbl.LHCb.CaloHypo.EmCharged - ): - assert em_hypo is None - em_hypo = pro.calo()[i_hypo] - if em_hypo: - assert em_hypo.hypothesis() == GaudiPython.gbl.LHCb.CaloHypo.EmCharged - ecal_z = ( - em_hypo.position().z() - ) # mm, Z-position where cluster parameters are estimated - self._trkTool.propagate(trk, ecal_z, ecal_vec, prt.particleID()) - else: - ecal_vec.setX(-1e9) - ecal_vec.setY(-1e9) - ecal_vec.setZ(-1e9) - self.fill(f"{pre}_ecalx", ecal_vec.x()) - self.fill(f"{pre}_ecaly", ecal_vec.y()) - self.fill(f"{pre}_ecalz", ecal_vec.z()) # Material tool. self.fill( @@ -776,14 +736,6 @@ class Ntuple: self.fill(f"{pre}_ip_chi2", ipChi2.value) return pvr - ########################################################################### - def _fillCal(self, pre: "str", prt): - """Fill the information for a calorimetry object.""" - mom = prt.momentum() - self._fillMom(pre, mom) - self._fillM(pre, mom) - self._fillMM(pre, prt) - ########################################################################### def fillPrt( self, @@ -798,7 +750,7 @@ class Ntuple: vrt = prt.endVertex() key = self._key(prt) idx = self._index(key) - pre = "tag" if vrt else ("trk" if prt.charge() else "cal") + pre = "tag" if vrt else "trk" if idx is not None: return (pre, idx) @@ -819,10 +771,6 @@ class Ntuple: assert year is not None and run is not None, (year, run) assert prt.particleID().pid() in (-321, 321, -211, 211), prt.particleID().pid() # K-, K+, pi-, pi+ pvr = self._fillTrk(pre, prt, org, year, run) - elif pre == "cal": - assert prt.particleID().pid() == 22, prt.particleID().pid() - assert self._fillCal(pre, prt) is None - pvr = None else: raise ValueError(f"pre '{pre}' not recognized") self.fill(f"{pre}_pid", prt.particleID().pid()) @@ -839,19 +787,21 @@ class Ntuple: self.fill(f"{pre}_idx_pvr", -1) return (pre, self._save(pre, key)) +# End of class Ntuple def fill_ntuple( ntuple: "Ntuple", tes, - candloc: "str", - #candloc_noBrem: "str", - #head: "Union[str, None]", + candidate_loc: "str", + hh: "str", genTool, ): + print(f"hh is {hh}") """Assign values to the ntuple.""" year = int(DaVinci().DataType) def get_particles(candloc): + print(f"candidate_loc is {candloc}") try: particles = tes[candloc] #print(particles) @@ -874,24 +824,20 @@ def fill_ntuple( GaudiPython.gbl.LoKi.L0.DataValue("Spd(Mult)")(tes["Trig/L0/L0DUReport"]) ) - print(candloc) - # Fill the particles. - all_Bs = get_particles(candloc) - print(f"length of all bs is {len(all_Bs)}") - - + all_Bparticles = get_particles(candidate_loc) + print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") if DaVinci().Simulation: - print("yay its working for mc") - mct = MCEventTools.MCEvent(all_Bs, genTool) # mct: monte carlo tool - print(mct) + mct = MCEventTools.MCEvent(all_Bparticles, genTool) # mct: monte carlo tool + #print(mct) mct.linkpars() mcpstore = get_particles( "AllStreams/MC/Particles" if DaVinci().InputType == "MDST" else "MC/Particles" ) + print(f"got {len(mcpstore)} B mesons from MC/Particles") mct.deltarlink(mcpstore) mct.categorize() else: @@ -899,12 +845,10 @@ def fill_ntuple( isfilled = False - - print('moving onto for loop') - for B in all_Bs: + for all_Bparticle in all_Bparticles: print("its working") - args, kwargs = [B], {"year": year, "run": rnum, "mctool": mct} + args, kwargs = [all_Bparticle], {"year": year, "run": rnum, "mctool": mct} boolVal = ntuple.fillPrt(*args, **kwargs, check=True) print("I find bool is {0}".format(boolVal)) @@ -913,7 +857,10 @@ def fill_ntuple( isfilled = True print("isfilled has been set to {0}".format(isfilled)) - if len(all_Bs) > 0 and isfilled: + if len(all_Bparticles) > 0 and isfilled: print("conditions for filling tuple met") ntuple.fill() ntuple.clear() + +# End of fill_ntuple function + diff --git a/bu2kdarkscalar_darkscalar2hh/README.md b/bu2kdarkscalar_darkscalar2hh/README.md new file mode 100644 index 0000000000..fd0c9bc37d --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/README.md @@ -0,0 +1,47 @@ +# `bu2kdarkscalar_darkscalar2hh` + +Analysis production for dark scalar ($\chi$) search in +$B \rightarrow K (\chi \rightarrow h^+ h^-)$ decays. + +This production is based off the production for the dark photon ($`A^\prime`$)/true muonium search in +$`\pi^0 / \eta \rightarrow \gamma (A^\prime \rightarrow e^+ e^-)`$ decays. + +## Working with the n-tuples from `job.py` + +In the following: `TFile` will be shorthand for `ROOT.TFile("<treefile.root>")`, +where `treefile.root` is the ROOT file containing the n-tuple. +Likewise, `tree` will be shorthand for `TFile.DecayData`. + +### Prefixes and Accessing variables + +In the output `TFile`, +there are 3 prefixes used to label data: + +| Prefix Name | Prefix Number | Associated Data | +|:-----------:|:-------------:|:---------------:| +| `tag` | 0 | reconstructed particles (i.e. $B^{\pm}$, dihadron) [object has an `endVertex`] | +| `trk` | 1 | tracks (hadrons: $K^{\pm}$, $\pi^{\pm}$) [object does not have an `endVertex` (long-lived) and is charged] | + +The data for a particular prefix is accessed +(after getting event with `tree.GetEntry(i)`) from `tree.<Prefix Name>_<variable>[j]`, +where `i` denotes the i<sup>th</sup> event and `j` denotes the j<sup>th</sup> particle in the event with prefix `Prefix Name`; +`variable` is the specific variable being accessed. +For example, `tree.tag_pid[0]` would be the PID of the first tag object in the event. + +**_Note:_** Whether a tag is a $B^{\pm}$ or a dihadron can be determined by checking its PID; +if PID is +/-521: tag is a $B^{\pm}$, if PID is 310: tag is a dihadron + +### Establishing Lineage of Particles + +For a tag object, the variables `tag_pre_prt0`, `tag_idx_prt0`, `tag_pre_prt1`, and `tag_idx_prt1` are used to find its associated daughter particles. +`tag_pre_prt0` and `tag_pre_prt1` are the prefix numbers of the first and second daughter particles respectively (pre refers to prefix); +`tag_idx_prt0` and `tag_idx_prt1` are the indices of the first and second daughter particles respectively in their prefix's particle entries for the event (idx refers to index). +For example, if tree had `tree.tag_pre_prt0[i] == 1`, `tree.tag_idx_prt0[i] == 2`, `tree.tag_pre_prt1[i] == 0`, `tree.tag_idx_prt1[i] == 3`, +the data for the first daughter particle (prt0) would be stored at `tree.trk_<var>[2]` and the data for the second daughter particle (prt1) would be stored at `tree.tag_<var>[3]`. + +`keys`: a 3- tuple. + +- for a `trk` particle this is the three-vector of momentum +- for a `vrt` this is the position vector +- otherwise this is the object momentum three-vector + diff --git a/bu2kdarkscalar_darkscalar2hh/hh_KK.py b/bu2kdarkscalar_darkscalar2hh/hh_KK.py new file mode 100644 index 0000000000..c2e23771fd --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/hh_KK.py @@ -0,0 +1,5 @@ +"""Set hh for job.py. + +This file should be passed as a command-line option to job.py. +""" +head = "KK" diff --git a/bu2kdarkscalar_darkscalar2hh/hh_PiPi.py b/bu2kdarkscalar_darkscalar2hh/hh_PiPi.py new file mode 100644 index 0000000000..21570c9612 --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/hh_PiPi.py @@ -0,0 +1,5 @@ +"""Set head for job.py. + +This file should be passed as a command-line option to job.py. +""" +head = "PiPi" diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index cee77310d1..d4354840b4 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -2,7 +2,7 @@ {%- for polarity in polarities %} -Bu2KdarkScalar_darkScalar2hh_2018_Mag{{polarity}}_job: +B2KKK_2018_Mag{{polarity}}_job: application: DaVinci/v46r8 wg: QEE automatically_configure: yes @@ -16,13 +16,13 @@ Bu2KdarkScalar_darkScalar2hh_2018_Mag{{polarity}}_job: - job.py input: - bk_query: /LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p3/90000000/LEPTONIC.MDST + bk_query: /MC/2018/Beam6500GeV-2018-MagDown-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST # For MC # /MC/2018/Beam6500GeV-2018-MagDown-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST # For data # /LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST - output: bu2kdarkscalar_darkscalar2hh.ROOT + output: B2KKK.ROOT # For MC # B2KKK.ROOT # For data diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index b24f45abd0..e785c149b8 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -38,28 +38,41 @@ def parse_args() -> "tuple[Union[str, None], bool]": ) args = parser.parse_args() + allowed_hh = ("PiPi", "KK") + hh = None + + for options_path in args.options_paths: print("Adding options file:", options_path) # execute module # https://stackoverflow.com/a/19011259/4655426 loader = importlib.machinery.SourceFileLoader( - "head_particle", str(options_path.resolve()) + "hh_particle", str(options_path.resolve()) ) spec = importlib.util.spec_from_loader(loader.name, loader) assert spec is not None mod = importlib.util.module_from_spec(spec) loader.exec_module(mod) - - - - - return + # try to get head + try: + hh = mod.head + except AttributeError: + # did not set head + pass + if DaVinci().Simulation: + if hh is None: + raise parser.error("One options file must set hh for MC.") + elif hh not in allowed_hh: + raise ValueError(f"hh must be one of {allowed_hh}") + else: + print(f"Running with hh == '{hh}'.") + return hh # In order to use Run 1+2 GaudiPython with analysis productions the arguments # passed to the script need to be executed to set up input data and other state -parse_args() +hh = parse_args() # ================================================================================= # Set up material tool print("About to compile velo material tool") @@ -89,8 +102,14 @@ DaVinci().Lumi = False # ================================================================================= #candidate locations -candloc_KK = "/Event/Leptonic/Phys/B2KX2KKDarkBosonLine/Particles" -candloc_PiPi = "/Event/Leptonic/Phys/B2KX2PiPiDarkBosonLine/Particles" +if hh == "KK": + candloc = "/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles" +elif hh == "PiPi": + candloc = "/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles" + + +#candloc_KK = "/Event/Leptonic/Phys/B2KX2KKDarkBosonLine/Particles" +#candloc_PiPi = "/Event/Leptonic/Phys/B2KX2PiPiDarkBosonLine/Particles" # ================================================================================= @@ -123,9 +142,8 @@ while (NOfEvents == -1) or (event < NOfEvents): event += 1 print("found evt") - fill_ntuple(ntuple, tes, candloc_KK, genTool) - print("filled ntuple KK") - #fill_ntuple(ntuple, tes, candloc_PiPi, genTool) + fill_ntuple(ntuple, tes, candloc, hh, genTool) + print("filled") # Close. -- GitLab From 6f63b008f4930afc6ab1285b7391d08cb04a8f56 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Thu, 25 Jul 2024 17:00:44 +0100 Subject: [PATCH 09/70] logic for B2KKK must be differnt than dark photon --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 2 +- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 142 ++++++++++--------- bu2kdarkscalar_darkscalar2hh/README.md | 83 ++++++++++- bu2kdarkscalar_darkscalar2hh/info.yaml | 21 ++- bu2kdarkscalar_darkscalar2hh/job.py | 2 +- 5 files changed, 177 insertions(+), 73 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index f42bd97f38..32740602ef 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -3,7 +3,7 @@ import GaudiPython as GP from GaudiConf import IOHelper from Configurables import DaVinci, TriggerTisTos, ToolSvc from ROOT import TLorentzVector as tlv -from LoKiMC.decorators import MCPX, MCPY, MCPZ, MCE, MCID, MCABSID, MCPT, MCM, MCETA, MCP, MCPHI +from LoKiMC.decorators import MCPX, MCPY, MCPZ, MCE, MCID, MCABSID, MCPT, MCM, MCETA, MCP, MCPHI, MCINANCESTORS from LoKiPhys.decorators import M, PX, PY, PZ, E, ID, ABSID, PROBNNk, PROBNNpi, PROBNNghost, IPCHI2, P, PT, CL, ETA, TRGHOSTPROB, PHI from array import array from math import sqrt diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index da3e3e1ea7..e0d1fb4f43 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -9,6 +9,7 @@ from typing import Any, Union from Configurables import DaVinci import GaudiPython from LoKiArrayFunctors.decorators import AMAXDOCA +from LoKiMC.decorators import MCINANCESTORS, MCID import ROOT from dtf import dtf #dtf: decay tree fitter @@ -46,7 +47,7 @@ class Ntuple: gaudi.toolsvc().create( "TriggerTisTos/Strip/PhysTriggerTisTos", interface="ITriggerTisTos" ), - ] # trigger tools + ] # trigger tools: L0, Hlt1, Hlt2, Stripping self._docaTool = GaudiPython.gbl.LoKi.Particles.DOCA(0, 0, self._dstTool) self._veloTool = ROOT.VeloMaterial() @@ -54,10 +55,12 @@ class Ntuple: self.tos: "list[list[str]]" = [ ["L0Hadron.*"], ["Hlt1.*Hadron.*", "Hlt1.*TrackMVA.*"], - ["Hlt2ExoticaPi0ToDiEGamma", "Hlt2ExoticaEtaToDiEGamma"], + ["Hlt2.*"], [], ] - self.tis: "list[list[str]]" = [[], [], ["Hlt2Topo.*"], []] + + #["Hlt2ExoticaPi0ToDiEGamma", "Hlt2ExoticaEtaToDiEGamma"], + self.tis: "list[list[str]]" = [[], [], ["Hlt2.*"], []] # Stored variables. self.saved: "dict[str, int]" = {} @@ -109,7 +112,7 @@ class Ntuple: ] + [ f"{dtf}dtf_{x}" - for dtf in ["p", "d"] + for dtf in ["p", "d"] # p: parent, d: daughter for x in ["m", "dm", "chi2"] + vrsMom + vrsVrt @@ -279,7 +282,11 @@ class Ntuple: ########################################################################### def _fillMM(self, pre: "str", prt): - """Fill measured mass for a particle.""" + """Fill measured mass for a particle. + mm: measured mass + dmm: delta measured mass (the error on the measured mass) + + """ self.fill(f"{pre}_mm", prt.measuredMass()) self.fill(f"{pre}_dmm", prt.measuredMassErr()) @@ -313,16 +320,20 @@ class Ntuple: self.fill(f"{pre}_dih_m", mom.M()) ########################################################################### - def _fillgenMomPID(self, pre: "str", mcp): + def _fillgenMomPID(self, pre: "str", mcp, dptype): """Fill generator level MC momentum and pid for a particle.""" if mcp: # mcp: monte carlo particle - if mcp.pid == -321 or mcp.pid == -211: - dptype = "hm" # dptype: daughter particle type - elif mcp.pid == +321 or mcp.pid == +211: - dptype = "hp" - elif mcp.pid == 22: #photon - this is topologically mapped to a bachelor akaon for our analysis but how should I deal with this? - dptype = "g" - # check that the mother is a K short + if mcp.pid in (310, 211, -211, 321, -321, 521, -521): + print(f"mcp.pid is set to {mcp.pid}") + + if dptype in ("hadron0", "hadron1"): + if mcp.pid in (-211, -321): + dptype = "hm" + print(f"found mcp particle which is hm with pid {mcp.pid}") + elif mcp.pid in (211, 321): + dptype = "hp" + print(f"found mcp particle which is hp with pid {mcp.pid}") + pass else: raise ValueError(f"mcp.pid {mcp.pid} not recognized") mom = mcp.lpar.p @@ -337,7 +348,7 @@ class Ntuple: self.fill(f"{pre}_{dptype}_le", mom.E()) self.fill(f"{pre}_{dptype}_lpid", lpid) else: - for dptype in ["hm", "hp", "g"]: + for dptype in ["hm", "hp", "K"]: self.fill(f"{pre}_{dptype}_lpx", -1) self.fill(f"{pre}_{dptype}_lpy", -1) self.fill(f"{pre}_{dptype}_lpz", -1) @@ -485,6 +496,7 @@ class Ntuple: pdtf = dtf(prt, pvr, True) ddtf = dtf(prt, pvr, False) if prt.particleID().pid() in (-521, 521): + # B- or B+ pdtf.fit() ddtf.fit() assert pdtf.dihadron == ddtf.dihadron @@ -520,77 +532,79 @@ class Ntuple: if mctool is not None: mcprt = mctool.mcpquery(prt) + hh = "KK" + if mcprt: - self._fillgenMomPID(pre, mcprt.dl[1]) - self._fillgenMomPID(pre, mcprt.dl[0].dl[0]) - self._fillgenMomPID(pre, mcprt.dl[0].dl[1]) + self._fillgenMomPID(pre, mcprt.dl[1], "K") # B's daughter: bachelor kaon + self._fillgenMomPID(pre, mcprt.dl[0].dl[0], "hadron0") # B's daughter, dark scalar,has two daughters itself + self._fillgenMomPID(pre, mcprt.dl[0].dl[1], "hadron1") # B's daughter: hadron else: - self._fillgenMomPID(pre, mcprt) + self._fillgenMomPID(pre, mcprt, hh, "B") try: - hmmpid = int(mcprt.dl[0].dl[0].lpar.mother.pid) #hmmpid: hadron minus mother pid + hm_mpid = int(mcprt.dl[0].dl[0].lpar.mother.pid) #hm_mpid: hadron minus mother pid except: - hmmpid = 99 + hm_mpid = 99 try: - hpmpid = int(mcprt.dl[0].dl[1].lpar.mother.pid) + hp_mpid = int(mcprt.dl[0].dl[1].lpar.mother.pid) except: - hpmpid = 99 + hp_mpid = 99 try: - gmpid = int(mcprt.dl[1].lpar.mother.pid) + K_mpid = int(mcprt.dl[1].lpar.mother.pid) except: - gmpid = 99 + K_mpid = 99 try: - hmorvrt = int(mcprt.dl[0].dl[0].lpar.orvrt) + hm_orvrt = int(mcprt.dl[0].dl[0].lpar.orvrt) except: - hmorvrt = 99 + hm_orvrt = 99 try: - hporvrt = int(mcprt.dl[0].dl[1].lpar.orvrt) + hp_orvrt = int(mcprt.dl[0].dl[1].lpar.orvrt) except: - hporvrt = 99 + hp_orvrt = 99 try: - gorvrt = int(mcprt.dl[1].lpar.orvrt) + K_orvrt = int(mcprt.dl[1].lpar.orvrt) except: - gorvrt = 99 + K_orvrt = 99 try: - hmmorvrt = int(mcprt.dl[0].dl[0].lpar.mother.orvrt) + hm_morvrt = int(mcprt.dl[0].dl[0].lpar.mother.orvrt) except: - hmmorvrt = 99 + hm_morvrt = 99 try: - hpmorvrt = int(mcprt.dl[0].dl[1].lpar.mother.orvrt) + hp_morvrt = int(mcprt.dl[0].dl[1].lpar.mother.orvrt) except: - hpmorvrt = 99 + hp_morvrt = 99 try: - gmorvrt = int(mcprt.dl[1].lpar.mother.orvrt) + K_morvrt = int(mcprt.dl[1].lpar.mother.orvrt) except: - gmorvrt = 99 + K_morvrt = 99 try: - hmmmpid = int(mcprt.dl[0].dl[0].lpar.mother.mother.pid) + hm_mmpid = int(mcprt.dl[0].dl[0].lpar.mother.mother.pid) except: - hmmmpid = 99 + hm_mmpid = 99 try: - hpmmpid = int(mcprt.dl[0].dl[1].lpar.mother.mother.pid) + hp_mmpid = int(mcprt.dl[0].dl[1].lpar.mother.mother.pid) except: - hpmmpid = 99 + hp_mmpid = 99 try: - gmmpid = int(mcprt.dl[1].lpar.mother.mother.pid) + K_mmpid = int(mcprt.dl[1].lpar.mother.mother.pid) except: - gmmpid = 99 + K_mmpid = 99 try: - hmmmorvrt = int(mcprt.dl[0].dl[0].lpar.mother.mother.orvrt) + hm_mmorvrt = int(mcprt.dl[0].dl[0].lpar.mother.mother.orvrt) except: - hmmmorvrt = 99 + hm_mmorvrt = 99 try: - hpmmorvrt = int(mcprt.dl[0].dl[1].lpar.mother.mother.orvrt) + hp_mmorvrt = int(mcprt.dl[0].dl[1].lpar.mother.mother.orvrt) except: - hpmmorvrt = 99 + hp_mmorvrt = 99 try: - gmmorvrt = int(mcprt.dl[1].lpar.mother.mother.orvrt) + K_mmorvrt = int(mcprt.dl[1].lpar.mother.mother.orvrt) except: - gmmorvrt = 99 + K_mmorvrt = 99 try: bgt = int(mcprt.bgtype) @@ -604,25 +618,25 @@ class Ntuple: drm = -1 self.fill(f"{pre}_drmatch", drm) - self.fill(f"{pre}_hm_mpid", hmmpid) - self.fill(f"{pre}_hp_mpid", hpmpid) - self.fill(f"{pre}_K_mpid", gmpid) + self.fill(f"{pre}_hm_mpid", hm_mpid) + self.fill(f"{pre}_hp_mpid", hp_mpid) + self.fill(f"{pre}_K_mpid", K_mpid) - self.fill(f"{pre}_hm_mmpid", hmmmpid) - self.fill(f"{pre}_hp_mmpid", hpmmpid) - self.fill(f"{pre}_K_mmpid", gmmpid) + self.fill(f"{pre}_hm_mmpid", hm_mmpid) + self.fill(f"{pre}_hp_mmpid", hp_mmpid) + self.fill(f"{pre}_K_mmpid", K_mmpid) - self.fill(f"{pre}_hm_orvrt", hmorvrt) - self.fill(f"{pre}_hp_orvrt", hporvrt) - self.fill(f"{pre}_K_orvrt", gorvrt) + self.fill(f"{pre}_hm_orvrt", hm_orvrt) + self.fill(f"{pre}_hp_orvrt", hp_orvrt) + self.fill(f"{pre}_K_orvrt", K_orvrt) - self.fill(f"{pre}_hm_morvrt", hmmorvrt) - self.fill(f"{pre}_hp_morvrt", hpmorvrt) - self.fill(f"{pre}_K_morvrt", gmorvrt) + self.fill(f"{pre}_hm_morvrt", hm_morvrt) + self.fill(f"{pre}_hp_morvrt", hp_morvrt) + self.fill(f"{pre}_K_morvrt", K_morvrt) - self.fill(f"{pre}_hm_mmorvrt", hmmmorvrt) - self.fill(f"{pre}_hp_mmorvrt", hpmmorvrt) - self.fill(f"{pre}_K_mmorvrt", gmmorvrt) + self.fill(f"{pre}_hm_mmorvrt", hm_mmorvrt) + self.fill(f"{pre}_hp_mmorvrt", hp_mmorvrt) + self.fill(f"{pre}_K_mmorvrt", K_mmorvrt) # Trigger. self._fillTrg(pre, prt) @@ -787,7 +801,7 @@ class Ntuple: self.fill(f"{pre}_idx_pvr", -1) return (pre, self._save(pre, key)) -# End of class Ntuple +################################ End of class Ntuple ################################################ def fill_ntuple( ntuple: "Ntuple", diff --git a/bu2kdarkscalar_darkscalar2hh/README.md b/bu2kdarkscalar_darkscalar2hh/README.md index fd0c9bc37d..1907f83c8f 100644 --- a/bu2kdarkscalar_darkscalar2hh/README.md +++ b/bu2kdarkscalar_darkscalar2hh/README.md @@ -19,7 +19,7 @@ there are 3 prefixes used to label data: | Prefix Name | Prefix Number | Associated Data | |:-----------:|:-------------:|:---------------:| -| `tag` | 0 | reconstructed particles (i.e. $B^{\pm}$, dihadron) [object has an `endVertex`] | +| `tag` | 0 | reconstructed particles (i.e. $B^{\pm}$, dihadron (the dark scalar)) [object has an `endVertex`] | | `trk` | 1 | tracks (hadrons: $K^{\pm}$, $\pi^{\pm}$) [object does not have an `endVertex` (long-lived) and is charged] | The data for a particular prefix is accessed @@ -45,3 +45,84 @@ the data for the first daughter particle (prt0) would be stored at `tree.trk_<va - for a `vrt` this is the position vector - otherwise this is the object momentum three-vector + +### List of branches added to the ntuple + +*Br 0 :tag_idx_pvr : vector Vector for tags in the event giving the index of the associated pv in the array of associated ‘pvr’ objects found in this event. There should be one entry per tag. + +*Br 1 :tag_idx_prt0 : vector Vector for tags in the event giving the index of the first daughter particle +*Br 2 :tag_idx_prt1 : vector …second daughter… + +*Br 3 :tag_pre_prt0 : vector Vector for tags in the event giving the prefix of the first daughter (0=tag, 1=trk) +*Br 4 :tag_pre_prt1 : vector …second daughter… + +*Br 5 :tag_pid : vector Vector for tags in the event giving the particle ID number. + +*Br 6 :tag_px : vector x momentum of the tag +*Br 7 :tag_py : vector y +*Br 8 :tag_pz : vector z +*Br 9 :tag_e : vector computed energy using particle ID and four-momentum + +*Br 10 :tag_x : vector x position of the endvertex associated to the tag +*Br 11 :tag_y : vector y +*Br 12 :tag_z : vector z + +*Br 13 :tag_dx : vector uncertainty on x position, determined from its covariance matrix +*Br 14 :tag_dy : vector +*Br 15 :tag_dz : vector + +*Br 16 :tag_m : vector mass after vertex-fit +*Br 17 :tag_dm : vector ‘error’ on the vertex fit mass + + +*Br 18 :tag_mm : vector ‘measured-mass’ (i.e. before vertex fit) +*Br 19 :tag_dm : vector ‘error’ on the measured mass + +*Br 20 :tag_doca : vector maximum doca between the momenta of the daughters of this vertex +*Br 21 :tag_chi2 : vector chi2 of the vertex fit + +*Br 20 :tag_dtf_chi2 : vector chi2 of the DTF refit which requires that the particle should originate from its PV. -1 if fails + +*Br 21 :tag_ip : vector use loki distance calculator to find IP wrt associated PV +*Br 22 :tag_ip_chi2 : vector chi2 of that IP + +*Br 23 :tag_fd : vector uses same calculator to find distance between this vertex and pv +*Br 24 :tag_fd_chi2 : vector chi2 of that FD + +*Br 25 :tag_vt_tip : vector returns true if flight distance of a vertex intercepts a foil tip +*Br 26 :tag_vt_d : vector (offset, uncertainty weighted, scaled) material distance +*Br 27 :tag_tos0 : vector the index corresponds to the L0, Hlt1, or Hlt2 triggers +*Br 31 :tag_tis0 : vector ??? + +*Br 32 :trk_idx_pvr : vector index of the associated pvr in the list of pvr’s found for this event +*Br 33 :trk_pid : vector pid of the charged particles: always 11 (electron) +*Br 34 :trk_px : vector momentum component before any refitting +*Br 35 :trk_py : vector +*Br 36 :trk_pz : vector +*Br 37 :trk_e : vector +*Br 38 :trk_pnn_e : vector probnne +*Br 39 :trk_pnn_pi : vector probnnpi +*Br 40 :trk_pnn_ghost : vector probnnghost +*Br 41 :trk_ip : vector ip with respect to associated PV, calculated using the LoKi distance calculator +*Br 42 :trk_ip_chi2 : vector ipchi2 as above +*Br 43 :trk_vt_miss : vector returns true if the expected first hit of a track is missed, given a flight direction and vertex + +*Br 44 :trk_x0 : vector ??? position for first hit in the VELO +*Br 45 :trk_y0 : vector y… +*Br 46 :trk_z0 : vector z… +*Br 47 :trk_t0 : vector +*Br 48 :trk_p0 : vector + +*Br 49 :trk_x1 : vector ??? position for second hit in the VELO +*Br 50 :trk_y1 : vector +*Br 51 :trk_z1 : vector +*Br 52 :trk_t1 : vector +*Br 53 :trk_p1 : vector + +*Br 59 :pvr_x : vector position and uncertainty on any associated primary vertex related to a filled particle +*Br 60 :pvr_y : vector +*Br 61 :pvr_z : vector +*Br 62 :pvr_dx : vector +*Br 63 :pvr_dy : vector +*Br 64 :pvr_dz : vector + diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index d4354840b4..ba431d2f95 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,8 +1,14 @@ {%- set polarities = ["Down", "Up"]%} +{%- set datasets = [ + ('KK', 18, 6500, '18', '34NoPrescalingFlagged'), + ('PiPi', 18, 6500, '18', '34NoPrescalingFlagged'), +]%} -{%- for polarity in polarities %} -B2KKK_2018_Mag{{polarity}}_job: +{%- for hh, year, energy, reco, strip in datasets %} + {%- for polarity in polarities %} + +my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_job: application: DaVinci/v46r8 wg: QEE automatically_configure: yes @@ -16,17 +22,20 @@ B2KKK_2018_Mag{{polarity}}_job: - job.py input: - bk_query: /MC/2018/Beam6500GeV-2018-MagDown-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST - # For MC - # /MC/2018/Beam6500GeV-2018-MagDown-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST + bk_query: /MC/2018/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco{{reco}}/Turbo05-WithTurcal/Stripping{{strip}}/23103008/ALLSTREAMS.DST + # For KK MC + # /MC/2018/Beam6500GeV-2018-Mag{{polarity}}-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST + # For PiPi MC + # # For data # /LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST - output: B2KKK.ROOT + output: B2K{{hh}}.ROOT # For MC # B2KKK.ROOT # For data # bu2kdarkscalar_darkscalar2hh.ROOT + {%- endfor %} {%- endfor %} \ No newline at end of file diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index e785c149b8..d29b6b2d38 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -105,7 +105,7 @@ DaVinci().Lumi = False if hh == "KK": candloc = "/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles" elif hh == "PiPi": - candloc = "/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles" + candloc = "/Event/AllStreams/Phys/B2KX2PiPiDarkBosonLine/Particles" #candloc_KK = "/Event/Leptonic/Phys/B2KX2KKDarkBosonLine/Particles" -- GitLab From b791988679b2253207ff5df944b5b4971d67b63c Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 26 Jul 2024 16:57:03 +0100 Subject: [PATCH 10/70] updated readme --- bu2kdarkscalar_darkscalar2hh/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bu2kdarkscalar_darkscalar2hh/README.md b/bu2kdarkscalar_darkscalar2hh/README.md index 1907f83c8f..053db7f67e 100644 --- a/bu2kdarkscalar_darkscalar2hh/README.md +++ b/bu2kdarkscalar_darkscalar2hh/README.md @@ -46,7 +46,7 @@ the data for the first daughter particle (prt0) would be stored at `tree.trk_<va - otherwise this is the object momentum three-vector -### List of branches added to the ntuple +### List of branches added to the ntuple (INCOMPLETE) *Br 0 :tag_idx_pvr : vector Vector for tags in the event giving the index of the associated pv in the array of associated ‘pvr’ objects found in this event. There should be one entry per tag. -- GitLab From 62884aefc8d0404a30015a76e67c68ad080266f3 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 2 Aug 2024 16:13:33 +0100 Subject: [PATCH 11/70] fixed issue with it not finding mcp --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 50 +++++++++++++++----- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 48 +++++++++++-------- bu2kdarkscalar_darkscalar2hh/hh_PiPi.py | 2 +- bu2kdarkscalar_darkscalar2hh/info.yaml | 45 +++++++++--------- bu2kdarkscalar_darkscalar2hh/job.py | 24 +++++++--- 5 files changed, 108 insertions(+), 61 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 32740602ef..52e413446a 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -165,7 +165,7 @@ class MCEvent: """ Class for processing MC events. """ - def __init__(self, all_Bs, mctool): + def __init__(self, all_Bs, mctool, hh): """ Initializes MCEvent class. Creates list of rec level all_Bs, adds associated daughter particles to all_Bs daughter list. @@ -183,6 +183,7 @@ class MCEvent: of MCEvent [bool] Bs : list of combined all_Bs with daughter particles in daughter list [list(MCPart)] """ + print(f"hh is {hh}") self.hcuts = False self.links = False self.iscategorized = False @@ -204,8 +205,14 @@ class MCEvent: hadron = MCPart(ddp) hadron.update(newpnnk=PROBNNk(ddp),newpnnpi=PROBNNpi(ddp),newpnng=PROBNNghost(ddp),newtrgp=TRGHOSTPROB(ddp)) print(hadron.pid) - if hadron.pid == 321: hadrons[0] = hadron - elif hadron.pid == -321: hadrons[1] = hadron + + if hh == "KK": + hhPID = 321 + elif hh == "PiPi": + hhPID = 211 + + if hadron.pid == hhPID: hadrons[0] = hadron + elif hadron.pid == -hhPID: hadrons[1] = hadron svcoor = [dpsv.position().X(),dpsv.position().Y(),dpsv.position().Z()] dps[0] = MCPart(dp,dl=hadrons,sv=svcoor) B = MCPart(all_B,dl=dps) @@ -359,22 +366,37 @@ class MCEvent: if not self.links: self.linkpars() for B in self.Bs: + print("looping over Bs") if not B.alllinked: - #print("not all Bs are linked") + print("not all Bs are linked") for recp in [B.dl[1],B.dl[0].dl[0],B.dl[0].dl[1]]: + print("looping over recps") if not recp.lpar: + print("if recp has no linked particle") mindr = 99999999999 parlink = None + print(f"mcp store is {mcpstore}") + #print(f"mcp store contains {len(mcpstore)} mc particles") + thereExistsMCPIDequalRECPID = False for mcp in mcpstore: - #print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") + print(f"mc particle is {mcp}") + print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") if MCID(mcp) == recp.pid: + thereExistsMCPIDequalRECPID = True + print("mc pid is same as rec particle pid") dphi = MCPHI(mcp) - PHI(recp.par) deta = MCETA(mcp) - ETA(recp.par) deltar = sqrt(dphi**2 + deta**2) - if deltar < mindr: mindr = deltar; parlink = mcp - """ print(f"deltar is {deltar}") - print(f"mindr is {mindr}") - print(f"parlink is {parlink}") """ + print(f"deltar is {deltar}") + print(f"mindr is {mindr}") + if deltar < mindr: + mindr = deltar + print(f"mindr is {mindr}") + parlink = mcp + print(f"parlink is {parlink}") + if thereExistsMCPIDequalRECPID == False: + print("no mc particle with same pid as rec particle") + break recp.update(newlpar=MCPart(parlink,orvrt=parlink.originVertex().type())) if MCID(parlink) == recp.pid: recp.update(newclink=True) else: recp.update(newclink=False) @@ -391,7 +413,13 @@ class MCEvent: except: recp.lpar.update(newmother=None) - if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: + # i guess L418 shouldn't be being called - is this checking if all particles have been linked? + # in which case we should be checking if all particles have been linked to the same particle type + if thereExistsMCPIDequalRECPID == False: + print("no mc particle with same pid as rec particle") + break + + if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: B.update(newisdrmatched=True) if B.dl[1].lpar.pid == B.dl[1].pid and B.dl[0].dl[0].lpar.pid == B.dl[0].dl[0].pid and B.dl[0].dl[1].lpar.pid == B.dl[0].dl[1].pid: B.update(newcorlinked=True) @@ -489,7 +517,7 @@ class MCEvent: try: fillKpid = K.lpar.mother.pid except: fillKpid = 0 - if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(B.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle + if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(B.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle and B has 3 deacy products #if K is K+ this must be a B+, if K is K- this must be a B- if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: B.update(newbgtype=0) if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: B.update(newbgtype=0) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index e0d1fb4f43..e5c6a2b514 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -72,7 +72,7 @@ class Ntuple: self.ttree = ROOT.TTree("DecayData", "DecayData") self.pvrs = "Rec/Vertex/Primary" if DaVinci().InputType == "MDST": - self.pvrs = "Leptonic/" + self.pvrs + self.pvrs = "AllStreams/" + self.pvrs vrsVrt = ["x", "y", "z", "dx", "dy", "dz"] vrsMom = ["px", "py", "pz", "e"] vrsTrk = [ @@ -322,8 +322,8 @@ class Ntuple: ########################################################################### def _fillgenMomPID(self, pre: "str", mcp, dptype): """Fill generator level MC momentum and pid for a particle.""" - if mcp: # mcp: monte carlo particle - if mcp.pid in (310, 211, -211, 321, -321, 521, -521): + if mcp: # mcp: monte carlo particle, this is a boolean + if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): @@ -333,19 +333,31 @@ class Ntuple: elif mcp.pid in (211, 321): dptype = "hp" print(f"found mcp particle which is hp with pid {mcp.pid}") + elif dptype == "K": + if mcp.pid in (321, -321): + dptype = "K" + print(f"found mcp particle which is K with pid {mcp.pid}") pass else: raise ValueError(f"mcp.pid {mcp.pid} not recognized") - mom = mcp.lpar.p - lpid = mcp.lpar.pid try: - lpid = int(mcp.lpar.pid) + mom = mcp.lpar.p + momPx = mom.Px() + momPy = mom.Py() + momPz = mom.Pz() + momE = mom.E() except: - lpid = 99 - self.fill(f"{pre}_{dptype}_lpx", mom.Px()) # l denotes that it is variable associated to a linked particle - self.fill(f"{pre}_{dptype}_lpy", mom.Py()) - self.fill(f"{pre}_{dptype}_lpz", mom.Pz()) - self.fill(f"{pre}_{dptype}_le", mom.E()) + momPx = momPy = momPz = momE = 0.0 + + try: + lpid = int(mcp.lpar.pid) # try get the linked pid + except: + lpid = 99 # if not found, i.e. couldn't link this particle, set to 99 + + self.fill(f"{pre}_{dptype}_lpx", momPx) # l denotes that it is variable associated to a linked particle + self.fill(f"{pre}_{dptype}_lpy", momPy) + self.fill(f"{pre}_{dptype}_lpz", momPz) + self.fill(f"{pre}_{dptype}_le", momE) self.fill(f"{pre}_{dptype}_lpid", lpid) else: for dptype in ["hm", "hp", "K"]: @@ -536,7 +548,7 @@ class Ntuple: if mcprt: self._fillgenMomPID(pre, mcprt.dl[1], "K") # B's daughter: bachelor kaon - self._fillgenMomPID(pre, mcprt.dl[0].dl[0], "hadron0") # B's daughter, dark scalar,has two daughters itself + self._fillgenMomPID(pre, mcprt.dl[0].dl[0], "hadron0") # B's daughter, dark scalar,has two daughters itself, the first we will call hadron0 self._fillgenMomPID(pre, mcprt.dl[0].dl[1], "hadron1") # B's daughter: hadron else: self._fillgenMomPID(pre, mcprt, hh, "B") @@ -843,15 +855,11 @@ def fill_ntuple( print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") if DaVinci().Simulation: - mct = MCEventTools.MCEvent(all_Bparticles, genTool) # mct: monte carlo tool + mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool #print(mct) mct.linkpars() - mcpstore = get_particles( - "AllStreams/MC/Particles" - if DaVinci().InputType == "MDST" - else "MC/Particles" - ) - print(f"got {len(mcpstore)} B mesons from MC/Particles") + mcpstore = get_particles('/Event/AllStreams/MC/Particles') + print(f"got {len(mcpstore)} B mesons from mcp store") mct.deltarlink(mcpstore) mct.categorize() else: @@ -866,6 +874,8 @@ def fill_ntuple( boolVal = ntuple.fillPrt(*args, **kwargs, check=True) print("I find bool is {0}".format(boolVal)) + + # if not all parricles are linked if ntuple.fillPrt(*args, **kwargs, check=True): ntuple.fillPrt(*args, **kwargs) isfilled = True diff --git a/bu2kdarkscalar_darkscalar2hh/hh_PiPi.py b/bu2kdarkscalar_darkscalar2hh/hh_PiPi.py index 21570c9612..0504f41322 100644 --- a/bu2kdarkscalar_darkscalar2hh/hh_PiPi.py +++ b/bu2kdarkscalar_darkscalar2hh/hh_PiPi.py @@ -1,4 +1,4 @@ -"""Set head for job.py. +"""Set hh for job.py. This file should be passed as a command-line option to job.py. """ diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index ba431d2f95..0b1b2bc94f 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,14 +1,24 @@ {%- set polarities = ["Down", "Up"]%} -{%- set datasets = [ - ('KK', 18, 6500, '18', '34NoPrescalingFlagged'), - ('PiPi', 18, 6500, '18', '34NoPrescalingFlagged'), -]%} +{%- set MCdatasets = [('PiPi', 18, 12103024, 6500, '18', '34NoPrescalingFlagged')]%} +{%- set datasets = ["PiPi", "KK"]%} +{%- for polarity in polarities %} -{%- for hh, year, energy, reco, strip in datasets %} - {%- for polarity in polarities %} +{%- for hh in datasets %} -my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_job: +my_B2KDarkScalar_DarkScalar2{{hh}}_2018_Mag{{polarity}}_Data_job: + input: + bk_query: "/LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST" + options: + command: + - python + files: + - job.py + - head_{{hh}}.py + +{%- for hh, year, eventnumber, energy, reco, strip in MCdatasets %} + +my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: application: DaVinci/v46r8 wg: QEE automatically_configure: yes @@ -20,22 +30,11 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_job: - python files: - job.py + - head_{{hh}}.py input: - bk_query: /MC/2018/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco{{reco}}/Turbo05-WithTurcal/Stripping{{strip}}/23103008/ALLSTREAMS.DST - # For KK MC - # /MC/2018/Beam6500GeV-2018-Mag{{polarity}}-Fix1-Pythia8/Sim10c/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/23103008/ALLSTREAMS.DST - # For PiPi MC - # - # For data - # /LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST - - output: B2K{{hh}}.ROOT - # For MC - # B2KKK.ROOT - # For data - # bu2kdarkscalar_darkscalar2hh.ROOT - - - {%- endfor %} + bk_query: /MC/20{{year}}/{{eventnumber}}/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Nu1.6-25ns-Pythia8/Sim09h/Trig0x617d18a4/Reco{{year}}/Turbo05-WithTurcal/Stripping{{strip}}/ALLSTREAMS.MDST + + +{%- endfor %} {%- endfor %} \ No newline at end of file diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index d29b6b2d38..96745fd31e 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -102,15 +102,25 @@ DaVinci().Lumi = False # ================================================================================= #candidate locations +# IMPORTANT WARNING!!! +# is the data is saved in microDST format we must use '/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles' +# +if DaVinci.Simulation: + stream = 'AllStreams' +else: + stream = 'Leptonic' + +""" if DaVinci().InputType == "MDST": + stream = 'Leptonic' +elif DaVinci().InputType == "DST": + stream = 'AllStreams' """ + if hh == "KK": - candloc = "/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles" + candloc = '/Event/' + stream + '/Phys/B2KX2KKDarkBosonLine/Particles' elif hh == "PiPi": - candloc = "/Event/AllStreams/Phys/B2KX2PiPiDarkBosonLine/Particles" - - -#candloc_KK = "/Event/Leptonic/Phys/B2KX2KKDarkBosonLine/Particles" -#candloc_PiPi = "/Event/Leptonic/Phys/B2KX2PiPiDarkBosonLine/Particles" + candloc = '/Event/' + stream + '/Phys/B2KX2PiPiDarkBosonLine/Particles' +print(f"Using candidate location: {candloc}") # ================================================================================= # TisTos configuration. @@ -140,7 +150,7 @@ while (NOfEvents == -1) or (event < NOfEvents): if not bool(tes["/Event"]): break event += 1 - print("found evt") + print(f"found event {event}") fill_ntuple(ntuple, tes, candloc, hh, genTool) print("filled") -- GitLab From 82cfc59b28c627756b6d766519a760d709ea6735 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 5 Aug 2024 14:39:55 +0100 Subject: [PATCH 12/70] change root in tes for mc productions --- bu2kdarkscalar_darkscalar2hh/job.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 96745fd31e..8057fb822b 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -12,8 +12,8 @@ import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -import sys -sys.path.append("../") +#import sys +#sys.path.append("../") from bu2kdarkscalar_darkscalar2hh.Ntuple import fill_ntuple, Ntuple @@ -107,6 +107,8 @@ DaVinci().Lumi = False # if DaVinci.Simulation: stream = 'AllStreams' + DaVinci().RootInTES = "/Event/AllStreams" + else: stream = 'Leptonic' -- GitLab From 9c55c8ce8414c53a3e47c0ac4cffd72b7b98e70b Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 5 Aug 2024 14:49:42 +0100 Subject: [PATCH 13/70] remove dynamic --- ...calar_darkScalar2hh_2018_MagDown_job_autoconf.py | 13 ------------- ...kScalar_darkScalar2hh_2018_MagUp_job_autoconf.py | 13 ------------- 2 files changed, 26 deletions(-) delete mode 100644 dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagDown_job_autoconf.py delete mode 100644 dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagUp_job_autoconf.py diff --git a/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagDown_job_autoconf.py b/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagDown_job_autoconf.py deleted file mode 100644 index f079315413..0000000000 --- a/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagDown_job_autoconf.py +++ /dev/null @@ -1,13 +0,0 @@ -from Configurables import DaVinci -try: - DaVinci().Turbo = False -except AttributeError: - # Older DaVinci versions don't support Turbo at all - pass - -DaVinci().InputType = 'MDST' -DaVinci().DataType = '2018' -DaVinci().Simulation = False -DaVinci().Lumi = True -from Configurables import CondDB -CondDB(LatestGlobalTagByDataType=DaVinci().DataType) \ No newline at end of file diff --git a/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagUp_job_autoconf.py b/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagUp_job_autoconf.py deleted file mode 100644 index f079315413..0000000000 --- a/dynamic/bu2kdarkscalar_darkscalar2hh/Bu2KdarkScalar_darkScalar2hh_2018_MagUp_job_autoconf.py +++ /dev/null @@ -1,13 +0,0 @@ -from Configurables import DaVinci -try: - DaVinci().Turbo = False -except AttributeError: - # Older DaVinci versions don't support Turbo at all - pass - -DaVinci().InputType = 'MDST' -DaVinci().DataType = '2018' -DaVinci().Simulation = False -DaVinci().Lumi = True -from Configurables import CondDB -CondDB(LatestGlobalTagByDataType=DaVinci().DataType) \ No newline at end of file -- GitLab From 4ea3fdae1a494936d11b98f9f236b5caf61aecea Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 5 Aug 2024 15:06:08 +0100 Subject: [PATCH 14/70] tidy --- bu2kdarkscalar_darkscalar2hh/job.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 8057fb822b..9f183a6d64 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -18,7 +18,6 @@ import ROOT from bu2kdarkscalar_darkscalar2hh.Ntuple import fill_ntuple, Ntuple - # ================================================================================= # The next lines allow configuration to be done in separate files in a gaudirun-like way # This is needed for AnalysisProductions, but also helps us. -- GitLab From f33c994a0aa747a30a6f8a1eb4fd0703ccd871eb Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 5 Aug 2024 15:56:45 +0100 Subject: [PATCH 15/70] fixed info.yaml formatting --- bu2kdarkscalar_darkscalar2hh/info.yaml | 27 ++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 0b1b2bc94f..5cee402ca8 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -7,6 +7,12 @@ {%- for hh in datasets %} my_B2KDarkScalar_DarkScalar2{{hh}}_2018_Mag{{polarity}}_Data_job: + application: DaVinci/v46r8 + wg: QEE + automatically_configure: yes + turbo: no + inform: + - eleanor.whiter@cern.ch input: bk_query: "/LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST" options: @@ -14,7 +20,12 @@ my_B2KDarkScalar_DarkScalar2{{hh}}_2018_Mag{{polarity}}_Data_job: - python files: - job.py - - head_{{hh}}.py + - hh_{{hh}}.py + output: b2kdarkscalar.root + +{%- endfor %} + + {%- for hh, year, eventnumber, energy, reco, strip in MCdatasets %} @@ -25,16 +36,16 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: turbo: no inform: - eleanor.whiter@cern.ch + input: + bk_query: /MC/20{{year}}/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Nu1.6-25ns-Pythia8/Sim09h/Trig0x617d18a4/Reco{{year}}/Turbo05-WithTurcal/Stripping{{strip}}/{{eventnumber}}/ALLSTREAMS.MDST options: - command: + command: - python - files: + files: - job.py - - head_{{hh}}.py - - input: - bk_query: /MC/20{{year}}/{{eventnumber}}/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Nu1.6-25ns-Pythia8/Sim09h/Trig0x617d18a4/Reco{{year}}/Turbo05-WithTurcal/Stripping{{strip}}/ALLSTREAMS.MDST - + - hh_{{hh}}.py + output: b2khh.root {%- endfor %} + {%- endfor %} \ No newline at end of file -- GitLab From 9e416b48ee302ed81afe9881b8445f8509da96bd Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 5 Aug 2024 16:17:09 +0100 Subject: [PATCH 16/70] test for only data --- bu2kdarkscalar_darkscalar2hh/info.yaml | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 5cee402ca8..0e92b2d7ab 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,5 +1,4 @@ {%- set polarities = ["Down", "Up"]%} -{%- set MCdatasets = [('PiPi', 18, 12103024, 6500, '18', '34NoPrescalingFlagged')]%} {%- set datasets = ["PiPi", "KK"]%} {%- for polarity in polarities %} @@ -25,27 +24,4 @@ my_B2KDarkScalar_DarkScalar2{{hh}}_2018_Mag{{polarity}}_Data_job: {%- endfor %} - - -{%- for hh, year, eventnumber, energy, reco, strip in MCdatasets %} - -my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: - application: DaVinci/v46r8 - wg: QEE - automatically_configure: yes - turbo: no - inform: - - eleanor.whiter@cern.ch - input: - bk_query: /MC/20{{year}}/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Nu1.6-25ns-Pythia8/Sim09h/Trig0x617d18a4/Reco{{year}}/Turbo05-WithTurcal/Stripping{{strip}}/{{eventnumber}}/ALLSTREAMS.MDST - options: - command: - - python - files: - - job.py - - hh_{{hh}}.py - output: b2khh.root - -{%- endfor %} - {%- endfor %} \ No newline at end of file -- GitLab From aae02d01753df47fb7db58e7ffa9b19e1b04d2ff Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 6 Aug 2024 12:02:31 +0100 Subject: [PATCH 17/70] removed unecessary file --- ap_merger.py | 112 --------------------------------------------------- 1 file changed, 112 deletions(-) delete mode 100644 ap_merger.py diff --git a/ap_merger.py b/ap_merger.py deleted file mode 100644 index cf7dc2fac6..0000000000 --- a/ap_merger.py +++ /dev/null @@ -1,112 +0,0 @@ -############################################################################### -# (c) Copyright 2022 CERN for the benefit of the LHCb Collaboration # -# # -# This software is distributed under the terms of the GNU General Public # -# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". # -# # -# In applying this licence, CERN does not waive the privileges and immunities # -# granted to it by virtue of its status as an Intergovernmental Organization # -# or submit itself to any jurisdiction. # -############################################################################### -import subprocess -import sys -import tempfile -import xml.etree.ElementTree as ET -from pathlib import Path - -# pylint: disable=import-error -from GaudiConf.LbExec.options import FileFormats -from GaudiConf.LbExec.options import Options as OptionsBase - -SUMMARY_XML_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?> -<summary xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:noNamespaceSchemaLocation="$XMLSUMMARYBASEROOT/xml/XMLSummary.xsd"> - <success>True</success> - <step>finalize</step> - <usage><stat unit="KB" useOf="MemoryMaximum">0</stat></usage> - <input> -{input_files} - </input> - <output> -{output_files} - </output> -</summary> -""" -XML_FILE_TEMPLATE = ' <file GUID="" name="{name}" status="full">{n}</file>' -ALG_TO_CODE = { - "ZLIB": 1, - "LZMA": 2, - "LZ4": 4, - "ZSTD": 5, -} - - -class Options(OptionsBase): - """Conditions""" - - input_type: FileFormats = FileFormats.ROOT - simulation: None = False - data_type: None = None - - -def read_xml_file_catalog(xml_file_catalog): - if xml_file_catalog is None: - return {} - - tree = ET.parse(xml_file_catalog) - pfn_lookup: dict[str, list[str]] = {} - for file in tree.findall("./File"): - lfns = [x.attrib.get("name") for x in file.findall("./logical/lfn")] - if len(lfns) == 0: - continue - if len(lfns) > 1: - raise NotImplementedError(lfns) - lfn = lfns[0] - pfn_lookup[f"LFN:{lfn}"] = [ - x.attrib.get("name") for x in file.findall("./physical/pfn") - ] - return pfn_lookup - - -def resolve_input_files(input_files, file_catalog): - resolved = [] - for input_file in input_files: - if input_file.startswith("LFN:"): - print("Resolved", input_file, "to", file_catalog[input_file][0]) - input_file = file_catalog[input_file][0] - resolved.append(input_file) - return resolved - - -def hadd(options: Options, compression: str = "ZSTD:4"): - file_catalog = read_xml_file_catalog(options.xml_file_catalog) - input_files = resolve_input_files(options.input_files, file_catalog) - - alg, level = compression.split(":") - flags = [f"-f{ALG_TO_CODE[alg]}{int(level):02d}"] - flags += ["-j", f"{options.n_threads}"] - flags += ["-n", f"{max(10, options.n_threads*2)}"] - - with tempfile.NamedTemporaryFile(mode="wt") as tmpfile: - tmpfile.write("\n".join(input_files)) - tmpfile.flush() - cmd = ["hadd"] + flags + [options.ntuple_file, f"@{tmpfile.name}"] - print("Running", cmd) - subprocess.run(cmd, check=True) - - summary_xml = SUMMARY_XML_TEMPLATE.format( - input_files="\n".join( - XML_FILE_TEMPLATE.format( - name=name if name.startswith("LFN:") else f"PFN:{name}", n=1 - ) - for name in options.input_files - ), - output_files=XML_FILE_TEMPLATE.format( - name=f"PFN:{options.ntuple_file}", n=len(input_files) - ), - ) - if options.xml_summary_file: - print("Writing XML summary to", options.xml_summary_file) - Path(options.xml_summary_file).write_text(summary_xml) - - print("All done") - sys.exit(0) -- GitLab From 10d68d0b48cf10f5019415f13ba3382dcb3a63d4 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 6 Aug 2024 13:40:45 +0100 Subject: [PATCH 18/70] removed inputdata - only meant for local test --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 12 ++++++------ bu2kdarkscalar_darkscalar2hh/inputData.py | 15 --------------- bu2kdarkscalar_darkscalar2hh/job.py | 1 + 3 files changed, 7 insertions(+), 21 deletions(-) delete mode 100644 bu2kdarkscalar_darkscalar2hh/inputData.py diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 52e413446a..5a503f5955 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -498,7 +498,7 @@ class MCEvent: def categorize(self): """ - Categorizes the type of decay (i.e. type of background, signal). + Categorizes the type of decay (i.e. type of background, signal) and sets dectype as one of the follwoing numbers. SOURCE TYPES ------------ @@ -517,10 +517,10 @@ class MCEvent: try: fillKpid = K.lpar.mother.pid except: fillKpid = 0 - if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(B.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle and B has 3 deacy products + if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(B.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle and B has 3 decay products #if K is K+ this must be a B+, if K is K- this must be a B- - if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: B.update(newbgtype=0) - if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: B.update(newbgtype=0) + if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: B.update(newbgtype=1) + if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: B.update(newbgtype=1) for dp in B.dl: @@ -604,7 +604,7 @@ class MCEvent: if B.isdrmatched: drmatch = 1 else: drmatch = 0 else: - drmatch = 9 + drmatch = 99 return drmatch @@ -619,7 +619,7 @@ class MCEvent: OUTPUTS ------- - B: MCPart object of the corresponding B/eta [MCPart] + B: MCPart object of the corresponding B [MCPart] """ B = None if ID(part) == 310: diff --git a/bu2kdarkscalar_darkscalar2hh/inputData.py b/bu2kdarkscalar_darkscalar2hh/inputData.py deleted file mode 100644 index 22ef3691c8..0000000000 --- a/bu2kdarkscalar_darkscalar2hh/inputData.py +++ /dev/null @@ -1,15 +0,0 @@ -from GaudiConf import IOHelper - -''' -# Real Data -IOHelper("ROOT").inputFiles(["/home/exw330/data/00092412_00000138_1.leptonic.mdst"]) -from Configurables import DaVinci -DaVinci().DataType = "2018" -DaVinci().InputType = "MDST" - -''' -# Monte Carlo -IOHelper("ROOT").inputFiles(["/home/exw330/MC/00217278_00000014_1.AllStreams.dst"]) -from Configurables import DaVinci -DaVinci().InputType = 'DST' -DaVinci().DataType = '2018' diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 9f183a6d64..e003a2aa57 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -93,6 +93,7 @@ try: shutil.copy(this_dir / "pars.root", Path.cwd()) except ModuleNotFoundError: NOfEvents = DaVinci().EvtMax + print(f"number of events is {NOfEvents}") output_filename = DaVinci().TupleFile # FIXME: Turn off lumi tuple or it overwrites ntuple. Cleverer solution possible -- GitLab From 05223efa866d7f723502e5893a2fc66801f7715f Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 6 Aug 2024 14:35:54 +0100 Subject: [PATCH 19/70] test for mc --- bu2kdarkscalar_darkscalar2hh/info.yaml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 0e92b2d7ab..96629dca92 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,11 +1,14 @@ {%- set polarities = ["Down", "Up"]%} -{%- set datasets = ["PiPi", "KK"]%} {%- for polarity in polarities %} -{%- for hh in datasets %} -my_B2KDarkScalar_DarkScalar2{{hh}}_2018_Mag{{polarity}}_Data_job: +{%- set MCdatasets = [('PiPi', 18, 12103024, 6500, '18', '34NoPrescalingFlagged')]%} + +{%- for polarity in polarities %} +{%- for hh, year, eventnumber, energy, reco, strip in MCdatasets %} + +my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: application: DaVinci/v46r8 wg: QEE automatically_configure: yes @@ -13,15 +16,16 @@ my_B2KDarkScalar_DarkScalar2{{hh}}_2018_Mag{{polarity}}_Data_job: inform: - eleanor.whiter@cern.ch input: - bk_query: "/LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST" + bk_query: /MC/20{{year}}/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Nu1.6-25ns-Pythia8/Sim09h/Trig0x617d18a4/Reco{{year}}/Turbo05-WithTurcal/Stripping{{strip}}/{{eventnumber}}/ALLSTREAMS.MDST options: - command: + command: - python - files: + files: - job.py - hh_{{hh}}.py - output: b2kdarkscalar.root + output: b2khh.root {%- endfor %} + {%- endfor %} \ No newline at end of file -- GitLab From 0f4591e57998a1afc330b40bfb4df90b7b8a8f90 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 6 Aug 2024 14:40:09 +0100 Subject: [PATCH 20/70] typo --- bu2kdarkscalar_darkscalar2hh/info.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 96629dca92..59338bf5db 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,8 +1,4 @@ {%- set polarities = ["Down", "Up"]%} - -{%- for polarity in polarities %} - - {%- set MCdatasets = [('PiPi', 18, 12103024, 6500, '18', '34NoPrescalingFlagged')]%} {%- for polarity in polarities %} @@ -26,6 +22,4 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: output: b2khh.root {%- endfor %} - - {%- endfor %} \ No newline at end of file -- GitLab From 092fdff99df7efd6b4b0673c9d98d00e168817e5 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 6 Aug 2024 16:15:09 +0100 Subject: [PATCH 21/70] fixed to work siimulatenously for data --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 6 +++++- bu2kdarkscalar_darkscalar2hh/info.yaml | 24 ++++++++++++++++++++++++ bu2kdarkscalar_darkscalar2hh/job.py | 5 ++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index e5c6a2b514..f0e4c96282 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -71,8 +71,12 @@ class Ntuple: self.tfile = ROOT.TFile(output_filename, "RECREATE", "", 207) self.ttree = ROOT.TTree("DecayData", "DecayData") self.pvrs = "Rec/Vertex/Primary" - if DaVinci().InputType == "MDST": + #if DaVinci().InputType == "MDST": + # self.pvrs = "AllStreams/" + self.pvrs + if DaVinci().Simulation: self.pvrs = "AllStreams/" + self.pvrs + else: + self.pvrs = "/Event/Leptonic/" + self.pvrs vrsVrt = ["x", "y", "z", "dx", "dy", "dz"] vrsMom = ["px", "py", "pz", "e"] vrsTrk = [ diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 59338bf5db..acd6d538d9 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,4 +1,5 @@ {%- set polarities = ["Down", "Up"]%} +{%- set datasets = ["PiPi", "KK"]%} {%- set MCdatasets = [('PiPi', 18, 12103024, 6500, '18', '34NoPrescalingFlagged')]%} {%- for polarity in polarities %} @@ -22,4 +23,27 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: output: b2khh.root {%- endfor %} + + +{%- for hh in datasets %} + +my_B2KDarkScalar_DarkScalar2{{hh}}_2018_Mag{{polarity}}_Data_job: + application: DaVinci/v46r8 + wg: QEE + automatically_configure: yes + turbo: no + inform: + - eleanor.whiter@cern.ch + input: + bk_query: "/LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST" + options: + command: + - python + files: + - job.py + - hh_{{hh}}.py + output: b2kdarkscalar.root + +{%- endfor %} + {%- endfor %} \ No newline at end of file diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index e003a2aa57..4f83d69785 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -105,12 +105,15 @@ DaVinci().Lumi = False # IMPORTANT WARNING!!! # is the data is saved in microDST format we must use '/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles' # -if DaVinci.Simulation: +if DaVinci().Simulation: stream = 'AllStreams' DaVinci().RootInTES = "/Event/AllStreams" + print(f"stream set as {stream}") else: stream = 'Leptonic' + #DaVinci().RootInTES = "/Event/AllStreams" + print(f"stream set as {stream}") """ if DaVinci().InputType == "MDST": stream = 'Leptonic' -- GitLab From ac27941f4bcd61597ad68974362fe6b5753d72d4 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 7 Aug 2024 13:44:30 +0100 Subject: [PATCH 22/70] chnaged categorisation --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 21 +++++++++++++--- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 26 ++++++++++++++++---- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 5a503f5955..83e95f7dd7 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -174,6 +174,7 @@ class MCEvent: ------ all_Bs : B+/B-s from the TES mctool : IParticle2MCWeightedAssociator instance to be used + hh : decay mode (KK or PiPi) INTERNAL MEMBERS ---------------- @@ -363,6 +364,14 @@ class MCEvent: self.links = True def deltarlink(self, mcpstore): + """ + MC truth matching using delta r matching. + + INPUTS + ------ + self: MCEvent instance + mcpstore: list of MC particles from the TES + """ if not self.links: self.linkpars() for B in self.Bs: @@ -444,7 +453,7 @@ class MCEvent: hadron_plus_lin = [B.dl[0].dl[1].lpar.par,B.dl[0].dl[1].lpar.mother.par] else: hadron_plus_lin = [B.dl[0].dl[1].lpar.par] - sharelin = [] + sharelin = [] # shared lineage for i in range(len(hadron_minus_lin)): for j in range(len(hadron_plus_lin)): for k in range(len(Klin)): @@ -517,10 +526,16 @@ class MCEvent: try: fillKpid = K.lpar.mother.pid except: fillKpid = 0 - if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(B.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle and B has 3 decay products + if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(K.lpar.par.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle and B (accessed by K origin vertex) has 3 decay products #if K is K+ this must be a B+, if K is K- this must be a B- if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: B.update(newbgtype=1) - if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: B.update(newbgtype=1) + elif K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: B.update(newbgtype=1) + + elif hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par and hadron_minus.lpar.mother.pid == 310: # Check h+ and h- came from the same particle and that particle was a Ks + if K.lpar.mother == hadron_minus.lpar.mother.mother.par == hadron_plus.lpar.mother.mother.par: # Check K, hm and hp all came from the same particle + if abs(K.lpar.mother.pid) == 521: # check this was a B + self.dec: B.update(newbgtype=0) + for dp in B.dl: diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index f0e4c96282..fb752aba32 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -286,7 +286,10 @@ class Ntuple: ########################################################################### def _fillMM(self, pre: "str", prt): - """Fill measured mass for a particle. + """ + Fill measured mass. + + mm: measured mass dmm: delta measured mass (the error on the measured mass) @@ -296,7 +299,9 @@ class Ntuple: ########################################################################### def _fillM(self, pre: "str", mom): - """Fill mass from a particle's momentum.""" + """ + Fill mass. + """ try: m_class = mom.m() m = m_class.value() @@ -325,12 +330,22 @@ class Ntuple: ########################################################################### def _fillgenMomPID(self, pre: "str", mcp, dptype): - """Fill generator level MC momentum and pid for a particle.""" + """ + Fill generator level MC momentum and pid for a particle. + + INPUTS + ------ + + pre: prefix for the variable name, either "tag" or "trk" + mcp: monte carlo particle + dptype: daughter particle type, can be: "K", "hadron0", "hadron1" + """ if mcp: # mcp: monte carlo particle, this is a boolean if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): + #only when we inspect pid do we know the charges associated to hadron0 and hadron1 if mcp.pid in (-211, -321): dptype = "hm" print(f"found mcp particle which is hm with pid {mcp.pid}") @@ -351,7 +366,7 @@ class Ntuple: momPz = mom.Pz() momE = mom.E() except: - momPx = momPy = momPz = momE = 0.0 + momPx = momPy = momPz = momE = 0.0 # if mcp has no linked particle set these to 0 try: lpid = int(mcp.lpar.pid) # try get the linked pid @@ -373,7 +388,8 @@ class Ntuple: ########################################################################### def _filltrkOL(self, pre: "str", prt): - """Find number of shared track lhcbIDs between h- and h+. + """ + Find number of shared track lhcbIDs between h- and h+. Accessed through dihadron candidate. """ -- GitLab From bd90538e37c87c8de093d89c1b0dc9d7c65615bf Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 7 Aug 2024 13:53:56 +0100 Subject: [PATCH 23/70] remove obselete code --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 79 -------------------- 1 file changed, 79 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 83e95f7dd7..45e2273fdf 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -543,85 +543,6 @@ class MCEvent: for ddp in dp.dl: ddp.update(newbgtype=B.bgtype) - def bgtypequery(self, part): - """ - Method to match an input particle to a TES particle and return the - input particle's decay type. - - INPUTS - ------ - part: particle to be matched [LHCb Particle object] - - OUTPUTS - ------- - dectype: decay type of the input particle [int] - """ - dectype = -99 - B = None - if ID(part) == 310: - dectype = ID(part) - elif part in self.Bstore: - for all_B in self.Bs: - if part == all_B.par: - B = all_B - break - if B != None: - K = B.dl[1] - hadron_minus = B.dl[0].dl[0] - hadron_plus = B.dl[0].dl[1] - - fill_B_dectype = B.dectype # fill B decay type - fill_B_odectype = B.odectype # fill B decay type - try: hadron_minus_morvrt = hadron_minus.lpar.mother.orvrt - except: hadron_minus_morvrt = 99 - try: hadron_plus_morvrt = hadron_plus.lpar.mother.orvrt - except: hadron_plus_morvrt = 99 - K_orvrt = K.orvrt - - try: - fill_K_mother_dectype = K.lpar.mother.pid - except: - fill_K_mother_dectype.pid = 0 - if not B.corlinked or hadron_minus_morvrt == 99 or hadron_plus_morvrt == 99: - dectype = 8 - elif fill_B_dectype == 0: - dectype = 0 - else: - dectype = -33 - else: - dectype = -66 - - return dectype - - def drquery(self, part): - """ - Method to match an input particle to a TES particle and return if the - input particle's MC truth matching was done by delta R matching. - - INPUTS - ------ - part: particle to be matched [LHCb Particle object] - - OUTPUTS - ------- - drmatch: whether particle was delta R matched [int (0: no; 1: yes; 99: error)] - """ - drmatch = 99 - B = None - if ID(part) == 310: - drmatch = ID(part) - elif part in self.Bstore: - for all_B in self.Bs: - if part == all_B.par: - B = all_B - break - if B != None: - if B.isdrmatched: drmatch = 1 - else: drmatch = 0 - else: - drmatch = 99 - - return drmatch def mcpquery(self, part): """ -- GitLab From 88173cd76c0b90602dbc423fd2c9911f824f2fb4 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 7 Aug 2024 14:19:14 +0100 Subject: [PATCH 24/70] minor formatting --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index fb752aba32..7ed3d85d0b 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -127,7 +127,7 @@ class Ntuple: vrsMC = [] if DaVinci().Simulation: - dps = ["hm", "hp", "K"] + dps = ["hm", "hp", "K"] #daughter particles params = ["mpid", "orvrt", "morvrt", "mmpid", "mmorvrt"] # mpid: mother pid, orvrt: origin vertex, morvrt: mother origin vertex, mmpid: mother mother pid, mmorvrt: mother mother origin vertex for dp in dps: vrsMC += [dp + "_lpid"] @@ -142,11 +142,13 @@ class Ntuple: self._vrs("tag", vrsIdx + vrsMom + ["pid"] + vrsVrt + vrsTag + vrsTrg + vrsMC) self._vrs("trk", vrsMom + ["pid"] + vrsTrk) self._vrs("pvr", vrsVrt) + self.ntuple["pvr_n"] = array.array("i", [-1]) self.ntuple["spd_n"] = array.array("i", [-1]) self.ntuple["run_n"] = array.array("l", [-1]) self.ntuple["evt_n"] = array.array("l", [-1]) self.ntuple["evt_tck"] = array.array("l", [-1]) + # tmap = {int: "/I", long: "/L", float: "/F"} tmap = {"i": "/I", "l": "/L", "f": "/F"} for key, val in self.ntuple.items(): @@ -562,16 +564,14 @@ class Ntuple: # MC BG type if mctool is not None: - mcprt = mctool.mcpquery(prt) - - hh = "KK" + mcprt = mctool.mcpquery(prt) if mcprt: self._fillgenMomPID(pre, mcprt.dl[1], "K") # B's daughter: bachelor kaon self._fillgenMomPID(pre, mcprt.dl[0].dl[0], "hadron0") # B's daughter, dark scalar,has two daughters itself, the first we will call hadron0 self._fillgenMomPID(pre, mcprt.dl[0].dl[1], "hadron1") # B's daughter: hadron else: - self._fillgenMomPID(pre, mcprt, hh, "B") + self._fillgenMomPID(pre, mcprt, "B") try: hm_mpid = int(mcprt.dl[0].dl[0].lpar.mother.pid) #hm_mpid: hadron minus mother pid -- GitLab From 1ee5ea1e67a312a80501b9c462ab556dfa6a62ba Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 17 Sep 2024 11:54:30 +0100 Subject: [PATCH 25/70] adding back mv stuff --- bu2kdarkscalar_darkscalar2hh/pars.root | Bin 0 -> 342006 bytes bu2kdarkscalar_darkscalar2hh/velo.C | 782 ++++++++++++++++++++++++ bu2kdarkscalar_darkscalar2hh/velo.h | 799 +++++++++++++++++++++++++ 3 files changed, 1581 insertions(+) create mode 100644 bu2kdarkscalar_darkscalar2hh/pars.root create mode 100644 bu2kdarkscalar_darkscalar2hh/velo.C create mode 100644 bu2kdarkscalar_darkscalar2hh/velo.h diff --git a/bu2kdarkscalar_darkscalar2hh/pars.root b/bu2kdarkscalar_darkscalar2hh/pars.root new file mode 100644 index 0000000000000000000000000000000000000000..69780d2e1ce3e868f2d2164f9eccffe05eea421e GIT binary patch literal 342006 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	Qhnjtk(2!Rl$HA9wKN(s@u)d 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�j*eQ&{CD_NnEp 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Ƣ|CC+ZS?vIsV3Rb(&PeHv=k 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¦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Ⓢ5Wo_Wq}#Q-fewmWf01ZeL+RZa|YwFI%89%b9m1p7U0%>A{)71lJUa&NeO|t zQ%Yj7amb!~7#8#$9!H&XO8joQCi_51Z1UWJPLWM^=?OHpTk?o1k^XeNB(GFW!N8E` z4w++82G)-EU=>T_5zN`=jk8nWRDz37BY3R`I?oAM5Z@CQEH#A;b<_(W;p-_1Fogg$ zQ>e!s4Pd~a(i@RR4!t~D7LrO++6AJm#rWbU$o@L|DrC=P&8}>3FM3V8Qx$7uU?Gya z%kN!`ppVd$%4#&Wq(qj4M?Om_l8l|@&^g^S6BmT0HSeXmT3|!f62Z!Q?wM}EfkgqR zmvW1OY@;guk|55kk^^u61Jpia;!HD!28tJFpS9wkp2CH=UJHIzdbZ#!7YGp;Idbf< zSPc8jOssl(@LD)XOay%3ztQ|H`EXT_(-y9|HF;PV-oK>LwB0F2s{=p#R63lTqr=6l zPvQg0a^=xx=zKq(CmduGvW!Bs@5@s<0_=%Ec?2?LPHk4g3iHL*%RDej5Nfs1wg1Si ztP6fi!x$*Udlo}_3K`&>{q5l~Rn_v*=J!gT>Rx^69X!!lT=DD{`qH`LD3=_xXHqx9 z@=-eT#L6`y14w_4c(`kJX!C>b7jj2t3190L68p?HwpcwuCa>3wwFlJB<5P1`_Dl;} zTMXGKbJcQRX&8O3@Wz^pOL@s*9ui^P=3%%|oS@U-G@K>+UZJ#<80%CVpx`zG-;<=c zHYLpxDICj)s*4!MS@@M&hQn{<(asZU1EJJEcj<owFiu?c71|ZCW=2)Iv8AgmQX}&4 zS4iZ+?9U)VmVuz_@$g0=HO;bnCy>KbP>L4&(d*hi36*mzs(C!W&obv7USGnbu+L75 zmL@&eo8#wPwYQ#cyMlHC>b`Fczb#t=@27;#%L0t>ed0Tuo@-k)cWHHuWD%}amOOO5 zichTWzKg*%6%H3?>HReDw)nKdVc_}i_kJXYBdGkY&N~g9@C^HR<3h-mEPgr7+MSGM zG^y8`xyLU9<Ej$HAgL^W3ceNfnr@JH;co)mFIOvKyV}$bysQ_)e?)?S+`Ibwkx3wh z<*ReIK{F7MEk6Mjg$!6*VSx%mR6$qV;%v7lx^&fH?`>ZJLd$WqQfWzF5=@{fj0ej| zaG0I4h6s}+&6VdLDqHYRD|=1%Gq{LAp|H9fSDF-?eNWtx+qs0~&Ju3EBDR8FRazs| z?M#$Q_VvmFVlK~xy49Z1m`q`-kARKLS|l<#2S;Xxn(}*fI8>AjETB1o=*iP|=S`-( z{#`g6+Im*nHHuhTqh`9d*go!DI#BSQa_lb<EiAR{`PmY_rKeSrZsU$f>bzGGXBTmC zXRwYLp{9(kqN{Y9go|#J#5+jehqJX_BjEL>{zz{ld8>QV3HVPmkbmpJoHa4Sv`76% z{|!(gkBAyW7lG6d)usJTp9S=?jHNT-NuIFeDTWF0#n*~gO%CF{lwwmQiDPtp(FTkq zm2S<anU>5XPFyD_2w@_7yUt0s@%_zi<H?kilvEjMS8Lq(xQdo{eFV|H7oj$cEdXJD z&r;_4c}ri4PwRH*)NzSau9QX91(~W%H5~1w!cT^;S#^<+xFeh%W!yaaBaTX`?9)_i zr2C;(mRsxXlWKnuP><It5+Xe9)e^B7I~5qfwB$*WXeO!OOo9|-Dp3&T{~fDaPi)P4 z(?Myom<az9o!x4CzHwdYY;TxnR~9YG^^Mv)*9H^PA#7}}GQv8SIg5}@hMhW<4_{O= z87$~Gpsqbfc90>$*0=Q6CT+9at`{FiB0qbO-WOB*8_m#y*I=!nMWg$bz*PV<3Cb2F zQ;4M{TCy?Kx3(d#dO#fKU?JUqut-jn!3JuNW}wXd77c>znv7o*6(=W=-bXqnD?DNF z!N1!ZR|3HAS?E^?%L@3e1~+d@9Tc~h0NYOi5qBKNe=H?3b;e8}dbywsx7Cv`9HBhQ zC!1<UPe4`V?MKkCaZv8Y{n-_<W*Icc*9F)`rQY#+uo7oMaby_LL?GK-VtVvM<$DKN z;t_CuQ^=8g7%qdIQ<)cU2R9l`yVX&@$ePq0WT6HqM5-B-gV;gwmHtp5Z!+PLheOrK zl}ynn-0Hm3BX(q~e(-EgoZru_4hY*iGP!=<tXjSTDI{n{xn&YrDurDcQ#h)os#~~7 zwc(}Fm0l|?^eJToYb9Cx)fJdMfeLnc++9><&x&&6u2e<JACNYDmbeoH-1D-Y=<Ax5 z3CFu3gpaVHu}>Qgnp*iw;W-N5^fc8*96UwrjwVXhi!JEp?Mm}s_L*hKo=2B5Dg)a1 zY<P%>mf8<;h*hZZ`Hb!5IY4%refVMtz~n6QhLz<S$}RnJHn$xLm7<lT*D5i&ih0H< zN4cYo`6HCC`?8FH8Y$;5&nwBy%78wd61?h4P;&jWsJy&_#L}7FKn3N}F&}@(EFXh{ zH=FoTmYFdi%mK%<bzP2^{k@6}aZJ%7-K+AdK><zs>$rwjJFQx6714Q?a*v=Dxz38& z*9m3Xq7m#B!v)Nh6MkVC-!^xWl#~QRN_pTW>u%*N%%#z*mqs07*!mOS>{g4uH{%JO z65Y-3oX=DgDM;#<MQOyGFYc-l$tnA_2{aU+^p&r>$R2=P)Fv*@7VxJxZhaSA4N1#> z)T7KVKpH+~>(;?L>R8u=Db_;p(#EsS(dh5wa(0X^0}xGD6lyAoT{O*)Gg&j0(T`TM zKEY*CTJKou2yhjNfFyRJ)S;&x7g)lp#k=RJ+(&8$5@n}-A37=OKx93T&XTy;0`Tt% zhs=16GG|swXZF3<OiQ`mfd$|MLJtN8{EOz|li+uJ2_13jq$Q`E%}OGPuS<xKT}~<Z z*}T5A%kN4&U*j2_G})(gBGX)s2xF7)_JlkjXX8ml)FN-}U=T<Vpz_n7B659ZZCSTA zaX<EyoB%VY3R7(D2IKmV$i8pPD$(=XIhFa$!z{2vw{XjcKITqsf>gDu2cc2+U5r>b zIpq%L+FO4;y6v~^V$%Jeyw_hBuruvxe(x1;j*oiP_KWkFtMoNqvUE&o(eC@)7gDJ= zDkJ#;j_b1HZ?94o_yw74OUIm0MVg9q%*qWrB98TRL{xg-Z5PI`z&m7h8*aaR%36S= zrhaRD+I~C39qym#QdWI4JNU&?f$*?wW+L~JY3&dO!bYHC(fl@1zri~I;#38x`)6Lo zrCDtzA`(6=ie%_m9DA;5S9+M;!S)4!d<?N8K2USC%37#qzn2i)?kugtC(*ncLE{`m z{mG*X<Vkg-oR2DFMadq^;=k2yJLHuU-IwQuO&nM$r~xwk4Q<kX7X_-$!K6WJTH0O+ zbrsw+Y<-ncii9GN+UY^nf}P(FW0+t{D*;%J9d*QHPrj66?kIA^?ntEUEh|pWPvIkf zV&V21xWh7X1zq@z)2P0KKGB~jysAak+A#A4>xI@eN<V&7_!eyaLvCmrz?;I$nnze? zEW1T&WDK;+kleyq#~H)*(sKFkX1pkg){#cZtFwfcpB>IHk%c8%XxOTd1V~aHA-5e2 zAnX9yoO&`$_<$42AYUzEXDcA)1UMGR_&eF5QI@amn0yKgkD$j9zqaxA#9jc3lj%M) zf6Z?e&=aunPL)ndVU0Rnh#kSWHOKPNhIUs<235=%8*G!9lWCB_4fvqf6=Jw#=c9ZS znk9Qr*;n{oo%(btq*3cm@7g?STo2&>`JC?0Dh1#Wvh<l9T0P%HDnsHyqO#j~lj1CV zzL%0W-QL8Mpj9eRL~&7=t;oRNfe`TfrZ^&uyd;{+WKN;Xy0{gR)$Uk5By(M-Efp&3 zTQU+=9to)&s7O}?Ihk8lPngLkd?xiZN7&t-?f|7C;Vjy1p@eMw3#!^ul%g|v+5qU+ zLidEcWUYhQ{=xNuPXNQ4)IJKJL>V3>k(g%Z&h}c*QQ(2EN*PHQjiI?@W1{8G8T$n2 zZdIveyX>xY)@EwS4e}P?sL;h>ZimApL*O!<nx^UQmT$jMC=BudTCK3&J}QOWjdwIg z%I%Jd@rt5n%6Hs&?oGHBCoR6;^ai@H0~p-#{p=Dxl&M0?lja;jWkiytqNlQn(;_~B z<<;dsn0J2kPhP8OrCiznKKC*%+j-%@3s1Zgp>kW=nGyKX&=H;F(El!}Dq851$ARCp z!YL*jMQyg*&YFVo3M#(o0#%lpV5OjG;Jdpy!3R3LwR9*84xmz9vCL}GhY=uM7FkEw z=Ho*A%ljP$f6(t0&+ZtIXtQTI@|Qc4m6Gd=G?AXsS7L2{kIahVNrWGrZ2E%|$f)MX z!0`h<U+u4b0&h>@t5NR!=SWC*7Qe$CnWD{b%^pE>Cw-;kL>N&*p@P};^7x8tSpa2P zSKCnr_VhmgVtS42efy*Z?50i(4jRs-C414C)ewhSo_V7MigAW;2^>l457+43^j?5s z^b1AJEfp4N83J{}g_DXuea=aV4sY0qTMTR5c#i7Wy1xoMIEE2V-FL-dh4<CoRKhIP z@NkVFQzdNKO@ViEw_P|U#(+4MOULcFdxA3Rx`($IQT>1X{r;gH#Y*+4p7Ws{#k=s# zP<f9W>-a-dZ~lZbYTs&z>uKq6(%ZUkEMewvh{9;oMp8vl&xdn6SV(sgJ6M<!hco@n zmB8ORSg!JXA*bSEd7Y=(8LMz32ujRkYVC&%#>bCeSy);Fm$hNvgJZN|J1@W_cMQnf z7@3ay)y7RA{|(apwt2aH;ASOv`1P`OSCK`4-|wz!mXnPRc_OAa_Fg6P@hRqldeZQO z40G%ZiGlb6bDABQGWi$QeZrOD6>mp}-*Cl^z119g%oUFN4nyHDkUQ_cAwn4+=;Jxf z8A;`R5|{zfi^r<GNwU)ty9$)0v`|oxXH*7HA|^m>S01mF{z-e%I4F2F$p+9(ZbcvS zub~h?s!RdRu_9qMfefI6?ZuFxKxa*J4vL+Pf5;M^QYKTtC}nU5g=;0Duaa<Ezr_RP z{*zib4ocvO_W{V+$n!G&j^X+**)RVkn_xLDPbM!~O2_+9QTtz^zfqL`T}J4Cq4fVt zZZK1xl?jf=pFj#qB|MGg{M&|S4B-NlqP*p@{;~gF&U{!NC0BI*b8p5ODE?F0GF~4r zCfxgvUH-?8{bOhUw&Zqi8bRrvUn9q8`rFA~Wk}j6a31~kd)XesNF|Msh{6Jh1g)bH zVrfuFI|oLgG?(Mn0|>DI6!Ol-Q78a~g!7|*f5t<pTJipld-fG%;jg%LAR+QQ31Nt7 zK!iyGjBa5a_P$GaN9j6#)trAidSB6lRyZlwoS}|x;o<D=fc38JWGRu#{C(m(B2cIs zU5<RXo#bKyo)`OUJpF3!ecEA&7}m9@x?sMGWFXd=rz8x6t5akWiQ#WNiNu0pX^E@* zOP*sjV*T)!IKZ@#Q*ZmSNIH9%AMofL=SQBzE#|JAde@h0(%GYY=SO73`OfI#H@dWw z0v{Zsth0OaOvwWONO8ufJyxfPcSIb8b<&)ATQ7&e(LMu%;Lkp<2Eku^1_r}PKd*kG zNfC}8p_JwvH^43S;^?S;VQ}LsS@@1y{?*ZQ&eDHb$C~unL{d<s=Z^<Vzxs)gU^M^N zPhmr4n_=APpTZyhw&ZW<8_hAT*YSvw^tnQK5V3^+*q8s<*GUf?{L25JWdA|2{f(l0 z|F?x=%k!4fx`T3g;$^4D$Z2VypzzrQ%xU>d@4;vP84`XtNGRZy(w&2Xc@iyG<<Uy% zc^`i3Ntn|^!jcam%2EcrhhO!n?0=!iS^p;VnU)|^;CB2Lq?JSlklR(juzow<{^|)R zb2eTGphhN7XARGJ`(LRQ|CQ>D4p6a?|F?$f>xW%TYFA$LUo4uB|BHqBKUu0L!U+?Y zr=xM-N)3~61uN6e3@dZRvAh`_R;&y6%*11QqYWY^l@CwkG)K?)P@I%uPNgTEnv`OW zcjQJ6kS>#F=ZeRDXZA3Ggefy{4~x`&Wnp+fOt$LvzXeJ8Kwq^IfaWx$iUaY_9%d$0 z%S3PU7ka8XiM;KB3-NCNGCm2x5A%)`ef-Y<6;%G;IQ1Stn281ExET_ESNRv&v;R#g z@_!2|{1g1E2qtr(3<Y??miarqK4F7?;lYRP^{;i#8GCy=^rkMm;;pUM_Tqokt&^xV zAwn*eE4MYKsiC|zh673;$sm?;{(rZj-~YBs=64cNe|>$yM;}Ip`9KU0J9kp_u=XaV z3wdjrh%!9UYIxNAnRkSra_8IfVfIb-)hBrJ!qd5KS87V~#i?HpiS5F?`YO5wa}0Wm zM0ya7^O6Fr)WqAiK3H&YMW<V#a3}%sV>|q7Y`9FnV%8Wshy99Sr-)Xtb8Bv<unSfC z*MsqC%wJNBLQC!UtpI^LbMf<Q3W5@bEPOU=PU0%#`q!1UtB;ZFYuw})cUaRhOOC&A z_t9IW3eWO`Ud7uUyd*>#NY+jl_jz<N*K~9^I$W(#?;reFt%v{VIW+YA^m)*(hS-#v z{Z#wQZA;}|-Y!&KZy+v)ZA!`lIUq90_1I3YWU*HcXktsJyK|t}b13G47;GIhF+&s} z2Apbq&vZL8P(e96fWn7omppvVEG0j_AsqB$?odTZ<l5(e;e!nF^P<&HZ3OTWHNuYk zt5rgLvxNIw@v|MQ*#Fogdjw4<<GZ3t`kY-nl*(-|-(;=!7O;bZTYYOPjK3477u$w= zzj=ud@vUlGWx`YbJxK7{bD74j=4wBW4ueMD>(l&U-22}=LTje$XlgCW>`Kb-fsYXS zs1pCA4cZ;Ie)nNNM|s&<W+LQwn)lI%o_(hL=C%BTt(SZey<(#^jk_F##mOUQFqM3p zdz$vm9?u%h-sy6`tl)7kQ!m<Kv@ESFX7h8a9~I;}V`83ob_h}#9e25!qk+SZ7xU;f z4A;KjJaV6T?Dee<^uKSt;Ip?S^2FUAmx`)(EX%(UwmD7=*~?cxs5m5?CpcltBeQ~x zg^YEKb=@L0<MAm2m%%HZ^11T|H+C;@Yp35^X^-->C})rd*1J5RfTT*xcoTi#IT~-R ze&zT@kIbih<4n?orCNbSY&*o+Mr@4h4{NAdtWva9<)c%pOw{h8UCZd94P`ZbZ&6bp zqO?!#1BD*R$(PDd=?k}^#k6zUdb>-@a2@PcM+b?yJl*&%vemL;ga#v3xp6KD34W19 zVcpqEqX}i5ZpFRVb34#z;*w)X58uLVB_iog9bfroV|kN~(A6%CpsF*>FA<tc8d5BI z;iRyXjMYIa3U1mBIG~rGUC$~nW({p|9?VJGj*Hkki;-LPt`0usjN4K(qwhsb?zYsB zE72zMbm%&H(~_JTEx|h?f;5Z*Q<}sH@l!m!)Dy5GMw|JvFCx)T>ugW92Q+D-@Md4L zR|U<?_vu_WusT&85FIYQd*+Y<k!vzq!$HUo@}46HB>|Udce9aBL%SvX_JN|WUE(l3 zO;cotToXflu2GWKje#NBwS<Z@q8qkKbid{*6|o{lx>fP8ov!TB_&~?d1l7~-aJJp& zh%<uZdL-fM4}&NDW1fBcxt<aGGA6HzoZo}CFYcp`s2>eqVP+Jq<bI%VJ9F>mV{ci) z`>3%cgf=muc^2IPm*Cko4_gAFrCZzT{zQ3VX%hLF4yIB$4R!7>fMi}$ydGa6e?jyZ z?q1r~9!J)T12<#X=<2I<KIw4dqj1P|1po}lX$6bGb7wYeZ*B3ia*R^qpT+H0Uv53o zOJ5no%R)+Zvbvbe%{r=!3=znqw&{L3wSm+a@<MK%4`SRm5bEx`$F#Kw>>v6zv#>e( z5S8<Lsv7P`Bd$|1;awg0{XNna4%Tlsl}0;Zi-A;ilL=)%e_q5ll{NqV87%g^+H_sm z(IeOZ>D(3da1Y@W5M9$DX#5)>dNR|ZhMUeAF(Eslhf&;Y95&u5!14Wwo|Rl+sFWY# zyHdi-uYLromoH(2%8fbD)AL9t{qx#{@kK^~Ft3gg`+3auwHNfVHJIy?eSD6LxS{6T zpczevhgLN}0FJnG8%q7CGA43IoYq3Y3;J}1&X_`;X|8&W*;H7obujnZPG-8l;!CM| zqnOUk?Xuqw5Y`8VF1N?NA77p$euyT=t-{W%tAp0twYR0L?P+1o#vF(|{&*Pz-!+#_ z9?-!Bd-fI-cOB+wC2+_Rj5^lY54Wij_aA7{qjrzjn2m-#T*5vnxtiw+(M^{8T=>mV za|f^Bwon`brVOtM7~<HDl%oOfce$c|L7r#q-+owf?$S*!o5~Z$LB8lIA88Yi$X;dd zoI{|!#n}ZT%eka7T|_o>PlU{U=g6q{fKI2=_G<ACtjUQ{GM`9AyRSF5S?1%=QGlys zhc=EM;F5VtAxo^3l9Qf_i^?9Lrdr*J|Ff;QK@UWxx8eOg9Yn$Xi-@iNur}0sh(6C~ zztw{uWRzF^ag`fy9ZW7a29xXc-~9QEc5d(YbigNKf<d3>O)B7}jS<|hreI~T5Dk7W z2U51jGay*7>N6D;m}qQ=h+(lZ6aCXhd&_AYgG7TBFaT}u@!%^2-KdXwFDqCh<H!ed z9ij8y6fh{^*U=M7Ztp00JyT!9cZzHNPFUMHuS9a{Osnv>0#^P$IoCViFTB5;E8A8N zU?j|zt4>GP^grnDN37v>m}SmcSa0zb&@bAMyM_*a_3P**1u;pj?us5|CUO*;&9QH1 zRkH~eidklBuj0dbD@&V?Sl%9z<NrDmd4|M=QRN|1dZ8Lwtf+XjlJOZXvoHERh}DY~ zs2-%JAx~6f=f>kH)=Jh+y$eHVQfKb3rENx%7VW*Q*Uxjvpm5~^g3#@NZ548wFxP0y ztUg=T-$D**9;#7~4rP|;F#TI_hkG@l8@r0Mte3GmMTmHVvk^5fFN~-|IV{%Zjkyd^ zrE&9?GFDrFwdu@ktNIll_)d`aRhde)8R~0*WFs5BNjjabu~qQyII6=06{B}tXpNT~ z6_0cthe+K`@oE)_U@Z)fG9|EG#7_uuZ1`eT<pwl%+~j`jzC9bMi#cfZ@yLR@8{fw` zk$pyj7e`<9;$7Ke@ttGTvox92XO7kcB0A!Ca-Yfmsdx1`bLLo`Ct&SJd@@z>Eea$< zNqYj+b2Ck1omX`*j(89FeYj#hSCIP2iT#_Q{=3%4!}!o0@&r8Ejy(S<@B;ae(b27q z!S5&iU&9gXED(uS%S^`)FZqG|#x{_2cF7{=n~a2+7iE<Ql{(iFsm5jQ^HMcREtNQ9 zL{~>L`TSP%G~6KwKq%%64kI}k+=@-(Tlr!KyJ!+CDUl-k#7%QNCn+x^620e?PF{^e z@C#eip|4+`NawXC)?flvQ;gz-<XI2v$rbaH&}o|UKMf_%i|H46M;k(acu5^U6@T3e z8Fn<Q^KW&|6c^YMY(D%Edw{al^c8@dS5vMQ5g0D$@6mPf_1)Gr^<j%U01scXdw`7| zl`^?sLOwf)DS9p&1k&L}#b+&aSh_-n8_>20fa%>-&#A_1bALil70vCMDb?+bNyHll z)qVgD`}qUZ$1CfjrEz$d?}lAJ97nZIsW;ttbitoBJNhEv^9?)bLmu8d2<%o|>x$Fm zU#*6yCSNzlSU0rgnJ#Jv{_3Y}Mw`CH^P1hQd6qpRtAjK?E(h1es`lO4Oe!e8cI-EB zDBW<Hp+jDcZ6%8^_M1GWkflZ={59}TL1Yg0+UJr3#Nm#PfRX>n9QO^I;WjZobpDqF zOLqEI5rR%0Th+iH#X~v`lVe`2gwOelQxFE|x<XoysRpZd^T&2eBBS6JzH%N$4(Y!i zL%(j1S5Z;mKqn|kkEX*vH2P?yNsT*xcn5A-T0F)22-6`*F3*)(=v}!v^HUniTPECM zeHLODAyyp*bCMs-rDoxEMqzA+z=VCBAP0N84*Zof-9@YE%ZE}JYachxisk+=f8}al z4_S0NXZrlsBOvq9!)t7A8+xCaq{=CZc`l>DHSS<x?s048Ic}vt=Ezn1^F~Gj_#(<@ zO$1(gKR-l@l#BA|Nx67AH>1Q`z4f#W61at*oLs;dD9G5pylD!!9LhrFFAH^+xo<F- zaHsv06-`Hn#<Lv&*_JS&O_VVnLv2!xCO6@+S#Sx9EUm!1E|e$%>cvDMQo?$ra~~S9 z5KpY@E=aP7U6(Q!Xvx-wZGC)m18g39UVTRL$$09e11i_&^I=!G5~M1OKR`8PX6#@! z)*9{ktvjdN-cNoLwwoeBmmBU-gLNbF&W~uH3Jxm5jqPb|>Ki*+H(yFy04aUa30yZW z)<Qg4i~CwRtf~P**i})$+wbJ8A-?@{gMS2`z0AASz=-9EI7_?t*x2l%JwlkxNR}8f zzBGHEL{HKZATrruI_6tbavk6Rx5t5owmgaWZuueQ;ABIVj!4To{IXuBR(%+Je_h=C zWNH4c@28+=fty`hl^)DDJ_~qT+tGnjH?B~sG(SSq+UZj}u0ZS{&f&e|kZ0)7xNL-& z?Bq7IH0Zn%t|HC;4*N!Yn~o`$qs;F&jQ2G)rG?Eu11+L#N8A!CE>zt~d|+)BX7WAa z_7z9h<KA0{AAw9nH)8JLF@$G<E#9r~OqM8wTXHkSHikJJfI3ZecK*J6Z*yhN-5kqk zZwpkvr8j3S4G!#dJ_Y2b1Cb`N;RftTU$0epoUM!PPvZ0e0=Ur2(5*!a#UFJaax~S> zzSwO1LeG=Z|B-!>3e9?f@%ohqZuHuPIBT9VF$)=*_fD|yhyyY0t0you-z4<X^8$hm zd`Ou2=D?H=(Cqhxi@ayj(bJZ(7VAGq1ZlRcn07>4b<htFXic5<I8A$I^St|(?9;*F zU7vmVwYe2b9Y=%1@E;1ox?T80&qcwuq#CnC6O<xgJjEx5bRt4I3Kzks7}$ORF*(f+ zorCY?Ig9+Lz0w)y9*v{sJiRYR^PzH$f0nf|x`j{Bd2PP%ax0rnvbWV!tvh$uv~bt$ zRtt8x;N12uzr_sj?H%5QJ%pV~68B@dea3irQ}8-GSyDyi6)kZ!Vme&#!<@@@i+}7k zEhm#v7FAtn%h{JxPY3ElkK>bE24asM3qzaKQ`AcV6%E~r0DFRjCF+HPZ?w-ZZiU%z z<nZ<hY&&o)14Ba8Kh4*^@T|IJIcgSg=MObD5Lp4Zki}|NX@@OeJucBJwTdZvwtRXL zvLYY8M2$LgC-F07{27~9h82SdHb1N{yt4{jZk4>8R^K0h@;iF?RKAX?5jIeadbmQt zPxoNkrLKweJO>)I+^UX(S3Dwgogomh&T&ui@R6dBC1c69mTjAXA8?%Mf-yU)3t_}t z!g0@hpaRy=-gxuJ<V=j6z6=%e+{>rK1B3N0rh}6E>-zxk4(YOw!#Bh|A#Sq{Ura2& z*HeuK*bM|C+IngM%hv7l{cy%J>9x~iCs1t&KwNAQG>BYTuH{Sm{yswh`kHOu)xfmb z8yYq|II^fp@s@P@YK?siTs@eUcT<3h-&DhA=WM$IopIm3=Wh^jI^$>-Qx<CX$$Mfx z_+G~%**)wg0C6mirdHt9dLKE`hkIe&oOw|`A7pgmcK1%{aQP}=uyM_qp*we6wNlGv z&$eVqTd50o?LuYcy5Z_HTNvVF)hz6bUN?`I1-xr|lvuaimNi%;<yRMdy#ImV>&lp# z5RW&evx^rNRsx!;pl)Jp+UK=p)bdL7po@tkOZ1pqT-Y)EHf`EtWi8$kQF#v?a5^5_ zu|@fbQdl~)5fza?wcHPK!KyZRE_yVoitG0{!u3^v<FoaFLCm}i&k#(>lWw599%ERG zbrR9-s?nEAm&w7ylGg2#t$gGOF$3*h_7eBcTY5r1BQj{EJ%)uayzI9NhgQ+G8B|Ir zM6{~YMEi(Ki?8Gjn~*DNVOnaNPtXa~cOu;%2FEKn6P-F$z+fU|1zIUo!6om!vloT= zUrwu~7~=%~l3J(SG`&y?LNzs!Qrn&{UlnW>cdy3K9{w@q{?yGbAmRY;QeBDOgJka~ zq3L$>ENq!r_2s;0P)sx0OYPsHq%W4mUBQY7;m7=;tAbwtIglamT%6WV>q^!;AG=pn zj(l7!`>}K04|d1gw|(xg6)P;`iwNJvMR6<fB3DQ~?OuroOE`~^xB%iK#!z9EK^A(d z1SjY5PZ4eOWDNMneqz_#(o<_<PsOJf<0njkD&BoDFtrzV2GRL1{q(Jl4)C_B_4-#5 z9`~11YcwS+gAI_|)2rxJw<iW4xpYHXx2=|o?fHd2004CNoQu!aH%8?ScTpG)l}J{1 zN9%CgrH%p%gBEXMpT17NMXj%g@2>4Lg{gXTzdIzh#`NH3FTc^{z{7Pt_pj?y-~a|T zQFKkCedF94jrZ_*8W=}%tg!qzcGN((_y|T&a{A)HScW}JDL7)tc7(lUJ9kPx+&=hn zDk?Y1r#Alet|x2cAwdSyt!dthaQ+YKDOxv6#^lO3iQ?nNPc4l2NB6gKO>~h;$iRJU zzCgW&ZxB9{=J8i!hT{jX5^s<CVY3o~hvlbr%7UA|!aCw^n-@4(z4ZFHxEm8@V@)+i zwzRc2VE9k+C(F&MQTxud*bV`A)v}fM=nNm57d*V#LKoSx_Vx{b*I3Am&2+`~tjR&; z1}j!gzI3uwjtb)yEJYNT>$;%4`MRzbhlZWb8og_CuAY@7as!R<`@6UD+JC|OqgzjX zSA+L`3u&xRe}CcWBc5g)j=tUyZFS!)Yc1cV7>eUP?Gkb0B$Nx)teZ!Mb7k5?8)?w( z?rpD+*_XYA?Y;!Ykz(~sh<O&?RRB%+Nyrw};1e+C4mN*Lo&GIIXDgxT)&#dL<WhAA zeu86wv?pdYc<kW_xV{!*>uOr-)E&>SGI6a3dNhn-`E~%2)2T|`Wb9Tll^GZw1D*a$ zHpU@2+xD-J3}|+1vyFJ0a;@z%F|F<e*`saW%aRS)FVas#OIkfFMQ__<iKG_9VZs-< zY0adQJ$H)dlx%?r<ix^zeF^ZkTUY_)z5JYozbo@;1d6uFepywn<$RT${J2xlkY$ws zM%tgl0B&?~Y=+aP1ums;l~6t@5IVnTrE#DpV{P0kT7FvGr+kH1NH*5e5P!hMz;UEJ zUIwXq-Uw9JG2BHPG%f#;R>kR*o5H<6zmT$gv(u$0o+?Zor37C}t^V@1PB+J>+k=h$ zB2}UFEJ2t-6(>hO0a8gEj#=2^S2E3Ix%VWCb@|52jw7P-_eoOJUX%Dii$~M~!ZV58 z1akB1)Skt&lbOQuw9>-3Mv#iuqN=>7^SW?$e+l<u`TW+v9BL9>HOm6j>}#|Q9*<!? zXmy7+jjwGO{vXc1Dk`pSSvSE69s&dp5ZooW1`nFx!QCymy96f?TmuR2t_=Z#yKCdt zbfeu!)AY^W|G8(JJ;oj9;l8c8>Z`A6uDMn{tQu8ia-$3K;m+`L1A#qa_UwixH71`h z&9`w?8iU$Ho6J(0GT4z@jkB<il<k2hsT+*w8<Hnemz>lvH&97waN5sJSg8A!$nILl zb2AJw*Ld*gfz%&+-A&2h=My6^jn=|;^gqb{*>4Fyjc8W<L(Tgwd0MA`FuZEoHf!zN z{J{4D|JEnc{;ajHxczry>IoxNEjxnD`|zsxUe-G^zi!xX|I;lU=8jv9<@s#JmQgUr z!P-fUsPF~_OgvEcP21F{?q;4X_OGFy^48pd-#f-jj?O$sWg|&(g(^E2n|n`Xwm5wP zniL+lRQpR3b#=;KT7B)JLbb|yBl$6#g-MWCBl#g73_j=OsQfI3S0ckG2@u2e2-6q+ z94F?RPQ00b)h&)UtWy*d_sftZp#-z5wh6ra6N1AZkD7{QmS*bU_<BlKl%{jb+Jhqs z9@bVc=F`5U5HxqHNry3OMk9Giaxs)#&<*B3ylD2`ge7+);W;s2gU^d%S(2RqBFv|W z#niOAVFE1ta$wR0lBj<%F$rU}jU?z77nut=$wuS(&d+tIv3u%K!3McStKEWY{PeCk z!(p1N(Kyj(Y)f(sz^raVO|!dnf|S4HNmCExKF%IMZ1$h0Nq)Hvdh+utou=lAd-&EO zTiGw1$KGwcPJj=HF=@kVJ-kX`$nr^S7vIcWZV^&s472A4!3K)I2zNiNzMTwuNmZ$Z z`Bp>u@M^K2Y*^AB4Rihx@6r_M2Bvy6L~v2zztc)MQ64p`p4za)X-D)1d7riQO!to{ zig>vW9=Hd<j@`{Rom>K00QtKG{HmWy{95vF%)DI_u-&mRnwYseHZ5IwVr&r9m*2&1 zOEuDdPVGvzvAE40N!+$0rGyvS*rKaqT!->L#>LdvyCDaR2QX9<3eEfFCpoJ*I5^v@ zo&Zu$AqRCh@bd#Cg7(jrs}d5qXZbkH!sBDBeCutDY6D!FE;|eA-<KCY2mqsByZGF; z{W5fGGZt{Jf!nsXRo8Y=G!RXIU2Y%u@I&ukHZndwyySsv1pChCsJY@h_-Cp=^XPMP zUw7r~m+J_m+!e=|_`S+ro1cZhIJ$MD5yc(NS8~F%s1m@Eq`*Yu2q3+R#RnJGO-FE8 zX?~kdYDn?j+N37r$8~W}20v<L{=Ux)%zb`aO*oBc61}^(jm_C#q<KB}D!dJcn-KuV zkO57O|7!INv$RH1`wZuW_aQfjhmH(o>;Tuh{MO8|x(DFAj73uQ;OXfEdk|J;RNH`u z7W{o@6!PR^&?H4<g#*xN_sq&tE62#iDU6=I!ggq?HYM|r`+Kbk3JG$AL3UK7oP)BL zg_C_}>p;f+0t7KTRQn7q5++Hwyga+g2s6uANRNYXMFz|>`KZ(dqk4ez%nIS&JYHJK zKXIuD4sSFG4DT&@DoAAac4b$-w7tcP$HMRcW#K?@Lq!9YnuRCL%aeZ&{%P-cf3M<v zU)_3MoU)PhB0&nx&uv}8hv{$cMeXkMnEN-S^o#T0>!m(xGeDRLLpWWpC98k)8<HP) zr{S#(5Lq`1h3k)$neUx)b{aUrm~ylS-*q5igbySXz)d~r=Jb=G<zN!D<m7b5wJN&H zxO?616RJUCNAF3IstAG7Z4ZzU!HAI&msJ95%#Wb448VC4xIBJ;DXzIyvlr`&nxqld zB7+*#4n3E9IKf%cLn;j0-9T*rR;sUg4e4PE^C|yT1M#_wC#)p@9@3V$BTK#|>}7Dy zs;TXil__Q^y3dT;I=!g)W?o4JKSMlf-(DTis*KT2YCUAA=-cOKUV1x~%SmU#3)<g9 zH6RSmrY3wt^xllqaqh=t0M<rbW<K6zq@~(8%~!?3-<M-xASO%Yn=@TMh8+zFIo{l^ zY-B^sc*$W8MJD?1dqucVA;Eq>XU~_JN`8Qhs7WBm@%B8$*yVbA6jl1k%V{FrbfYM} zIjc#a17)mv!(4g#Q<00mGu&8<Yat_md#klB>Y)#^+G^k=;d9!xGncAVKEKZ4Y&8yR zn>+6j>%b}&{}OgY!XC4GnZzJDd@CKiQ$Ie6i7}#D*feY)L5C;YvBoWp^R*rJ-8F<j z*mxXXUkxOW#c1KM8AB)RK6MLtYCmzCXrH?rCTI<+GdY>BwbjEi*_z1zzKQd@d%13l zcFb}(!dZS8<GD*nRW2IIuYt+?33#ek4ITHAat_1=Wi_W^2LQ}|CeNOGek6dWiR;(! z9rz}O&*|Z6v&Bnua|8HBLImwpZKzHoqXF!%8PRp6kDp}{&qcw_8U6stZ0)vLFG~7Y zkbikasBIhgijp)#rsHrxG`@i{y~c^m<lH>!@soA2ZRy_}QRWA5v_z;?IWjscVt$8D z((e8@sp-y*<;_K~<cXVjf!*D!3TyaD=mdy{_6=c;owvx`i{b42Efck?D5Wk}-{7cM z;mebC=|(+hWkZFQ(?$v5^J9Y~dR1d@@Wo8`1-*!^!~xGGQI}*++t%G@FM3nJI2u6# zVnkO*oAjis*0K#O>yTMf;v(?}(OZkaH`?)Swa|^nSEng)f{vV`<*p;_m{!4|{s*1K z=H^bp>tZ*ozMm(TES2;phK@8B;cr)<=Q!5CrZePk<BEj86Yi{bitHb_CRMXnhM#SK z{XogCF$+j@`TH_Fx5P{Q<Bq@oTsuE9<enDDRx4LsiO*wkU4OPA{nHvT(|=@86=oBA z*m9N|sEnfc(ctlMbF&`^BL5Qng%*o4F$ie>BqMy1L_MKo4zC)rzAM_eCY-k4?QvVT zQes+wQe~v#o<#Sp1e?&M)I5qPZC&79MNW*yOhY0ppr!7vd_FRMFt@^&U22*nszF-3 zPZ$f?TOm|#m|3I9z~f^CW!4091)X7Mn27!kDMt_%bj!8obh^BF%a~2&Ndmj_GO;&A zl;ZhM7Ep^CNMOWq;7M76(_%Oud@Yt_@Z#j+S7wi7UMQ&QmLa%@F2>`h;@#GUQoAS2 zVScKHc2J^MDr8##<3}gPY(7}*O`xREWq2{xkJ`$xOS%y075dXX30cKh{IfG--edGZ z<*&qR%^=LS)k&^aR*g_kg}n(|AihkeR57BVpYjcINn+V%KTt440;3g~hkoz}%xqH{ z6m=TC2f8YXcan1Q`i00eF+23HcyT&m_B8TgCDk8S^{e&Xu|iFs+@qjqyYs7sk|e=4 z<K^d-eJ*vTnQ_H82~Fx5t)bnu$)zAl&V6r>cy@q5g!VbDkN=@^yPEnp&w_y|DbqbB z$K~`sd*7vj$<e>XGHaF##S#XDo?mZRScK7bYE=VlJT!!B^RJo$Vd{c~<GPs;n_*Kq z$+!S<iXTQo;MENatTbD7k$_^XDe&4_LDgr1<r90yT^{srKO_o&>AOltpVeN*y%{N~ zjHadL$BV1+=Kq5h7=5)CKk>Dzsc4K_?J5t9={3~Ak7{y;#@{Mf`+!3*ap4PEnz!vA zk({izT9B3ysL8}!dVk>IWwK<<nV#caYxqS>mQGj#l9vK5Tx=WrP2waWDb`wgoAR<f zSeQCbxbg*;vE*a$)tOanVd99dCJgU)sfT&k(AgaY@Jcgf&+nqDTE0z+ROSuUnq}b* z>s>AZa@NN^0Ly>V<cl~&FP8K?8S>f%^<$khaL-s!<|at(cd~MA%FQY3&4O1{soSro z=@>8_$Y<Yvs7EVh81lif>z47AZH5Uq9;VooH$}@`Bx$83UT`#3NI6m*0~tfxp8)b_ ziw&Vou!jbG6nmF7=tWZ#Vt@*5t0V&EG=jx0ybeue_{taT<!NMJX&T*%<pxV&T(zAN z>$%)ZdpCB$Bj=QqrV$)s+sxsh%dW?p-Iu~K*6?>!9nZ`k4k8t|#_9Zxy+U@oQLr}s zoxAyVh~xmK)MjvPl#d_;!RL^g2(7HE-KANN?Lj9u>?P!jqn|oYw>>L2SsJ9ErJxkK zU(OT0x6k;JMX4b37++jLOVcgRMOi&ewu)OmEP5slY-Q774pnN??zS))2@6OtQW4(Y z9@K(3iek^QXBWaOh&KaynB86WR~2!wT<18iVYgIY)~Y4uzQ4CQh@*Ov(ljDbTe+%X z++R+j*`8L7bW(-oJdC5hNmO}iye~khbrfv=x^_i01^0Kp4y3^@v*cCmD&Jjw^gR>R z8cyo!;6)U?QVX+7m_M;4TYfLFwgn7WQ;5wqEl6)*!~9)C@FPY7jOQK?3i4pS+}2)j ztjGS;j(;!cJ^fIVBB<Uq3pe7kx;h9sgM+E}gOVtvY<r?D?4MGL<5XEJ&ykj7FL87% zJb~-Co5kvOupZ+{Po4r@AZK^4*<cXxzD|t53!HeS7jG*K)p}R9<BpL|tE$1LQTG1Q z;Wr=mj_3$2Uhh3zmDtMGv@lR~kF`|g?Hc+J$=RdL9idn!?o^t#sZjDJio5SLXT}jF zUCzaNme3GiY|W97pg{-G?>~EzPSqFDX0ow+f9|Xa#V4kd4Dp5EQM+Gf?!>PI=D(Y+ zdJ$7))Waw0js6x?roHLxg))aRNJEf|=GQi^VsXawKJ4-wLiR_dqbYcR<AA4p>8N`7 zh$G7X{`fb1n5`eHg+zv66?*ZuYg}S@yf~QJz>Y*Rx^uod+2A%)=6=IQ$IF;8z4au+ zka&+S<nS%ff*92Rk*P)|StdjAPP}u}N27fy(s+H3?AZ|zSF2>b9_kKo+x|^MPAGro zXOq=I6y7~l+@I!1_vmr{`|gk)>-VRv0s}H!^UJ1zoO_#CV`d1zS_Eqrj8CTmN7<eC z@p#epF2cjyADIs(?bGpF*$8xFdwbc9kBG{rLuh(lt}~15^Ukp-=P<l-M7)qQUiBKk zY<vw!3DB$|#(aeo+oHlfYd$*E<rxtX2s)B)MFwH3>Zf2aS$irw^%L-nX||>6<F|P0 zofS8z6jSu2)vQSne(18rMkTnSL+Y~%2iQHgTFw#d>8%qg1p&@CL13J=<#=e|B(bnw zqz0v(FS`5CtZ@$o<o$-2jObA+){lYkTKn3x?K_8p^R*atLi@20z?0oev?f$;Q0`A@ z-{T6Exu=h3ojzZQCVkzK`~)W0-=i*X$X8jonndpZXhLlIO{Iv0<Pr-yVeHcp09<dp zA}r=-@&HRnjDw5@AlC*GTc_Y$bhM*O6X-q~n-9QKwJT-0Dt$5W{qA7#TA?0Z%xEuF z5QItmYROC4U9WuD6n;7MlTkYw$C3hN)l)oX0Sn$7`Cj1HddYC+8(07Clgd(1N}@&q z5H_T!w88aZEVJ?2N<Mh;_nBxcpgJcv{etF4w>mFvFC<jh4pI|aDM_euuxFIJE-TRE zS^b`eBph0k1PHLk`B*i@6+F56^iY3xy+2~@2D@qoX9yA1ipt)*>LJ5QrO9V%FG#E+ zg2E5Cnyr_aN#-fY`HNy*Ol$PGL%%r7v78@%@$Th_Ki`B3FUU8B$Cg|1RNW@DPu7E$ z!~J?&(wH8hd_0M^&P7Tkk}K9(O{#`nM*;f1k6<1p=2Y1U0D-TRxDufVrMuoF_e zY<Z}|LAY{CMmqW(JN34%kS3EvC)yNg-JeX)R6;%A>n-+2KDlo;IgkC!bX0lZ-V#6b zNLxkHU|LG7hRIvJh!tp~*71l6JJWg}t3UkYY%*ZQ@A7;kx{C7+SmM|b8YAjm*u*>D zF+<&Gt%1yWIzaC-CDfhjWRBk5iQG!_rjEJdtMl{x{`_>xlG4`Tv#fEeheDsBBF`v( zhrG$z-De*=T>_de+DSFMLXQJ+u_?hde)A%l(}vuo9T^I3=Uas!CqRmG-DTmgR{YS% zod;)z51uKEQZJSTsqre}ulLJ)y0%GuuSOdvw^IH<Xm}K_h#s|~zznSmC7Z()HNfad z=^G{~Xq7VF$C!_MDx&BS$QWY>_>3fBDLy%XlMxe419;tBi{!XStC}Nqt%|zYEZ#w- zh?apde!g|PWgbCAyl8eGD0W*&CkgIEJE&P8GAPk1I(=)%lQ(K{`~9%V__cTblc-~` zy_cnTkcYJtFlN)Ia9UX8kh2Tl4fzVz%0+wHFWF!Kht<QE`W1Bvtd8O{)w%~t(o?8s zjv}L>kqcnsvU3SA9eZG*n8O}wE6r!`2Xm=3u+$#A%BMEj$BkPTpzJ)IzI(gPU4$8y z|3jPLYI2O3gYyYkjfAj!gxZK_j#9mbJNSjGO;{S@T!M1z?R#y8mxplfFssjt>3`x^ z5exk@MM42}vQPF(CRZ_VO@$m$-r&R)MCv)z@7FV)-k`Qx!~Cfw#e<}`YBvBlb4|0K z#KJ)Xyz{}K2;$)nf%s?@e|bG&cp#ddPy0)l!?85FJ9s0M+(}~GA6?ueRrmSm1~c6# zGrsKF@C~R(+S}MekD){7;pOA%pYKAgzS1{ZUznTi>8Pa0sE|)qm}u8&-3nV3)<DWg zM!#W`h`rfdmE9+O?EX_(2ISS;`@8zq(YbXStdc@p=Oc6T<uHv?U0>k=Yr8baw2elj zVQahFNw7qz*K;yb)(2Thr-cbSlJ#K!9**)}U)-@&I_|Pgm7>#c8^`Jqgr~k;n=Q5K zno<XTlEQby6F1u@(<Q;?K-5=yfsakh3q6&Ciw%=SFVx#boSvatM<mAFAK0;6tt%!W z@~I2U6r7Gkw!*8eVnk2DDoXr9?HHF+{gVvftI^???cPp45?oG{d7i8UJzU+<$0id4 zTjZvzh@QKF!`z>Q6Wzs}l-<-Q{fluAiHdJ7Tvnv!g9y+L8HU;@hu+^=o-GtW(Ft9T zSpu&YFxAP*uyrF{!aa7s00nE{vW<xTjXvOmtb<ARMV|T5P#&rY=FM$UJ9W>1$CD+C zEnJ?8=H&zg=T>go=L8qZojr8`5JcHqWSc^iy;KFF26($#FZ1&cK<9!BSG;^P?13>y zni|NJ5W7b=76R|I(v3&#p&e!3X&gb>Ed#fsm6^+wRiWzM057z+n1$+I0xNx-)xj$r z{kGczT;c)&hZ<O3fHMu@8LA{qT3;Qp^u9~SO{F?jSdc=W)Q(A8Z3C#i4S%nDO?IP< z+;y%w%B|CV3Z#<HZnU*ut<OI{0RecBF#_~tA3g(ERueZdZ$yE1gGEuAMsQ!tVTc?{ zCRyG?>Bh6g!r$fhAQ~Bo+*udS3Jm_{6}JOl2{GU_m9IZglt+bo!(G+vDCVt})@Fv_ zS+Hdv5VK9)8W<9ErOIJ@J8wAUSCYtS2(%X)+EQEzTK<;qtem+DKliE16QEUHN*!B+ zopl~}8W(2I@AhTYmAd9l^x7JJG0M9=K+Ezi#Eg<*HG6SXH_l={-{Hky+dQPjL+KAA z139hSIxnSVFY~RqcZsDBzg&fe1Os(#C1u`Cj4Ojy3F0cxYBt|_thj?G<A^U6>DK+z z%NlB<_dh`Thmz0x-f=Eoa&e<hF&F6R(VU<#7veJnomblcb9GG4iNwi%*A_Ft#|)dU zYdsC_s2I^0xbo+yBryAL_J0qSw7n^B5=M|)M~+xpyEx!?nf5IC8m`29yCqF^6WLBj zfcm+q3DRHCecx(Fa)!Kv7fkCl{OQ2B;`ORkC}Y>$(65IDRfdC)m}$7-%+DOwXYl65 zziWnsXwN-d@xc&3PWNZn+g853*aY)taNc^D&i^TEIM8~%VkaZ8KBexp;4Ms!eJCIC zptDS7#$^NyX?=Q;^&|Y-<<wbw2;x(GvGM7Odt($ET;7AnM|=zOHvZXl+rj!4uTP)0 zqaL&-cvO^?e#Qjz*ROpA<R@=qA6G;qiz4g+3<lm#2!&R#nY3RS;COFH2>t$?kE&Rf z3cuo`7uL8z1@0*guX@~VYv39-J$b&oOH-I!ySl)+BbNLg=(|5<Lf{bmV8VANGmjy; zWz=U<hnOiMzyx^i6>Rko_i<NC%{Dpl-_3@c)I$mH{>XO`=aToE8h1!<!$=<0I3xTO z8ke<%TA<DzIFH8prg`69g#n63sknh9H&pp29c&_P)W1MI{pZVNyDP6b@0j6$OGoQx z$gS0P1TXc%LTX**0D4D#QVUx!M1sC|@C#rI>pAw_-zuV4XC7SSw0o2{NeC0jcUsA3 zNc(~g51QP<(S}0Q!l6)F#uwP9y_k;)I1&m9-L{2o;8<|3*LGdtkr;b5UV!N#@#S8? z4He?al#)^zH2@7C$MBUsDrIh~U?TEX>IB&%a}<2wntuZvMLtT2C-gWWk#{TMw@G<g zk5!60J$6aX?DpgVh2P$e7*8j)6<?kg=mmza@05Pa1{j$Yj-RnsW!#3jrTJ|{$eX_L zJZ<&v@#i%>qJ}iUmbnOPL3={yeS+-{1qUL~bM%kiNr<ogliP>Wj(+qz+`e`J8XOw) z{be`(O&Ep&N|eq`W6fuXq8oqrK|5Hmhi6yv`Ahbq_F#7OD$)1%OtTdlb4KOCYsZ}? z^U}^e!j1P!{ygp>n+OqSs;u(f#2!Rd5lus8vKoJp!^d6+pBq$`%vwo(DWt{-NCz`b zLGiudsr33*rNy0O6P}^3yF{tI<#1=x25Q}OvI3*TM6|8*4O^LV{K|btvqKaei3_-- zC+FUt&fjVDCJsN+O<ObDwaJ}Ow2D}P*7jTxF&84XfIydn^v_j(bdoSPa$WuC|9W@T z8^YiloN{y4=6NpXs`O+)?eV0@Q|f$8U*~F%caqxC^P#mKYHB|5+<9GfL`oy}V}cRp z(E0)G_(W(WJSO@%G9ju{_F}JZ$0=l~)mv1z9_KVGbnTIovIx?t5_s-`t^JNMZSjDf zr7L>kMX;G!Au#ZBbG4$!h)ped=ivvUKxf$-+rZ9##dhx7aax(P?&__qnJOZPvm+I% zDf4^Jv!8Ezj<DL(2a~D6ix|I6dRFM~L?bb2HWM{Av4}{kN3|4wzq{Rfs$(|%ZCtvW zAJBpY(X}-Ha_a2+wAS%fVF|0yE|*TBN2(NRd*l;fGSYgzUiq6%<keQmfZ;ho*bj(e z0%A2=Gb&lNS0~_wLm6qrJJeOsXy$9bDncKgZ+*Mboc(6MR=KiSxKT<B3Z3-^B!%KC z+T9bFpE0Z~c>9c_@I0KQA#MYo?FDUb&yG4#i!-_wO!~t_HfBVg3(*{fX48py1@vq3 zjFLHw+qRhx4{r{DT-O>vwisXG)adi;=L@ytOgypzKCO11W~2(qtJBjc{K(4v-T+p3 zZ%qF~^BbNBu|sDzi`%*jtr*8q%%aYV_KT$_Sxrcm(AkNT9?Mnd#P<uq_b#?8L>s3h zfjDKl0MjholPjv7W=WiF5<1^3sTvF#<tXnkpP1j9&V%FD2&0wDeF>&NJt;NZCClsQ zE_gffp=w(vX9!K=y>N!MPHE5mN${oCgqvfh`Nh>w12@`tOVFj$hLl$e3-SQfh)iJi zKu>MsZSK-QBA*}kT@sHi_;`9L>Y)mD{nGTkd3^R>nvE>^{gv$9tVQ`J;P*PwE8^3{ z#nmcKKd1a}zA}w>&&5pHsMKws==@9q!E<W$25a=oH^izI@z0myuy~ZIAAAU`3N6*U zJMK<oaIzW<;n9XheX5(Ckc9?yGhA60;QcrJKR3?E?`0}|`}mEia+|14zjGE;oAFhX z#`RA4U>>X|9}Uw`R$wG9hlmSw{_FcZ;oNm6z%jk3YJE7;`Qx1wHFqdb-}h`X6YGMq zQ_AXaGdk2hU>9mhf`%W@)+qZF+1KXOk~|=Ikz7|WN#H~f?67S6W1sD!%LG*GCWW<? z`Mry(#3tg_eL04KOfdYmG^mR1z@xvaoyGgs!75P+4Cuh>rewbrYrVoaf6$wq{$8;C zUBf_dL+oXDqmFq5?6@x=Czuy1)<yeRQX$og)+gV%?iail)b4eUxXmeCbaPehZVSYL zQ*n8~gyu8qQdWkbsj;Gc&g<|x6Or$3qpJ?-VuI+vvbV^=Ue;}dq41AwIOT;O^3_yU zAx`h6=F!;U>bBTXWk4Y4S+AX~oPPZD_V~G^@3kyOu+UREC-(abOjuI~B_;aPMH2p4 z@4<Gm(v9bZGO85lF{d<)vad}5uPcibH!Nn*+vblImfM_!DPySF=ToKo^cKQW=S&Tf z!YH$+81vOX&#;(cY7D%zaQl4`96W(o;WlVj5F7*BN%2lCoc_>6hhFJvmyjL7+X*8q z8woBcAwB&5_X;z}1XRcAOEFA8X`UgML;E8YLfk}bhr2wKr%ZF%Ux-u8r`}0W(9-iq zoqRS?h#4pgk2|DuWvvnJI5q=b$9ghaj<&V8?JJzoI))6kPWQ<Jyk2;qm|nBpAJlnm z^EXAGYt}C#Nfjf0{)pK|;D%5yx4gT<N3^==^I>v*WLKknGDc}zSYP&iTuLdQI+UDM zl(}j?IuCm4#Td;S_^AgGmZi$irQ&tuiwv7+j>MGT#^ef?nAyfR*}S|zl59XcS;hre z8?j&HN-Y9Bmcm?-pxyigvfMgWHqv_dp1<8##m?on3LN{l2CW&~x^G*@bp?^=4Z3VB zr+EazvcJKmF_5-vw<_{_Z;m^foy~eE(NgF3FC|1}Q&QHJOI{GZ1qaYVKVZr@861u- z{3N_9`wV~+MD*3|jDjDq>l-fbN4XfH)eGLON2>)=#{A7kzRE?K#x4DM`gdQ}&5g|d zzr$DmKSuxlErj)_eYVH<uR_>$Y$IIc-{Gs^tu;q)t=;v8FN}&R#orEz=~;C@C|etk zu2$ats*C0RQ2U*|gH(A+@EhTED&Y?j)(@O<bLL{lwaBO5>!;-5lB2)*5__qmKpWU| zUM6i`PofC(KQnWW^G>3;#=o|zV}H)^Jd_>2W0Hjf^jZYq56Jc9iup70S;bM|Xq$QR z;wT1*r?%m~)TAOjI}AR12GPFH^|%~=bETd<<f_j~`gwSs-8`d~p`5|BnsYgO=Gf0y z|7_y<d_Niw1uiP8wBihg0BUyg9DY412P#e<6%Vy4263Mq4=(OAiT^9yanQtOlO|_D zA1cCnYbR7L!&v_8XVL3dVQ64~bQaW|JJEOf%d9egFaPWA=XZZ6pccJ)6+xd;WL$#r zy$B^e?2{&LoOHN%3x#(03r&m<GQ2D(*lRrh2I8iNTWF%aY_3`;dZ``Cr}-jIhDd29 zy7FIktp6`f#edL5{Rhp#e>j*~@gItn|5H)-$A7P0HmhhCQKg5zUn3-8!DK8-5!6Q? zlfh;w#rqWYMHA(PJz15s2+Q-R<}fx*{14J}VEVtXsOINv|B@ke{!xwkM^;n#;xEx> z(|?G(!(OCEywp?zm7p9Hp}m@$()&xO#IE}f?U6a*>{OIES)-7Z>xfB8&ztEmam+?$ zD~*vRy6Nb%=c}k!93xF{rX$6P8^x@AM)><qwgr5HD6E7=TvCXv(6UW<dFmah_ChEN zUUY_PN!!j~d!jK%r1gC*LbW4Me>NOJKa+F`j2;dz>HoN`<R&bP){KT3@vG>WRYYgA zHfH$`Mx?25()KN3H$lH2VIEmOQ0BsiC=g#DU6M9@I%fNt{_YWLETSk7Yb?I#5$hCH z2Jxnx8x0y$BuTWu9sZ!8iNsn%m4Uyp<3<C=6iE;@bBDteW}dJfP{W_XiU39S$AECU zV-2havYe!oy_xeF+!08uBtC+8^6#GJhl{033{r{#46g~W{$Pe*hMPUUd98u<hb;Uu zx|=A-<aM$gm(}Y!JDPpJtS5;E!t~HDn(|_%SRbTC%6~wK!e46QSK5(b{XMBND9Jx7 zL}U?KdIZ~A)O$^oF&UB?z&}cqZ_pTvsI+IfUj73^jrs>R^bgEA<-cGY|G=XE#fhoY ztnv@rXYA*3(!4C_)<r1V;SOs*(O6K1ig2|<KWM6TB|MWadYvB5r}>;s<{kJ8%BwI7 z%@<?R=;dgO&Fom3XmK*EET|-Z-{$$+x6(xa&7t`pz^eZMMiUiby^8pxiNPk#SpLgc zEtFmp_k#?1`LpjuIo^2x;Y!QDU3rxr_Cgc8(ti2B75}>8{~s18|I-5T|FrO(PlkU6 z!xK%S?->tn!ryP+iidjQIZhv6J?UA|<_OxPH02B`?z4fvvEaEX#(Y0j{WA_!$A0`Z zEG9dQwa}y@%-7GN!)DD;vT%Pzh?qUs$5;xdZcbI5!EJs{EknTb3>UTJZ;&BZ#q{Vy zsmHBG7qctLDk8T-6%D5```a^CAp+ZsFAM)L6y1zmAIE#f#Uk9M2=5rnKlHiT>(_Yy zgEKe#e^GmK{+Ba<3GtXhN6kdDP&2~Zo&aLURFnVS0nyDf)IJ^_3e{(6{hmCS9M8u8 zhQ|nR9AW`f=fC0Y|GB}}O#gE7-(LI|CuwMl875dtbtc-oisAS<MyR=&K-M#+h!Zn> z{bw0re}&E*{~M$8|Mpbc`2UxKNGx&n{74m462FVl=kDS5S{TFgnQ<R^5!o>gMi_ga zm)Wa!k_~hSW^#C0Kgf(U3+FH*YCE7Fr5b~>Pq045JBDin12gmxSt0*UZ*hhCDO)Zd za93ZkS)1Xc;TmU`5Uk<d6^O4t>y*$e)nU^6@;t(~TCk?bRE5a)vS`pdB3DT>&){CL z1`FH7hN^r8K#a|&&U6`au7Gk?n0LJwlzQoOB|UKI7~9i35+FbNc2JYfWI12E(Ne3w zq@^S#)l{ppTNjP<hXco{6t!=*I)c37;PTBT2`K%-dBcx4%gg1*&s^&_HX)Ms(o6Zj zy8}3B!cw2I3TbYNIs<u0kAqp#eU{9_z&`{&nI@s9{c`MC=#57wrDBf=J<;Fp5pD1r zw&uvv7xr(6Cnim3PEJ%D(l;~S^oc7BM~B7J2r3Dgg)Ihvv&)QmLL6MY<d$(J#sePo zjPLL!?(4_D`PpLIbSiM;vci6Urj<-3ew5Mz0_wUC?EI1fZDCG}tH~!;lpRe|^uieS ziH*2WQbl@%1{5Zk)Ek>Nk|TC{Dkit`kXO*{&Ou)p9<Erd5nBg6p5e1Zm9s+7gVcj% zH|lz^!+j+!x{S_n+<TI6r{e%`$veIwILki2M_i3|ddC>%AEE7&Z5=v)f+$q}fj8Xu zw4UfYI({p}CW?Vl)=~%X9g+#MWqVc%9kLt_83F@@ud$d&bL%0o>ULv#VHm3LICM#v zm}}|u0E(W=%2M2=KF?<wn9C?7hub$Mg)0FlZ^)UMO9n4#7O;SK-I;`xaQH*UHP8Zf z%<z4a8``i_K8r&oub{8jAn$n<^lCB9=eNLz>D0<)Ag9X~=mV+Pq^RMi43%K$*|xSS z^v!y>h^@%Ukdn|4{L(2Nt8~xv=bqm^fm~qy<muAqaI}vfu09#R+_jM`tXkqz>`wl8 z`*(&%EXc|6cK2@^#2V|42LBn~L499dh*7=Bs`!`d_}IOUeNa1R!R^X}_Q1j!E8QRV za;eSPHS9`aeV>C5^Ip@z=7&wHz>W$>p7rTUO1)p^^hWTt1)Ywp>BmREjw-b4n$H|T zcl4@f8XCW<Re9^YG*{Yot+T$9OZ{RHN2841()5S_w9Ka<7=E+EQMo6pb;x#Y^kLCi za7jWcR69Rszo{>f>HcV;W&ria7-7j$W4$Re9`EMFQt45m%!WQ$OXA7<K_I<rkQ9Ir z`;kldAtXBzt*Ozm`coOh)1z)1fEz3wc$np9oY_O<x_`VBBVj*M{;tEy$oSdZ!+4&M z(;?y2wDV*9yV=4(Q`pYOOoLa@18q~+k|UbawgDR8IWO8$uXyy`Ed>GPFYv0};aQ?c zh;v;{M>i(1Y;0x)Xn@Dv@t8BS<t<ZZo*2OtJPzC7l4;ZOnumMY`VXVE$l+D8Ex#Dh zj6uPu4xF;5KkPH^8iN_8Ew+BFbldd#RO>@Eflo{h2SUJVHp=jQW6M-_OV<=WZ{@C| zg0D5*W`WpQQs|db+sXXTxj$H!i>2dDZTC`Vch0(!!dhdK0isFlvG2RVob3Zs&xywF z^rFO?32pmG&T+OfVp}q<eFn${=MvRf0f9Gc_hM%S@~&E<_rWvS6d49sP%qH;lub;a zCfIRPi_aFTA%mM6psV;b`>1u4`v!mbEYWpSu+C*r{78MLgZX35(-wsP+@6%Fk&OIL za!;+j>$0Q&8R5iyd`?ky@7LAwc+WV)9}O~IqZwI2QuR~A5d-ReqFU7B;>MhDt{+V^ z4++$~X;mt@-10cBrAC47-g^~lFB_95%#ydZ<C=F*xm_uQP)|1RSlrf6qT8*j%R0Ed zQp{X_4fi@e<Y1OvZX8F~#GWJXwlv{_gXiMV)6TCicnLTdY<Sz~HV4)rXIugApZaC; zaL-k*42j2<Iy$1*Xf%0tT;li_AJU{$Yqh?TtB?x&FNE|x&{}+665RO1!3@SCrbh9A zIO+7Rf#dvInRA}fH;239NU&>S#Ccqe51N2Yq-N?{+6W;Pv@D&{$EK^Vn1V{c!qeU> zh9y{uczAPu1q}V&d`?2NmJVxY!%=2VYN5u&O+)JKw-8;J!UTUgGyD_(=`DgyLBZ!| za>SwT8<;~~1f-TamR9I2WeFq^8|1rjzkbJI;flKgeSD9>_aQBCY|KN>Y3bB7Drq>S z*ghvjv@PX~mY(&UblJ!u6vkc`<o%P&7`Gll&sCB$jOWjg<Wlrd!oUVPAE`H~ScTa7 zappYP4!U%=QZ<!0`06<t^GtJFOV32AW##&`#f3Q19%`c(+-Ee9G<h!$*9V>Qrs+NE zLd!87xmWiydVEM0{rLKuPn_s~p1yBD^<~;Wg7391a>?%(a#ZCxyH=&NDkB=)G5>bl z8uL2%RAw4Gz(;<(agT=$lgRCct|$%qd}%fbXu%^N4)!zDory%lT$<=vvkq%-<7`>} z%9!(H)Vp!fOGTdL^l+)l@jfBE@MEi!&!?V?2hp!foTnKp)j|`B>hIhY6IO}rK0psB zbPfp|2L0=3RD2%&6B7RjU>2Nxvz+4j4jXO!nq^ya#F|%^T?X1KgT=qjYMZlO!@G2! zV2r>+30mW=x_I!c`f;8$YH_aAQu}8u%2>BCj;V6|Ku|1EOD>3YV_ngST0H}KS0gpg z8uK{d!O?XZLVQx4<bQg|k7a+_l$*;cGk68ky*4%?<P~jB5zbG(i$O{ZccjL~KJ8Vh z4ZaveXc-KsJ;trrM&aE<yHaFa2e_nyuvew7j@ZVzWlA4rmKY>|t}e_%vXR>=xRnRy zFGXi_HK0*#L&6dp3DD&hOhZgY^Agmb%s*_T2J-GQ6@GLevN8?$W{(T^vMzSlad33) z4jE`c8YkIV>ztbk;v|+Z{4WEzl++WC#Jy~ud!E(Tr^D+TIF=Q)kGBwaMJ_Gin9l^| z)HJ<RTVA&Q_O`;))4|WC+h(pgqKuc_CtM>evoPSDfa&1%^rQyxTcW-_wb9lij#6Lq zWUw*Y%!I6$Zb5z<QK3)o?2bIn?m@NgIrU<c@K0X7i)11tjZtUjJYD)ffN_P~TL>Lx zG^L{T_oQMP=O@XwVESAKzrU@9?0xtb$vlo^4o@ldfsQ)gq1D(~@1RCY5MRBCul%bI z0WhtJG|+3;w(@~U7283=05!jsIews_oEao&{%hgUD@&LeF+4DBJ&SxFChpk%3dCVo zLD}lfx#-S!A@;o~<6&+~JBznEw%X`AYvL(e_33c3qEq-6l|3^ztJ^Aoh#Wf;z?M!W zzfnplKRfwh(6Qn4)hfvwnL}8tpTdw5;b+e7{?}o5XQ`iQ*%j>%3)dD}X#<FMM1rwT z0-m|6T@?i7ltkbPg2i7lHZj1nr}*C!z|!yvbGVsL3w}U8!xJPvQ%b)BR;+dgPBT<; zpCb~5NG0^8K)1u2)AB3FX-$AS7luRe_IJp;1L~}{`ExSz2OMIp!l~R(#*trT)`(qu zdH-xgv-^LpW444naEC#O;Ji&<Hzk%2-uh(^r0)(@nHT*6mI33Q`4y+CAZAlfjBkZY z2i!|NlxnRo2oCoNx7?Z~S2hH}tl0!vH8&8RBO*#LCLT2wjeT9-9yI7kx@*eE9Yj`R zAk@+GSz;+eF0zfyy}5{}fq6=qVhB(Ap=M7NPrPKbw^)|gz!z_|2uM<!yRAYZ=W|1K zZ=u{`lB=4Su&xA6W;dpC|HO~HrY!4xs2-?}ZRVyg6UyqUb&vO$1!UEGcBBnm^$h}w z0T#(lgM;4`wZ{Hv^mP~Il0^;3XKfxFIK%ZQ1*tN1PiEhdfdcuby<-nF^S$=`Rhrd5 z0w*vWn5X=dGkRbq02^b{)mlqL@C%}(=1;KA)h)dw-246H?Q7Q9Ho6|Uqfd0ZA^V+} zevEJW%j+(i5^3q_TjqkMtG*1nQuto98%okAQ2#peLdu;@am6zOsZzt(%SxGR!q0GC zS9h83%Qp7c-6Bfv$hEiHvG-4gskK3o3th>ZWtxc0Ut}LQHuaIS&Y<@ltTvNb+PS5j zF&(@eP0S|PeUU+tjv-Djndm8@1n1e2U$dIZfbHcx+z|+kN-xpFSm-OmaoqYH5T>mZ zC$ae^rp(+dIdM6JxrFNwu=OEGitm@Iz#Yo{)agw=;XF+vI9|OXXbGd#x_u`uWFl~I zgPLRg;F82WBqqAGFhBlIA#gh1xrz{A`TJI<DUlFhm=khWN=$$F*nBo+5`A~cF8s5_ zCDskwFG1xK5&W)1ex3U}L&O2({E6=?9eioyO~&}XRgcU5CSGEz_H$eK20596W{qHN z2>b}$Ji0f_+O8Lu^30$_oaPH!Q;a%Q#!sx_ZV?6-Q9mDZ`Upf;4=>5J+IgbK+d4Bz zDRIm7$lEL|eenTZlJJi4vWE;mL(7twOH1NE80J0IdP@VAdYVB4O3vV!gN5ne^2D73 zq~E3^<83#pMi>qAf!`k8CYQql^x2!+sH9aRh`WC7!RR5a-X_~wY;x5t@l#n2Ut^b` z8&1>Oa`gmVp5lp$f$PI?QX7W{=*Y0QR|$ln)g!7&bx`I(1Cy5MRC%;l=aD$kh1#lT ziE~*foPPml6KIImKwk+p*r>0ZDb0XEI}WhLObOeuW`Bu`is3LiEK5<<C-%LwWP z{30Zl>e3=zBei(C_rdR<RL+v>;(&Fk2r+R{<N<h;J&><DVH1<f4=cxRpnF--cly9{ z%W%lSs<tas0QS>@Ne9pg9n8#*AGIJj@*yTWXEyA4qT1NJK<DMCekAZA9(e;@yIZMp zB=k9+kJ4%4BMNDa@`OEocq=UF`vDqPp+snV&32b)vuQaL^jo+cmncsu?b9LVAjRtg zq2?>bImcI9>bH~{-T{<4l04;a9jEIiUegAq_*=KR2mk2Ka8QPh)H|+zB_D{C^@v$8 zLyfxLl{#?G3UYJ&Y7P-lT`^W8N)n0t3R#<JN}!pz%|;?NJ-80J$9OkCRohRWgJ`!X z8p%B}dp%7&KE@`xT;y2v9+~%m&qx@y9zIIIUlx8ye)uTWeMR<mgNFe~YKxI?Y0PX` zc$TfLs_+f5Dv#%wXPDZ58kO*M10LJrBk$*PPdKPjA?jd6Cs&QO7oQe_JGW70q@LsC znuN7<*y-@}QBIDc!2wrbd39ijar2Nj_8@TGYM{YYDKpc0{p|u{7i<($TXj|O>2=0y zq!`0j|6H)2cgwh2<>Uv)vL8Lkr*&z5PeICG@t(kq--AVKvNL;=I&*(U`7xSs-%Qyp zor`JZ832iSLS~vGO!Aj`Oal!4^82uf^Cn7ja<{SqEbc_*V_#DicUZ`O5?(UYdt&VL zBwk$ft&j>(f2EV@FT%Ldk{8JQ&6iQ!>>FoSiDQ+nb*hA+<)ai+R4T%<FnK@6<-KSK z<c!+SUVOJO&|_O+mfxz|&*|p@w)5MRUXurx8I4KtN3wNuCoGFjt1f5)9P<R$V|eU% ziNX7!W?#z($h_{zOu!<=!W@zmBGj2K72fSu2W4?&l4-#!Z1Y^H9o7aYgT<vbnO8zT zR`ytLQ#kqNjzB4^ma6sgdt1I64Q<?3F;C4>M0^c{M2v!xd5UK!Mc{Di3C=4??dDxc zoBN@Vkk8)Kls|6yy$=j5^QWWRk#jl|zV8w|0=w{kidIki1>9i!)!C*s+*O2E1LCK< zmRJcR8UWL80{QubsdKz+OyAB?Q?&1?eC;}XdMnynZZG*#Nw+#k#_a8JC4XRRq4XbA z-07cd4ohM0#`&#`b3iAd^!8)XZ{=Q3Noj?s$KmQ=cfzU9Qemk==Y#x(-=Le%%aYji zK5-{q!t_R7u(TwPc;C(DYoCLK#?_gPh8{qG?-!Vv=H7r1=aK80m5cM;z^!#n)pjt> zel;TcK3Z=|2hl@vy@vP8i&i|U<94DnwPddT<78}TeJ4t_5b(mQVcxEC_-x_6BICOJ zut(c=J6wJtVl2bps?TdbX1-tDIqu_rVd57-`w(>An8`S|Zo>rO$8>gJ$rk;u2FHo* zuZ8bGcaF0<{a@a;2i+Q|1ySu9Zat=^Qbyc`<w2MBB8Yg~?q@%%0hU2*+P$>b+;f$u zfUW9ErY_%c*ijHbMlgSQ4dztJpu_W_K5z!sq<u{lbHdx^Tn=GJkHM}}(RZCBlArXW zo;XeZ-1Tk@pBU5Y1sm?NnI!Xxe)Y_&YrUYas#6^OkIs@lm#1i|8*jOL);1D5Qh|=% zAEUAuJFj-uQdp{)IlmA}=xX&4p%ye<Pip}Nr@=AVBcpn;Id2U!KL`qq`K$(bzo{82 z7EkAto=824FzL;S@@?z3mYkd>3^})<zK+QGQk(EOf3GyV@QPOIYy6~aI2mfko~Xd> zPrN-QgA4)3vD~HCbG;}HL%C>_2enG)BWd9qmr^4TzA7D%k3;I@(m0z$T1j~`0SUK{ z&fv$y)1RY1b3c_QjTO-fY3W)RBV=M?x(%ZeZKe#VnkvaX{4tQ7em@AV3#yQO8#-ZM zFJsQ)n%;qaV`CIX`c&}>fk#VIk#8+@737k1Nu#nc|8}>5<XV8$y0lqOq1t+4JFvAN zS}5mv$+>b_vDGSDO!@Q0VypEedB2zhnm2zxLtV>cu9uND6Mn?0p|N?poqHv(NAA9C zAB6zK-~Y)}sKp^iyu|)cMuqj-rM81$QBe13o4#P${<>9$ETvyiBKzuEw6tYA0A`;i z*~r-akf7y&_5Dvu2%qrVpE*+ss`hMJpYj{;D@W<B8)~(ME*0Hg3I;kzamwDKG^NGZ z14Se$JP1T}ow5f1bf*@+<^Tw*zW!oiTV`mtL|`@Bv8Anw%lQT5v0&udnzeeK1Alql z9by8j){pY5Edc-lepJ`pMw>4)YWuqlsyL%LC;P=SlT~S*ohO>UU0%$2Pau|voODvE zIy4^6bOD!yhiV|eGHpHjHEE#{xc;Si!7O1DB+NbMRwPHR1rgOTIz}KR#PvImsHV_) zFT7!P>r4wLE=wQiA8qCInSC+9_o5<3V@P|PdmsacGkalODc*<s%=KGhS3w;;rLR?{ zE$m~f8t2^5n5i1SBatDUA$4}K+~adv{H+ku2()B2C*MdZzF<ur`Z69`ybEo0N>cze z9#8h;J7w$W1fh42-kn7T%QrEwUgKgv_0F#dm|GSuc_;K;PhbP8{VMt*jRiGx?zkj| z$i05rYT56p&gI17;LR<gM_m(H=%y!-+Lwt26!ofXg~{LIFmdo=pYd0gb5lvmuz#q? zZTNc3)@nqDVqCzl?7IF9ezM>31d~G>DiV~5Svh$hhdvW`(Rk2yLoe4uo_QS1AGw-& zT5wqtSHp9E8x4ic-=5!?L-3|&y0sp%0?s~6%NtJK!vaitOa;+Bh&|4@Oxk8908?d9 zO#7wlFUZGb(ero9-zz$ay{>qC&QHEYEaluflW+z}c(fn-*WCqpG;z`6{-L#X%%)o! zW5Utsw<>j&I+T_k5^faZD-aTQ=DN51Enm@giV6)o^B276v7rtwJ<TnM!R7aqnmqVI zW6^VQCoQr3$*CNnbJ}IkYh#S?uK)6t^02zF$ESS!<AlA3!OC~DI1$cOqUtD$+~0>x zqPTzZ#^C)R4gv6HdBCAX4&$M?Q?t|^kIeOkpl!oR*=vrGT<GP3B|6Qq>vG%AYiyc@ zyMU|o#z1l5?@h4&AJ@J-0|!edGUJzhr7~K2rkgQ|HGvo-YHzcu1edf7vn*;4y!q1< z=_h(N8yIi%xentF&$4L5NIjT05-EIno_WZIQ~OhR@Csd2-b2Lhv*u}?VulNsHkNzu z8<j2M5;<0;C+p0rm)mA~+iW<N8eecL>LpeH%Q(2wpTw2F`>m|fQghK&c`&9$5}=Gg z`c)hT66RC4E~HutaZKqlSf0XFrZD8XTW)J=C969m^Zj5c8_@~3Fdy)`7!LW-y@(NF zY+?Ubo>|@JNe`czA)$#*!ppT_Y0w9cK^n=HKyFv^IUFEJEX0ywwFLRa&RYq`ph}N~ z-%*89r#qwJ#J=+#6n+jC$=nFBtXYuZP6oygJp>eFx+A|1D9q7xY~3171PjhfEy`*^ z$d4fBFb3Y+(WHQQHfMo7XN@z;$<AELY=ce0RzeSiAwz4;@(tN~0F8NS&XW!N!g}-? zV**j+)4|1zkMp!$g_D%K7v=+*_S*}hAn;atbQ2gPCV=G6H6N^7iTi5VHzLPYS2+F- zZ2=vTIt5m^JWYR5nSfV$3<O_3o2V!mjL;Y3^;tl{xsAy=l5KTH$2|MSv+Ve%w^kOK z1=^uqfWIg3kgQ$i6t0a}?JYzs#~hgr_#7Lmc1bK1iiyzOd?jAWL5_&&CYHBu-ObqN z;#7Pa(5bRu$V`=D(ql{VbYT_)ZLDtv7e;z^!ELylE)<-4vO4cxOm<h*f5#N&Niowa z-aT1Q%YjDf0Bo+IpQ*`*X5LE5o+FXdV<Euu8L5k%BBb{d5Q$1g@Z^s~p6OaPj=+?M zNvHB4iotp3@bMN6T&M#bdr+vfI^s2<`Dy1M1?9wPuLw5-er`8j_p!t#rQw_JXL;hJ z9&rLK8fSPst}778&nkhe+skVmT?>{&`Ey9uor}mJOAp{4S7VTLl9{~axm1Jwbw}U! z(dvpBw9&O}qD+grA{n=r0IS%6Kbh=YJxg)Ph<xUDysB=0npw+K2n>=-SQY!Qo-dmk z6{Ozl)_}cxMpK(}jlkNq{Ty6CbrjlefYZ9DjwQPhT%wLC!J=?zYEx@}J0(lH8HD4i zhA&qwkyu3o=}~z)0URqxi!iltU&p;>0G0o~o10JM{ZP5+0~YsK&&@Jl4p}<EGp%>; zIokOk!y<UJ>?PH3nz*WHtgkSoo)xgSd$wTo{1DyfbF<&hhY9|nom5kR<rH(JN5&0l z>xdm&P6rIdrlVUo0ZV-tLk}lhY)YyqnA51mz_vSm&a>tUJ~OY{%U|!?DMu)KnzzU1 zk7AT6qwt%(_g3Gkmw%48iT}C#fZmW~_Qx1BMN(Ur@};)*t(oQAX`oz2?z=()D;nty zKW?!4Xx$r`)3M`}(zk;QYz#SH{6;o_zxJa$sInK()&aQZ5w8_761~2b(*c4$XX|%w zi|Yt@C|Q09Pk9iXNImFbN8^9n(W6n*9ijI0*{a>~$j(W!X~tX8)pUYfTU+Q@5$h1o zEYFo&xxYK3^Qf{Xm&Y|Ge=)F0CQF6OO6r@9MeAh5?=W?ntd_BU=9ywl%EsVK32K4F z2NKE+!GIz4Q`uFo`_IuC)wTOlz)q2`kK{MYaCi;%$VlC();tceRqw>QKit+wZg8t} z`Dg$~u3M6VV%9R|E+(%opxyZW2Fd6$ULC0Et4`)X)yp6e#Vu+Y{kRoFq#C88Ub3nq z4FQ|7(%S%sTuCmI%k%}9Skx|?5o^c3rh{C3!ow52$AJFjmkIr<j)DsZR>4)i1#RyS zQIpeD5LkXag~yc6>zd}LC_f<-%>TpJTL4A2bM3;+4DRmE;5N9s&7h6DyW8N<xVyU# z(740k4DRmk?(SU9Iq!eJ_f*~gtM2MfvevVn>|HylWUpiezM04uCfhqL6aW6WQt=v& zJy`3{3WNFHuMv@y%#p{rR%*ob%b8~_+a0~`lJIA&XxUt#mqz=x{l)jA?fhVP|8=da z%zgUJvmH63{&;?Mt~0Iy(D+m?LIqZA?*zT#c~Y4O?`_hu=KQS~@1%ILZ7LRuxA%6! zWtm<NRs;ceR|D9kO$PqUnK9EAuZ6aY{-~8v>r(9VqDTikk(XDs!?M_wE|GH5umKsY zOT|$G2wTMKT+4y%q!^y%>5doYz6dq_rEUKXVv)d#iH+Fg+z)(Y@IljJ%9eRA^R3Oy z{2o$T8mCRigcTcYBy4p~d7SCK!Q4!u5`N^YvCzrp+%2Vk!2{qajmEA;Gop?iBXc-L zaIj>38}KwSS(CVeA=2*A-Xrs1{G}L<fYmW)p3{hvH@c(82(9K|*6S394C%5Vsdu&w zQKV;d*hwO9jQCIDuQge&j~VhCH5IWX;#RFG6%rDF?9;UMMU1)>a&?yD(9@o54_gy^ ztL21<5qCV_z|M<OjfBvTBd8V9_t`}aX>_Y1o2c~-?~VK*3*~)+l`4&Qd$Eemz@13N zs=?7_q{hrf{p<0z-_2{xBTd@1v>b1YBT_nfJQ@Pxx@%BR;kUHXH|n@eO2r+ObrDxM z#)DlaY}ckbTECHNmprqyhIR}@PTNU{K2DnJfQ^T9J~vj#hNYy+WS$x*q2;Mh;@cm{ zE-&3$oNOPs0N$pKvty?+DieCLm1Co@^K6$b?-6&cKDz)8zV?jQSQo=f3wbs!XAUpf zkQy|YNEzqDRzI(0)V=kh87Y`=-Ksc7p1cwAG-Z2pJ)RFr@c6GMC`OO2FqU6%6fGsl zEmn^hr?`#L+YgKkFZ}%8D^egs)}ZEHk~d$2mV!51(-|=v1=BJJu~TayZG~j}n)m>N z78k2O{!YroxO4S(lAX-dGTko&(uy7xA9+1CQ^sev`UbO7i6{1IVWstMGcSFVU%RUl ztl#*(zRtC=*cN4x1X}lPmI)RI5}Ux={ho|j&T<`xLocqKFW-x$ABq@QpL<uD2!#s& zIAIr4+@YI=!*loh)Vb%~Liy?144L#n9re6Vu*W9ioY1B3OKOeEnWhi!AR+Ln&UUDD zr{k)(m+iV7)S}_wBK_OX0?w?4wt}%|q`f!#Zth+$zQnnuk$P{zV2-}j*V{(#EGRjw zE(|XPyR8prmakLP>N@)^UB}Qs#01i@4=^%pYPxpr+>L)(+X4qwXHaX(p{c^a6&)#A zdXB^nli#dF2}Fkk6+ih4mA1um#O<0U(Q_YoCcMb2Hxrv6f5Bl5g2X{zCan!iLuj9b z3E^lP&`%j;?crEYB=3m#eQ1_{?xuJo1tzl!T=sB@j##+ge9HsBbYW<(5p2aniD%|u z?<EpHLBBteQ`a&&Vo1yO?ukA*EIlSftBSKqOCWDQttceyFB}j5F_ybz@)L^y9w#Jq z7=gn{yQaS|Rdy1oaNe*%gtuv<1a5ECfvyDjPKfq+@+^ZU!_?st>Nwx#I)W8}^hC&o zk0$gdo(Jl1*?Ta7iwBL}&~a$Cz3Lkzp01_&Rr8+EEoV%$vGW&DA@1J6YDM$_l&$Rf zS%)3<B72^5+qX7+CSy%!8yYEb6W}GD2s<FeLocMa$ru|YmTZoRd7dnfzGV04wlZXc z4(CN#KyCx6&GA;H1slabJUc*Dl<aQ7g$4EODCWl>wE5d9L!<GXlQ;b6Tq8xRTzO+n zwA-G^L#qoQSxI?1tX`IR7i5&}<6UL2{#=zke}<Lm$e8BGIdy$-Dfb5UgR^K~Dbku1 zti6*B-pmOOIT|poO-rJi#i^L=+4VJN0=P%u))yN*;jq$)q|06N=>Jgo3J<w*B_F-* zP?(RoNx!y;c{1EhTU;4y|8#6_fm_^bACfsm_DRXSU9LR27cRR!L~D(%LUB3UDH;7C z79voqyH|hXE=o7fDD(P+cX`^htQ|Ezoy;*;ck{q)r2oXlT=B@w+%8Z#LOvIAf=UBl zVv%ShG9}+ZqCs(+@VG+iZz}9n$vxd}p^s<6?ow{yiSMW0LgD#(=@-2Sb!v44fT0lI zIjQrm!bj6yYK4Qc{I<UCt(L9cltQHAt(jWl{jS<U%9!%_*|D4OIqF{K&z@WBy)_3* z)g|Vg0DE~jXua4aPC(T<uYF*ryta-`VKx(Y<mDc9vCwW&AbC0nUWNcFtk9LDc6ri# ztSfOqxSZeZ=Q&OfPa#t#{}NucYMb*k_3in42}7%Um8iE@joS0d%&YI)o5dWLf(Jf| ze0La8c9}5dUh{4fqJojDB#zph1KlFm&x6Gi{<eYWQ&Kj=5Wd?d%K{tKc>3FX5O=WR zQ8dDwHy($<I;@q+OMl_+x@k%#dw$ba2*+OArJLHjH9tiI!E_Kc8B@o2x5#8>V2W+b z_kx0>Z-1KN-j&*M9j6H*YN0Uo9CtiYxx~XG^Vjwwn&PCKV45jp7O`c$RLYH^5u4(6 zmsgKC&K@LzNb7TiJ?tbNh=lH(@Aptf-`LGe1>S6p9QMXL@d)Ze-d9vwD9mx5h8+!z zjSoJFTFKE$Qiy(!VbdEsk%J7$2EH#%LvWy!ZnvH03hc*L$9KEVjUo4hXpvT*Z_70_ zE@WVz*89`B)ZS06gBK^)H?5GIKP#Lzx)v6t7wU_s5A-R=QH*pw!<I_+>yzf6S!{*5 zq^$D5YwnbHuQa2*DjiRMW8$zGMC$KNd1%%3q^yQ>)O}zzUBomD|B_#y7)xihS35w_ zcF%70Fjls35&35Le4X6Zq1CUndNh1TMgbo4IKHQ;2B$T+H~P$=!Wx1+eahnEZBq|? zo8)Y_6=h-OvNoD~appj!GvnF9G6{5d+gv`EIwKP@?VcZFqKq|WtVMzv#w7W4<}q_n zN3=EKtTVE591U&g@4;5Cc)PoP{9fLG$fTpzYs=G|;rU9cSJI!9sPw2AT}FXLJ<D!+ z7H*UNxL~&sCI7>#XMwf4$(&&Q6Pj)E*QI9OK9i;A5v*<QcbC`8RHshtYkFWrTIR98 z8$n_9b5ekwP3Z0BQoi;=o?OL^R5KV}wzkFSqRn?FIneL=u~VMeC&eb6>WYMB^^efX z(-b7>iJFLB&#!4Gyqhgm3`X1r5K`wVBQH^$Qtn2C^E|PSbtsL_#_eJ+zohy#tviT? z^yO`~2aS3fhl!GL4MHE_I9YUWmx7_Qb&)Jl_sO;`(b(#X!jPv2PBpf$C29<fUi>}s zXMZ(Yto3%BM1Ypyc!%elzJMAmfx^dPv{k-VY=$fKn+tRt8-Dp*XpXWp@ar#!eLei$ zvtnKqhWz`2f9|FyQ#xL{bC4L<4S;#?MXD|1dRxqH48~*FBdhIHwR>t*4anL`Hz)L+ zeO%X0F!q~DF5O*l=_23>bJH3C@mGpOg!pVWPOO-6`YYsHfhXtkYK;n>SBO-Yb1|X^ zoKV+u@m*1eR%Gw_rJB=Rj~G35t%n1&r^km^9tfoJ{FseujPV+mPg@*F_uf@}=$Eo$ zNa>~v5erf00{5Am=r~U5t!^!J5i^fG?CQO;<m=ha3|bt@Qp#gP$L?vVBCl4@%e4cq zgeH58bk;K75i%}rr=syM)ozOi_QkrmOfK7TPW<M)BOZ&kj}hDZOun)@SjrB!_se97 zXgdN-_pzq4QR$7<y_LTM(yn+?oXW<<r`Nx-yJn3?x2%XdspBSw1b5`~s{oA%>Nvlb zBu7`P0nHc}-}LNm7w{l<Y*kDBlE7YHjkn7)l8r0ABQqH<mEedV+n-HUc#p}5Bw=ZF z<(~jahRY_Wr^a|lg?0-E87UtR_lsKV+_HI)$2;tw$1mjvSnZEelZzQbxW)$ywq?FP zddZNE$?^(on_p|Tb=L+xVY<>!y*|R06m=TT1oJpn*;fz7Ly^9655C%n{Y=F$De5?h zXATW-dgfG3DT7Mk;B*?B1(``#t)&w<&5U<2mahNgF8#)Ik-s`1lKhQvlPs}`80OkU zH9KR<9{joxQWI;%sf#!JdTlp-&M<5L0;O~+jmh?E=J(G2IMX<=c6V~ZG3A*laaWZn zKbsN5>$fvNKsOD@Q_#}`d5wA6I_K?;vnO_Y_Ug9pHbT4h&FIk;X{3C0V-uC6FQZLD zV{Wc&KAI@YZ3)lT%KZ}G#LZwtOFdg@p@?X<nmgzotC6vw{kLOsa6#P5No5q)Yc(a0 zNZilTR|?YnnWc=zSMU(8W&sut2hJ@`L=G1YS#dv0T{J>eIwLpNrIXH-CeZvwM<Y6{ zb!>sjDPsDB*8nr8kC}&R^g~Ca4B6e$ywDcmvrP7p&qyH#zNKpmD$~ayf+wEKL008) z`1+!VB$fNdtC4T{IqlS0w#xmK3Vdz|J-?Jb<1udO+<2!%sJn5Y_^-8fS{1nmA9(VN zP^)A1HY`Rr^@oXP74{jN;apSY_F*|Xj@a<l{ODhKnGYT26VN{!h1xDXxw%Gmb9L^S z@k-IuaCSZ2Tu&)YVTO3d)w5X(%O`?xM_R|%V{62Byysucem6tgJ9{iW(bM~~m$l5} zXsPw<?QO~uE$ac^aU6-2r*09`ae154-gBN<2=p;1tV6ssy*GKZ=#Fu7y|`tgP6}~y zzCV>BMD59N{8g+f!LqnHw@IPf5&n{6<BEQ?m|l{Cxu#|PD`%-Pm|PX_@SS(!Aha7| zIDElXId1b{gKS|W=8|83h}$amT6vMP4#qLBSBR4LPJb*~y~~&fdgKv!TT{n_@7Uhw z)`osOFR_+fMz06on~}8E@gxg3J+7$1aai_xbKJyewPxGq3*qv5KHBBGKx6lieT+nF zpPi&jIjmS(J<8>8Tc1H^)tz!g8A08r0%bKK5vdhHfBI!g;)N@yX^G#S-qI+=veC;W zzXQluSLHZ42ZK9;;kH+tC^<slR8Q`x#P|_*ufu!?mT8ZHlW+Ts^VR7*`avSNoZiIt zxn_IIr)Xh?pW}z)D>u8oYt(wj7!gabaeEY2XmK|C>ZRiIY2i%c)zzaFL~+x6Wn1zx z!qxuhwc~T+xQKLoLI6tjW=Os3ov##waY*`;iPm^UVu*JQo&2*E6RKwhR{Y#lD7dxc zO7wmZ_<Ac)x7a=Up}rq8%Z(t|wHE((|1nx@>cWOX*BH-0r$&)g>67@Io+QIfyf&G@ zCJtW0_>=su{14QlF5D76pyx~O7WC`Oz)st?5p4R)b=?6X(05cdy;X~E38t0^GV<qg zWwyZY{o%qq`4o?H*7@My;Y~QHF0E@khBhbpT)tV0DR@R>w2jmEjk&fFjN?=HmAPqg z%KV`znoO;^n*q>pKpmhy=3u|dykyy$&3UkOA3mW&#}L@^Pr0T0l_tXFvRTjUUDL>N zk?o|6Q`Vj&_4R4|qUdg3&G<3OCm7PAOJGTFI_?Mos>=D}B++#Sg0PB;Zl@Lzqs3$v z{K)MUB~&#JQ<25u-i->q$+(9c@W}}~UHN90?l~@8#>C})CH@(gWj}(gG{s!RiSudh zcMlfe{0@dJ<R16Rda^Ds9qRq-a&P?7Nv%6c-ww+5*{?;?X<CUS37xhuPVYQP?d&z- zmjP-Lkw!*|B=Jq)Tx?XxpQ`u#^}WZkkVwM=v~2yC_xJ0obxMXCjttDJ^|AM7Z{$rz zPB1XUb}(=-Fvd`I3ECtuL9kC?;L=H8U|>cNO48<zKj9UBIvDF)8$0~4F|kDzwRJME zawE1fx3M%f`uKDF`LWr?2@x!m@nf^isfM-{zA6Sk&)rhgX`j>93wy!zM0U>)++~Eo zmpTF-+3&<ol5N(RzkaZ^@EFR~v>1+$^f@n0C6<&V(qO=TSI$L6g}&_k0^&vX4Uk0M z{q)sU_y~^o0t_j{F8~%PamZyXGc!}Ya=wKGU3sg*<ZR?5^Lh2!ahz{|g~{waJfRRh z*UVEGhndRhR3u^K=Z3n;<dtlaRr1A+X5ColyBP!H&yAJDRN-*m*G-9R9zPdFv)i&( z4{i_hmFp2>;5n*OfUVs-6@e#n$Pi2l%yi?!;q;8}_4Z8YH6IE#ij(O?X(Alt<ka9{ zbS8i1q_&%%0Hxj6p=4VR58W4`{ho&#Cf-hL>hECwvPG=J6LPUZ*Jz);RPnj3Su*`$ zWn8XQsO_x!uR_f5`&IBulWa`Bg2Cs7V@MK+^NHY&MZ-i9_8-=;$O5XKL~?v=K?v~3 zvE1?`+`&u8_SRv|!M{uJf5mXoRa%?9aUThVK*$Im=FC0x5xZ0Wq1*H_anli1CPqN8 z$~E)`7Zp7|>6aQ4UgYL#V*6S^cmHE<)h{WNQ-%I@H|~5SsjT%Z;j=$Tjl>$UUJQ3k z4(_ai(`G5l?z6Yz9L&#<qL}&DPWYkr_5*h}Qryl-Zeld=a?+7sM9?gaqH2Imr&P2; z0hgi6FHg5va&Pe3aE%wA7k-S-*DAV(3s|xKj(Ym+w2^^=dlvOXp7%58dF9(-Qf;7d z+eQL||J&zoR;n>Z;9LB|c1co^8{N6m%!W6O|2np>-UVzx*Zj2E3ncD!r>>^;hLo8d z0m7c~hU7tZ)YI3z+f;MQO^_y)UKF>@A^S>VxxyH8Eris#*!b;bXuerno+p6wMfOEM z5ncb-mL@Dg7nYibR4qmUN@^AukuIdDTPQw3X_4HVdi!O31i~FtMQR*FdObS2&HZ(U z$otmhHtm>3h5%Xa2oHlzzJrF<I(!;ExS$FRBxQkEd12J1#`yI@=z%pHay~M;Cxjcr zctm;%%vBcM%|#SksK3X|hT)IHQAIhFqdk4+`!=UWeTEI^wdoI*5a~}X0x;~STJ<*y zO)2%fLA9_dC?-q6hauEN#Hq?Mk%CYW2s`-%#UY+`Ur4j%M9mI1=0?x}4)eqKgV(I} zgn3ep8BhE!5%=7egm8!c?j$fJLN_R|hr$Dml;6Kx<aae@qL2q*tYlrSh<STO+ItRr zZ>Yt~9Ms0j5Jr;yT#jxEFT$+lfjuJ?jdvO@@Kh%9fq2|0C-<rUiUkHV-F3eK!Nuyc z^>r}D2G!Op2+b|_3r8;4%T9W>7m`hk=GkIYs7XIUM-l$@)RN!mvmU@dHY$+7=P6<s z*HMW1TF^34m2*Rg7VF_XO~U6b(++*MEt$uQ`y8nu4yUt(^{aanC3#X`&V(0QmWw>R zea`FTtIov4+1I67im3VffmRb6ZTMNTvzV_l`SW}H`VU%_SW>YgddaJV^t=Ez26OMR zTh~b&6hm$`1~u63eW@GwX{M~7SAR0P)OLpFm^#a52rznI=}Yb2e=AZ=1fL^%2ez<< ztd&x^y%lFYOJi|=rQLaRo3gwAb#T%a3kU^$umnDTVXyd!M#!aE%PuxS8WTbcIa7$A zB2lS?9<75+XdM~i+jUzL#Bbo9%p~u!{x+m5m>tA1wt+A=$vER!T#}zG%wXKarS&sK zZ3)jwTJ*KyNco{oNSl^ItRaw#K}%w_N^~g=X>u#KlP_{2K>d|OBRzvTq=sZneafzA zrBifZ5=)_X_+vL3<a|INhJ@mDMgv^MprzVV3r4KOQ#xt1#tGz}voZg1>a}CS+rGeW z#VWSZJBXU^j5EV|NC*=Ie9@5e@{FjhC^TVogkg@9@X*05YA2hxT<tb3SvE%};GIWO zb%p0!Ep5%B94(;-R8gWZ-Gc|}v(}P0^bg~)<oJ5i1?iviDy_IaR-xUhwLwQ3-?BSQ zu=*c3h@qsT8H__$ciS64gqj{8XV2_dA!2C+2iZ+HZd$8sn1C~E%JouA=z)+&C%e?= zkjv4JvSreQe#%euq4q2)UbmJ#@f4C~OY%)b^MXXUlGRuyU9h4Gmj>R@ztLbEJz^b^ zPuaB-hlIM7EwZ$-(#BltQC>lvP>mcMq0~3A-YY%BvJrsjBQ~YGnKGK(3?JOPyt(KV zw<goRT*I4Vo2f6b;9x;x`aAp}rS-O5&_U7)g-?}ozLoPzj>J~u*fdV`8s3HpLQMBy z)kbwh%qQSvbw#L;i=*sL`Hr{^ZXoY6#}eLMtv{kn#B8Qx;<d(t6?;E1N*=sQ^K;1) z`EVq3a+^1(7r>!CWw$?o&5uST68##Y=n%HMHC8oOnG#q-4!VZmfCg{a`0SaAZh>^9 zES2nV5HXh{6fIc#v?u7#Q=ExW?`(sm*+xS8`^$B>y2!bW_cO#q4vy%#YJ%<Myt`C6 z3M?-A#0K{fZ*>7bE$<4N(+|7G$=$n2uXtJc%E1aZ*5`{r%k{o=`Qo%)8xc>OKRk@C z(|*Zn<IOB_$h`c-iDq<+3S~I7LyPVA?}XlJ5R$$%?Z+7UitEvVGX5wsdT7(4n^a7o z_W4QlWA*jqqoy7P4#&gyWAz^XZ#&zqS$ud)5k9N~^<STYxdMq`mHeAei!+p|9y}~! zZRV&STf`|{&S!Utyyh2nY`n&|pXGF`d0hdhkk_T<Wj4SsrDM3xQLGut>^!l1$2F!4 z-w?+_yk@SGvad7TcF;o(gz!6ht1W(*_FYy8C6rDismgQsa*=&AeOvx!3bA+M4Sp?6 zC7R7T@oQxmYFqfEo>Ka}&iYx!?!IC&Ha1pY)y6sea6Bz5E1iX}3~0v<H&-@1CkBV4 z6e+dy-2Iw(bM@IAaz$V=x7+@~37%1CM;lf<Zy^1PP4EU5xK@*Qs9se+zltBN<)c4a zFgy$cP}sWMzLsQa+V9uPk|-uY3o@|jH)n$Vm)ab;<Z!EUEP7LC*%3Cz0(yobAheTu zj*`lrU`GBbti?$bcv1&I{1+Vc8Zn(hJn4X|1@mT8aFtX#YE@XzX6oHSXrfBzm2m!* z<$bUDi99Pqq_ug-9D`22(!qCBq%p1*s^#ux-|#8I&~ibVV5{dNM0JlCONaXLEQhu& z`<)GK+YrW4KgZdfNT}7M;M2{vl}=!Evg<pl^Y?Bym61P1aS{ASNuL`}R&zaZUANu( zvl)r428#vs0xHxXva*pj7s3o@QJ*B@Atw`ISNQ{*MRCU*Kfeg#{~qr9rG>wY)1;76 z!K;otmhK#*A7JFqY!iUG7;W=>!+Y&owa3zY<ztTDUkw>Ksd5mmpK*zA4!MuUZO>0w z#Dsaa(s-V$_Un(P{uVzwj4NSby|fbgMat&Q$FL<R2Y<dtc-2%c!Fu_MbLAAN(y60u z;XW`d%uCs>sDTj`uW(hre|fxjA|@xuOLwl;4=HYGP}-%xe>92-<U#%G_M=;Fq?48_ zt30mu)=2BiI%FF6&pB7BQ4XiQlXvC7SaESs!=xK`<V$)=Bb*}cL?Yl0%9<=4el<`j zKvY($W1+C|)OOGcghlWBM9q3G;wX#eJkxT88`N2g4ub$AXtm`dQ2>Kx@>>dmc)71s z)1~k$c3LnmtM|U<fdGyCR|8^E#6WI2+%t3o(w?K5z$p6f78&#TO%`&8aao3Ir`?mD z)QN?y(=k&g#WCO%_rVzYL|&Eh269zZinNAM_t3SGO%mg?j*Mbd4`f6Z;=HG+7cf5U z^?NQ<bIOei6Jl6VY^z3K1?!-J-_w<YzmM;JEfyEJtaHDBxoZaZseykb3MIxY{1oUE zDJ75{5iS4Hmw<3#9_hKGcou9`nXfR*!5&?HvTOgnww;2qT}*eRfvw<6z#;%n<raSW zPI{8sTG;BB=A-)JTs;BO9kyVnp0Z8Fn0%fwLqPNQH2G1vuQi$Q#thaB<Ku?YRj@%` zcX-`#x2#c#0Gl3zk>XsK1<oM9gdX;dq|86gDifaH=S$Fv;XJrE763gQvRi>RU#c`P zl-YYi&xyXO9MyTjjx4F|E=1<i1yCWHL;A_6e?yJ>ne1uD(y-XLak-NVDYQz-!K=vj zF<7{r<lvu0=uQ>^C_zpte|q&?VLRwoRS>gOXwt~HF&FUbq=DnQ#@f)`y}ml@AW|!| z;v~Ch<fZIKGx&nYL<Pc28Y?@-g#Qs7CW0;(Y=BXsp3jdnY=0-<??E}**(@<pee4V6 z;beIj!|!1Cez5T74|O|PEpku9$=X%=z!{~YP0sT(aZ7fQa@+7A2vlpwGJhQ`a)OJo zgNdAz^B8av$!~xwzJ2o)Rx_q;kg00(E!-E;8eLjp|9q10SaSdT2;rwwH}s(qOw`tk zlu`X=-+m@-6jJuox+06#Bui+mM3B_|vByvFm+k^4tpstd($yoSKQuL@f-Q_=9XjcU zai`s*HBCdjJLAem{mVOIp`xMbG!6;kKCR%443z;*hl*FIoxafDP;v9?f{X&c%dp3Y zu|$#K;#AqA%AG;_;Hn=75FDs}EuI-Er=gCY=wS`9Xa+Cllgh8AjjOVHpzys-f4#gt z30@~gY`7Pd=9=<x*BO!S(%&i0Z1EhT8HIEdfhZPbx&Sa55T)1WIn~U~2sc<ftSDC7 zf%=5!1nO0nFxS#ZkCk0V`Vf@a*NErRG;c;ECn#_`n5G`UHI6mbsaoPMT11A_jyc3N zyvg*y6}^AuZK2;{sO57S$6+G2yMT55$bF<Y8?%>eULzffV1L81ADRt=<>#C7S=ZZU zi%@K+2vJOE>~mj}-ARp5QsAKt^R>_{J&Q-S@(#+@?RY$b+4zQZXzS{qpD0k-*RB3B zgMcLZQ?&IMHWbqH+O*T2R1S@;)-#WOz3>Dta9_xd>Gs4?=$c~)28OHi*X)(XQ9bRo z0*w8y*{i0~U$a+8B?$+8J2ONRTXQR2CtZErZ@P|lRuF#|e#}{kaKRjD{t6JW1g9l+ zN(6QhI*%7f@}nzehkqA!3%)p(6*k|%X0X1%;48)8+D%Pdz&BCa;>q6T$8sml^5H{% z>QS#Vk%9X6xARB0C(k>+j{S^}&lS&p9e=gi_NzIc8fzLvdHQBQpn2LI`+c{@^vinm z6YB{}A<k=KW4eCJ{(ECf2dmX>p#==dC?JBtMG@TLy+ce9yhdJg<}+L2O1=mi2sSZa zK`8(hAdnEroznn<F!n)I&UIY_qwc!|f!&HUQQ<iWTFb-Dq{OX<9sv+agm*#ckrHD6 zB{P)t=9A20R*JZiM;sD8OlY|U!DHm(CxlK>yBx}+?*1nk_gATBf<Lo7_b%f91xOaH zR7m_#3i=ECYo+k7WSBhvkkDZQ&^<xa7=-;7T((I$`foUX!jHq!51kJxiVtMoKk9$8 zrr%P94gr{kB)F*J9pVYe<#A}lxv0<z6m4em4HBXq0Dl2;{tLkSR~`N%(5e)3qw;si z#N5q=mn5R65pqjd(@^re0zeidgMX9_)&G^Fnm3b&84|%s(4znAx3r!L$rw;Em4})T z%uJ;!35eihy6agGk$U!)cl{0^+=ZooqBH?qI{b~n|H~?s|CF{+Q-s)IP&Hnk6qEBS z*&O2^SD3zVCX;uRV{L$GneL55nM>CK%yu9);{(~;xP-decvp%$%M|)`K;1i5uhtAo zX5^T#X&%eO;Q{LmVrCfgBz4_;m;oyHCxY-OA&Gpl$xRTsMHHf-%p_sLM5qOIsDJ?< zT(qga2WD1u;J)dmlllS@=3RK;t5IbB*l^cdtsO5NJ`r61aMxw+N#Hx1-sVe{UAr2m zH(iEY(ig(u>(MlSrV1#y`QX6g-mY82P4^l*FZFATnOn>%0K~3v1e1VT3J0C(%DLb3 zqIb_zFSa4D=9{j1g<7=RXcw<(lRn=X6kRK$+L>DPJzO|h?rTTY#hU^BzC6=faM`W~ zjm{rIw~u%U3hv(kgfv3+30b@!VHT)Wng7X^V*e$pFv&rcWa!f(>_b(OY6|kb0>JEQ zKn{u2C+NT@1WN+w3pl<c=4eO~{cYjc^>2)64zH5%V1B-ILUfosj%1$u5W>%dPzL}% zjkqx=%YlqwH^);cVhDgbt7wz=@tpmCTB9FYSMp4f3O2d_gO$|f9unoEBEraLPWZJV zDUte5QuH<f_#dMpKa}K}BBTDc|5q~epQLdD&jA(MZjOOc&=IxElsw`GJ5}gMa#Eh3 z=5I{;Uu=IT566WbLD-48U&*tdfZbb~dM7?L@(eVBVrINFV!I&7+1$*8PnaP3A@L>< z5sl!sJT;Ak@;}8M5g)A-(@AjgFTvmXe+lFvar6Ju7kmAPP$66Z!k^9Iu8U_Lz^?yj z)$D&4<3>x$bCn1=nNb>7F<JO7<yj?!!%ny=MU{LscM%@ydyO2|wYHD1R@uD>1PD4Y z&p|2B0zk0zqiOtey%+(2;V3xlzw{ycK=emG4)Py-+aHF7*+7V=zgBF0xpO`YKjo6L z5+>%;C<Qw!5PlP<2WZZGGR_Brz%X(zLFkG3xJnW@1`!%L4GB>12R=-AyLrHmdV!e2 z_F;POau@i~mxB$S0aipboz8z5`lyQk(S+=fNFNBoBK+f49R<SzQ2x21e~5XlB9`(H zss1wbK_U-Z%m_x8i_@n9V&g{14C2m{Jy4iIyA|ii=b0qFA;ygo&#g8iO$*!0?=>S| z^5GX-&mlOF8zSQaD9#)Sw4flYU`ba-$%$UiKsbdGKEue^RI<JPu+3{#P1)mL#~W%J zVSry(hhNm^3agGX7-+UXTNX~DjI~kmSbx9`9)Oqm*c&}3`Rl~>*}B`9J(fw?C8uAS zD|e-t*+$ZyG#7a&FRl;QXuw3SriS7yVC?MGjwl-tQKw_>#UATZ0xT_$e*ix$aJ*Uc zOdq($-NHSg`Fh3eVheigC1EHS(sAKrB0eqLz42eg5e35Oew$O4%)q(a^W1jEwcl;6 zSCpj6i~*Wyn-h_eD`dMNRq*#x#N~fZ6VQ%Fr7n%r3)B(P%C(1@Itc_EG}1j^h;nPC zOF7$GvQr#6Ofypm>iV7RJB&OIB78<X4`GL$6WTm&EAl&7{8s;TlUgy#8l%ps^@3?4 zDJrYBWjNXC)3V%*2w{2@GIQTZk=^kt@YnCE*|QEe(Z0lm&Z!m}(zT?PDvdm1)IRa{ z_h&!90ZgN^O1||UJp?B+yc-vx@Oy{+`g1&?5mzz|qbjy~(!HH}94H8PZUU{EUa~?D zrlV&?i<163*;gXXkW3_i^hz$OT$Dz$Oq}=!1K}j(7sl6baATR(C26tUltyA5SqCCl zp>d#rt6cU1(beH1C<n=r(7lv1A))18JvYg1+YPwW8$0DkM_eSrTPahz)ot3}zg9Aw zG)kQcg2QLD=xr%?-+xcuy*{$6>nSguq<{4PK}yh0baFyVX2T$rm2SWOZQNyMtQKA4 zcb_Ixcwz~e-g|f#D<eb(TpUGVFE#XIJdeGXl^XG4mURzptVmZzGcGVJhBHrw$f-W~ zvoN-5VmCi6wEA!ImNbB1Mt6ncTT80w74K#0)+XxKOI?5mk~=fBefQTt4QBNA`lD<b z*9i3X1}nuUA<(!d?OBOT7Lbu@(@Ny+)VV?%emGo=mgaU=Ci5Va;J}`mMzme_;-K5K zxu=)IAGW03+iksn+Jy()yVXJbX-HgO&KyGmS${9`(dtEHgMXb{=OE{sLcN3-TGVxh z4W+GimMvG-*!npT9Lw|>GJ}BIA2=BbCGqq#eZ%-`Kt9~Z2L3Bk6xC81`br=cD#@+m zwTlPzrmTt4kFh7u-_`leb#Jj?HXWYc7Tn!PX*1PKpPa8ZS5m~EFWZAI5oH%?udsx* z?xSd@5KuYO(Yv6xs)=9tykFrIzOypA>ARB*q`UqO>bR|L#zp539gSz~6?9+i+3#Q` zkD<WjS|Jq!uWmPrWDSIfMZCtB3J<9A9*bMIeS!-`w>~ODwj_*INqD_`vY_9z$Y^l_ zl1DDYwOoS_$BN@G>RvwR!@<SfjkV#vCZkt8qi}TDa0>>FeXMQvLH=rQjv+qFI*_r2 z-nBwcN;)DS&|YRF1T1p^jE^VU*>`KFuCkD1XiD35v?f4=qV{Vo=g59vdbd9|PegQ{ zyXP06_JM4CMrg!o8%v^+zqx8=35FzZlLOZ%-xQJDPf5@j`#hlG;x<Fkg@T#}g%QC| z8=y#XH~Yxk?bNTbk3RxUI(Fa0u4n2Eb5fr|b@w0veb_M9M6M`m*TnR$x~>8<ATCD~ z%P#vV`Sj`V3nTPqQu;WZc^tTcKoOlL-njpAZl#V1W1~+*DsYf`+bE{y`PXuu&SgxK z?ZEk(TQA^>cY7m;9G5B#>3asnp^wIyALjCgmRU6p_jiIZpJp8XyJ!lC&Qq@KRgPCF z|Dk!2v(c=V_}{-*_MgSNT}xcY9bV=+g~HyU#{GcO1N;|HPCLiuF{tEe?z`kL=2DpR zZW()>vpA{i6>XT^iOSE-l^^FB)}Q82FZ0L69L{M}QO5^>UzY(Zp|GE_?WvrC6FxOk zX1hG^o<OHI9_h-q_6KGd5=v;VyL2V;#IOCNF#_l@;vQ(pk}0fMy0jqb3I1ud&el?? z+IR|jGh}d`3}Kxtq4dATiJ_-?x}EIDGa@wx_G__|Xs36D@0o=nBgOyJB_c5*j6#iE z>GCbh=F?JHtlb#P`r6O3{4)lnLV)iK4bAAwym#4?1YBd;H7zjOR9O7v#p6#hDoLeF zW2qy@5HSz+YOhTc;m+-J<19jS@Cry&ze9+0U?*|)=Fu9`&4%FiJpO0QzV{a3<vS(F z;c<V{rV^1#XY7@CHz4bGbI*;75I9-_ZL(*k)+pFxjbXM(+AZMnsNb}A+HT+wBef}y z1e$qkMl)#In&eqo=P}4r1bCAA_>d5Skb0#f8)x`)z_xjczs?eOr*0i6Pi`C7>eCmN zUx`jkDKj>#n=UM?`)S@(0&2%jN+*FrLMW<Az~fPd2|a<pdbMr*)0OcwC6XNp$+@Ql zC_EP42njq~uGLt?rdCAk{7w{v_qwavT}Ji0?Qw+P`ZvCkf28oRH-<=V;G?}<6!}i_ zZX-6=K<E?h^tEP!CCp-?Hc7?tLyQUgvKM<aWE}N8-cm_#jQ7k?u16s3nWt58Hl>~j z34F^N-7OdTRS^7vu|%*ime4TWKGpC0*HA?>X-Yw@ntPwrHLw6jp>=#ss)c$9=ba)Z zBk#ADVyBPeMLn9<k-hS|`9?X4ibT-`{YF#e`CdPc6i2_bcm!}-NVsdJqf1I$6|Q97 zqEBreOdV8uQZ-Gx6g1tt<Xsi3=c(Gz$SbtI-X3STQboKOR};H`VqHFQZ1OfD)k<Z} z8B8N4v2@ZBkS+c~(pX*VkqYvm(<}wV7H>X`aZEEX@<2nBvun$Hm63S=QHqDtBHY1h zXDsF=hT82-_CFLc6v8=ocmovcd)m;;537p$Og$Y6c(@n&O^scGn$_R~Gjy_(9v=R* zEpRF@FqcUIR_Vu0DA4bu3$cLwU{&8SQJAE_!YtMl-fJwV2nC@lyx*aR)(ZEw`ipx` zpDFjXVM2mMRd4UK9e?Dxy{0e%tKF#<4{)5Lp2N(5bymH*Mu=2NGrcvGCjnhy<W*L~ zvIE?fSf?3XpLTDu(<8ENgSQ6D<4Gk=eBwH6awnxeQK79I?aF!xHPPrBRo-yqc(%SY zW$VIIc?CD}rX9l#**mEewsHgZdTmyg@4?V-A058l*Tcg$wq1E0n85~i{QiLfT+I1q znG<VEAXI(v+SMW^57={FeJR-F1L(%Me6wETqB?%Dco9B@<5En*zLjhAad#+ZrL#jf zqGNaC3^@Sr<#~5286yNF?A1n|;eF{%(zl#)Ajo0u92uUX#*oCSAC#KY^6NsP6yTxV z1w7Pux%;`0quYD81nGK1k#A1gZO_?KjlsIj8!p*W;pVu^+pvs`C+5ToNHOQ`bZ@I* z+d_~t_NVP<Ne0B(UN-3}HvtsNUAy0;K9lQm6Um$v`guaF717~oQl*Wkp?X=7Dp3W} zk&z?RxbJYUA>8?w6eXL6J^$*|UW_C<zpo7$NsZ?8Bc-rcZv5eWNJwxdjuak7H`BFt z<FJ~LC`@I8^R{X%j-23R3p{<1h9ze+)^8;8_MKX%q$7&F@JChT*~ibqRNCvcWsCj| z*t=YO9k^o5!M)l%sJc+0I%PMm8M0tWu<0m)p_22h(bsqS<9+Tsbsj4aB$gsFXjwUX zb$<zXq8f6TdR@zb=O<~&*%X#fG$}DH`8=T50)w)SRZ<e3DW-lenAt$N8BmJA3bf)z zV=<#=73i2bSi}V{IO)?daT5pJHs<{j`H4D{uY4XQxh~{(MZWe%Z31BG-2+bjf&c*L ztWL~yXq3LTU!OW3z$I9+>vG$_@+v{2jWzdme7dze?w%|X5blL4XWj^fP+~Rxc{5!O z=JcoeH5L{MRCmQhSo(l)<dJ)Sk4yCV_8jH;OCm5wRxYeWHPR0@S@W5`4g>J9w)6hj zxgiJkiEpbG0xgYZjMd8Pg<$B_TXdn#8_IZj%TXMkCCI(NTIed68lX9G7oukishpEK zS(c3oNOzD$%)|44ipW$Q@mB&sF=Sq>BqCXaE*~($EdsbqJuU4{O+d`6f{6CK;L0z@ zLOi_g!ilxrwfd*|f~%+Mn`N%1RN4rq#=uN~GxLUh_!8a_FHR`DAw+`sg-w~AQ@~WX z-+0lBhv1=JbTI@ObMDN^FoDZ+L5}Bz`JYUYUO?GehD)IWVpIRTGZ#Vbm&KWzZOZN# ztRrhfvi$);<wmnO2`wZlb@GsJr?e2{A18CA#E05hiArp2@<QyaEgRh&=xPez%#yC0 z<|EI@`E3u;aOWOW1CZxgQHHP)#}O?80L|VVuXMI>R5EZ@9_d2<Q$I#*!(0S%Gm=n< zP-kGt=}$ZtgUtbR2D1BW=buU}z2!i5C?e`FkzCR7@W({@23AknNF|$;5<2Lif=(iI zQjrTVk>*IjC)ZgJw>AWQPj1Wrs6&reCoz~_6M^h|m>41J5Tjpg*I)^Vd^LjxFnH*# z=-R@WI5XB!xwYhrf_LwUwF#FWYh^xbw7uIv)R&CYt5iXj7%DY$wy=VfQRVQ`RDGW; z4fIQ*doFbF3G~?BsCieMT-)|%QU$r`AB1X7az{Dpp{R@fq3&$4D6;j1=;<0}UL9f6 zjQlIae#K8P^SF%<>U<%-wsr;T=_1aEuBsvwUj&A2PkVPX5Bp8gXv~Ay36x}T7tq$e z3nk#NXy|qM^$!#9N@*BRu$B+M`JXa?fDYG-=aFp)Zs*Tdhb@p3tw;QK%4_1TLmg=R z?cFe8`)M}!F!91DtD}de2^dr|O8$`hl)`S03#O|c++a9mt1q63pNGmYr%yyM0Bu8- zw}G^w#BR@3g4SaGyvgmij@fn_Vr%mVT~oCepMg)~OUiHs5ZdWjBgTS{j}46Gq18F4 z(e{i#*?6(`=!kZ2b5SkK3`69qX-N6#l<6g>QIlsAQO?KUa)ALkhQ||$IghLM_sEA( zL-`Qo>{*vF)!G;Y2VbGY9!fZa=e0muF<p#V-#?o(yLMm7;N`rCOE(-9vL!rPwWZ>G zMAi%)oR}xW%_%wOx~HF=&N=%ox#pkHFPId`cW3(~VOB-9`mi^iW45|^?cr1nXxh`d zU@i6ZiKNl-i2Y1s_WHAB>*eFbspydBYV?wphUP%kHs2$eT$b%d9wDc-2%WuXXW`l6 zr2c#p+%uk1qK*v1uYwiP{Om6Kj-dC_`R0_}{G78)sqb{hUt{Q3uxx?}h3h*+?aGQE zEutRXt!h8sz!d8;Z2BY2AEs<q7dRr%Fu2z-y=Zd~&!5UzDVzD=zelG&A8``*H;nF2 z(EIReD0mJUG0s>+Gnv2Z!zlk84(KFM0bD!QSZc8@$7G`clU<ZPr<xO}-_k%^jCMr) zCIdlU=^PAKV8jZprRee&PC$RB7^c{z{$k-h@7<m`1yS$l5!r6HIuXsH-)4*G5Y+5d zns3wb0A0|ph`9=+^~)32Un$Dh0>$aFuRbY5Cn^L3%QI=ZZ_zwyi*WXOt-RNL60-mV zm>(;=PCl{eBgr<>pZTQy>L`^){|vHOrvgn3=j?UTpGAiz0@7cY4t7PjX2MqD>r2mt zteyAjdi(i&R{#L#*&B?RW+)^puWj)9S)@5i$bP}qwiD|+nC;xRC&D1(8yJuP&*cz! zBc$0k9{bJ1OB2v@4Ofo!03#^*>RdVV74lX;oF}qvUFdXQNc)U3&j;VeP^<1w%!jju zgEyiTwjL!4_z@<d98z1~P;8GG#B!aYlFte51eKJD@6-cx6y3D$aPJ1|bTs`WU-kLu znM+_<{GL@ypz<9cMZg;1u1$cM2=kTRz;)Y!nzgl52N0VK9XXQlECAdU6j_K2EH&~L ziFeeGKHZiRG+gr>oZ69^pz4@lWA<WWeKYH*B6r4T{TO*bb)n_Rd)<WtFPzL)2%mEv zesZ!#Wj*q>JHxXE$zTz&vHAAt-Ls!B2fe~vom7#iPoR$@rs!EA%801MTOEHjLv3nT zYrRHeCAic;?^wOSmuF$n_;HeZc+?&yG5jQ`xZ1lt{aIXa`ss&Iz2lm2ZEBJ7{pN7) z<nz`B%=?{x--zgHfiYChqQ+zg{T>!8yXR1ImJ57ZQ3lM)x++p6*Pqym*FK?Tu7fu2 zMJyV*mEDySih!_uhV%7bVY!pu>m^&i@h0kAr$1sU1bL1ZN0^kc2L~C1dyhB#6wdq+ z@7NNppFt68g*AnmriC?rQ_8LP(DsARNZbg5HQFm}b}kXN{60e5jxTIeND9fseICn< zV7|66P23-<eD9QE_shngAHxf<!&y0*0{j+l_ZT@|yd-3^Ah^0m>C4P(TS2MD^>vIH zsHKdL$kbTqA|BOL3qoy3gslDrkx$@tS^)LF*$6NhLxrraQus64B3T0!XI~RhIkL+o zo1Dqv_F6O+q1tBOI$cp8lOp-dQ66$LL|eLu;?Y2pXmBBJdib>GtjgzPCa^T+A1^7* z24<~}Pa`p^3TCVV+GUwkenE}!>%l^-B0AhPK__UR7hke?a<#zg>przPVWs8lB@LCm zly7oj*9K=#-%~63zCSBTbUzDB9A3ucF6ROlbsgV}sRAKmf5O^YLa-L3MQLx-GVe@} zht)IoOpuOZ;GZr+TX#2TYp0qbSZAjOM!CV85jkGZe@p){(XKa6+qVntitcD9()CB^ z!YFmLQ<4LLH<ep|s@|6H;@6p-WHvSmZ$n)Lc9|MvGy_~>p*sBp@F3Y`W|nY5C;o62 zh_tZ@a@xGBah!Pa8iPHate$ADf4RD!(d%}ndr@jphCz}(;cTKE6B_OWd*6}gHRD1g zyLKJVA<uT+t`NapVSKwY>Yt%8t7ehpR`WtX>(690)f^9geyzA2<;8(d(>xOpFHeV( zIkv!DZm$arVqNZ*eNhDn>~-I;wFm9{&3Vr2Y|DZsc})0Q3QQ->$eQ7M9S<8iiPK1q z<erbm-(%kHljV;@P)=Xh`-tA5G83@gKEjXR{pDR^Vfi2Ll2gt9)4Rm_FYi(#(Ff)) z@6sZS*6{l~*oE@4MzYE&^q>Q#K6=<gs}_uY0A_LSccoJNrns1|JBWi0LEmC2`uDe~ znkZGHED}-0j_ba^-oJY3yvs%RXJyat*LpSF-#)gko%?Ju2mAqt>`v0pd7riwho=Z; z<Fbh54FJw&Z-0Y-cbOCH7=N=}KWG7tC-6)5Fx{k23VxwkI_}_u#5nzUrP@Fh%q|%` zm(y!TQX<SUsl<wcAXEUtI{J@D;a{RiArS!eQDAd!)W=)u+{EJv+&XM}QDrlVv@nW1 zCNpRnu{Hq53`)HiR~?FmQY6hJx>A(nho7eaVLs17DYP)hLCG&np20c=Q#oIf6=HX) zV&=cgPhsgx#1qY6X@q*IlG&BQOXSJZ{D$OVXvCZ9hU-DN%6Zfv{KOoC1Sz~cu~`tR zjL_~!#PWQtCF6gQX_x#<s7GHSstDlv$LRNe8I`L0Z=)@0{}P7(OGrEY-!-+)`55Ce zD~aOe2w{A<$|f0Ra$rB4VP>!Y<R<>f?OOsMXhg*7fVfJ&LjaU=C4U+K4o%+e?0>}O z|A-ygK%WyQ$0q+N^<w?V<&rKf{sTItL-@-CDnf~%F(|~DBJlGme1xeUFTwU9wuj+K z<RxoFkv=a*reA-N-br9JR$n|*Rzx*rUlz7pmn&)?8dFxtC3pm-9xvhcC3bYTD}J93 zlb*CGHoS!u{&q(suD^Ru{-ivJhZ~i^7g0i69_uGIH#$bX2=@CjNa#Rt2Ie&SPMiSX zQHMt(ST66F@I&edfIxrwzGdHe{|ShA;qEgJ^}^qI4)ww}k=#zWaSr~n>az<*ho#5$ zX&6Bb$>;X7nw-xqq*}DkEv(x32IhSSBY2s2=N*_8QRm%fD}l~Ca4X%;J4h?<&Nr|H z0X^P{`Tn;r3)x`LSbE-HPQ1UttP6zZ3G9b{T+~9gK6Br~`ltR+8^6KuDZgQ?2X8~! z@qUV8?7W1W_Ptz(oTk>m&-U5IF!}7|Ns*OIb%5P3FoHUHNrzJ+$W~V?27*`4!POu_ zp%K9V1vy7SC+5N`iSVcf3;`fYax*D~jX|GhC!WFpI3+oWfBk-dfTT)6GysHC&;NjY z{{bB{{-ex*p3AJ{Yhm_9^l#fsg8!(g^ojL?z!USSL8!|48cSkYN)eI(t~5UydA5=Q z?U8>8$^Rup*vd0d@*DbSJE0}=^w!_75_23_A(THJKdF|4@UV~OAfX5#FA=Y=8}9ip z24n^radHs8j8Gyd&6x`eBOg^MsxU`VDbm7Rr0K&=^&cZk82=*E9{g8LzWt98+}Wuh z#}7}J(5@M5iRdgSiQVsC<PL}y;oNyPAS@a&kqK;86d5rVc?^vZ%zQRb6nmWiQeG4Y z?<i1m5}6fJMpSW9S}71Suh$HrBtLTN!*xw|6y%<(zzQBIg!a+rpeg4DnlWkkADQKI zefXn7nsaqP=m<iF6QZmbqaXbXiN?n|I_@Q9{12a*=qi9wDJ&?5=`YWZNghodGQGGZ zfZ){PU-kLFY}$tZQ_KHY`<rOq;lrDS;v~`rpvLv<%L)AmuMk=?AzX)DF630l^uG~t zYtA6t3Ky6`<N8zN6@y^uMZ+g?mu~+z2qdC~RLkW5A6q)-|8mW0e_z)B^LC8>FO=5! ze7S*~?!s{Pyu3EScS4!+sxx0QL)`Nn0p6_8+MyM~9C^zVMmNN1L0Lkz6L<#%Hz-f| zUg1EYjyxwbmL=Jiz|gdO-WkMGf9`j=FL_%N+Hyj>;(65NhF+He9(vf-J4=<;TzXH# z`**ibxK&l0Eu+tqBpWJkoN4D?P*%XTlbV(TzKe6D3FbH%A0pn~X*w1t@T@=cEFQX_ z{wS0-Cx3J(AWy=Z+;ia#EQYg&hZ_atkxI=;DlX_30o%7RJ4DDosiAFm+TXy1wEA+O zXPJ=U1-So&HX?(EyVmMh9fSM!b~n2}VsCh}gy_!l;g$7|M$SPh`}Pi)B<iUWretN` z{FGOQsES}1T~n=3d7y9Zo?8Qv@XNft);&I2Uk$dG6S%}Hd@sMS9*p`VhHmI>SM9$| zJw#<EA-dAW8Kyml&Ep=0*GsLw+19I1-Mffd%^tcUg#%~UFFLONgQ+p-q|S{c_5>T? zb%pS4Fez|oe8g+bNX)SZgtnU<>S>=&M|Mmx>0o{HRlktyTk>5)_-gH!5xi#gax8HF z**dvHF=mzxj4_V!4n6pWl6;ifCvIP2Jcbw7WF+BrB=uzqdWcPryjRg^O5z*M7IP&< zZfIK*>cwS^PUnL^0gFzxopTM9z7=ys0jkDpx{lF)7pZnLxig5!CDC$cZKQ|+7)1Hq zW$A*yy$07aAcT<__+Ce|*bu<)TX=Mzcrd{WvPbFb7dy9iSDxms`P5iGpH)P@LGL;D z($(Vg=pm&Nv093JzU;s1_IyVn6m<+OS&z3}9hmC;DSKGpc_m?AdT&`9Z1k*~&3W#H zMAf%fv)cIbBaEq0Yr%LF>uUW(QrCau`<08#!xVibxzV*R>|qN3ryZ!?QIyy9Kr~$m z8Q1w!0DLZOsmCwQZCJ>9d1KR#AAfk8Nl)Gy-Sp}mk76AO@jBi2X!mAt*)GDf_SSGa zFu~RN849F5yu4)ixQynxkP}X#!>;%sJhdN#mWiM~+uF3B5?j*#AHLo?Dvo6h_YMxh z-7O@zli)BRSb`H=2Pe3@LvVMu;O_43ObG4-8{7sP^dozpd(PSSe(U_xtGfEB=c%4G zuzJngRljQfg^D&U9@6S&y2t6&kFSri93Ghv*2J_b3d|o0JH&RHzZb11P0)nnn1;jy z-^a1!_du^_xkjR%l6DX((ZU!~n|=WMxWiV{4DZg<n2gEJ4!}Pma7J%ueoyHfpweFx zJXh*#)Zkfk|IDAqajA7^Js3DEDH=9WWE(K9Ti;%;H&sfz8^l^y7iXJK9Kt^Kk*-2p z#YZ1M?y4N>-0+7A_ax?INB5X(=-F1rqMQwSpEjBA>0=RPwY5^w!NWr?<d%RAaKFom zexJ|ZJ=y*~PEhbegFhV-=RHY(^+2bFY#I*x2tF9w%}DZhiW;f!JN(o$ZVav(5YmNZ zqRO=f?RAMQN4-@|IOO`I^e8K<YCrs%?xr?qWx!~x2f-}>)EN70(2FR1-x_NWQ%0Qa z5|tAnIgsQGPp5d4{#A7*<a1cT2&+n|iBh?;%kh@3DZ}88#uM)ro{>UkaE1jo!M1}9 zlC&%)$5f2$t~yWGyQhH^lN+xKB=z0Zaqfpq<XxS6MmoAIgcH(qT$}WKJwvzMy|MYM zZooXSsaz5Mlk*%lh+5!sC&P#KNFIPiOVr2_Sr}kjWA^B4K(_&u1&yA(L7-szuIADV zP=8l0nDP6DlzC$={#_LvEMzR<<jJa1EPboU%%=(gs{!=_g39Q)UDQ*#{fnJ@v9{Oa zhfP0vZ6?5B=lj!0f=JB#BugVkLQU0k*=<-2-l3mLF%Tjc#?qzl00e)k0Y#yC@*fEf zXde8wYu;p9wBrL$l^<c%n$7sp4^8+Cmo|53xQJrl%nsUF#m*Dr_{78UNq-;nb=p4o zK34WtPdDz|{63_jTZI&%Iyu=lazu7a08-}b^X>CsTJL+TjOQB<0ZAW9M{GI($PuB3 zQ>7O+_SJV`gI{vs*AH#;HkhPB7?xrFG%lnwzO!*+1%;!y%yNOJUU#l|)dx2O^?WV3 z{G*ncf?WZ{wg<KR+;rK@Y`g`FnQd8?=i253+gqPGo*paI)>>q0mU=foMFW4x5SWw| zq^9CAExSI){4^iEg^WwhM!+TX00v@a&=3~g4t=W}9}Q|}YkHxRSGE`N>lczxja{)t zlYx{;D)OmD*MlOBfG>Ig3w{=cW4PV^YL?NPMm_v&&p|^^^o6&#UXzOgNVjU{;g;JX zR11*so69?jjpUZ^Qb~>oPai;lR$oM{{Jm1%u?{~#d1KN6+pF{=O^0VkY;_2$MznP; zB7~A{_VxX1J>k?dTTt)&O;AYw&Pnc2Xl)?yq@5oSU#?!O$g%Wg-G^MVEAwbUj>9~g zJKNPUiBogny59LotmY0Oy=bMZQ2iM#&%y`);B*<vqbW(CIPOc=ha=EFGWocl*xz5V zZ38?NO;;_7O=H1?O#3Y00!u4F2mCi==PuxG4j<YXn8VGXI8#Iao=rWGU-Oi&tH1V7 zZYTp@r6bL$lmy57W()mJbX#J<(q?8K<4p4J&M)bjAbf{hX_P6PMvVLrC_Wl~kK0{% zB-z(maINPAW;Uz$K~-PC(C}e2qhJ?cW?r`HMOn?2_mH66ry6w|ImKuad<%F!V?7`K zLZxMCB&|855}&E#JtUwKk)Kkjo0mNQYGCG)vh6xQ%wRyQsO6ngF4{@+rHd6hoj?~Q z<HE>Htko=n*uYoTk_OV<hYlY!4MrayAKZ9Z<(0092d&lQ+j#2NX$ft3>H}uvCdZ9> zxfCQV+O0_CSy{E(A|GIF#B`Mg3#jkzrS<Nl8ByU+>(7%rcKv_$*>6Aa#9x7cxaM0P zUM<ps(ZY8(Ft--1<0-hru!0hhx-X}%%B|-C<`i)lyG<lKS?otB<(-d5H*U(ftzTHe zp+e}PaV7BtCOWC2su}O&J5ODu8&g<U+b=9x2bzaa7@s=hIJNOooQ(!G!!#cI9dAFd zjefgatQN|xqIX^o6j_=%#e;|_o(=reF_Nsi_()BcV0i*H=m$f{ji4iQ$+|5g{qxbW zUi9{`Q&}lBZ~Ho4tJ6Kid$Qe%CdXt`aM@HL9o9srtZq~K^7t(H7Nes2`j}0IRX}Jy zzWi9HFGKkV!M?s}%>M8i{w54prN_G4hofl!XO46A6b25ny%3UW`T$GE0mv8H7i8T> zmf_3LabsjXi};Cfx4}{@mQ;~!2J*J;7EObn@4R4<o~Cx-tCENQ6?vH5Ltk~Y7la{? zy=w|?Xgv?DQF%15AS=P|jn?7(Q=$QBt9FL-E|;<6cyR1_hO9HudL~DDSE{9}-J*j{ zw&jJ!;eCD2wP^^YN1th^#CKeB@a>Kvj#oEtUgdpaq_Cb@wp0DXE^@W-m3;MwvkzyL z%xn!Pw>h9Hb)KT^Hk3~;Wn9cfHB{}Y!sA|%49N2>XQxIWM3LzbxF3VW-2k8NwSgP- zv*xCJi9AaG*+A(^p0#6GNXZq{8Ia+(e|9lb7;O`hl<g)FIr*i(P4-bEv1%|L*tWjd z?9`ZMx!=Sq<95bBK<{N=xhcsuTC7wv=$V^ga0*JB;Xe`{z`*3FT3rw3Y<GMkz++A| zaN@#O?f%R+s%N1g8hyAt5(Ss#2S$<_mhiU!h5`-rJaEXdP_-aa6sVM+S(c3yGc|OZ z@+TbXY5u&wFKqluz49t#{ex6?)3;Po8ZB?Qs>|(+$Du$fs(hB%9X=|@Ri}?DP%`-G zKKBS-fOa-dM3vXq2EyAi$%e9(S-p7TuB*!*vwFs{+KQX!7~|i$71hLmJ!i%7Dydbd zQ<r?Fm4+aEF>05wz2#~7SimHFhs)7ZqGhb5d)j-p1cPrD2i%z}Vl``Q-&Xb>qYQS^ zBFv+1a?~ZQSj#QvZ<ci$@CiYW)B@bVd#49$s>MAauDg91a_G-n`FF9?QZuucTp8|L z!c#4dCC_&mq1`ntY6icsFp@Op>}*!n{jbi^2Hwo!Y>+QJ*De_%A_o<7mgETEfu9rM zj0@RB7Hpu-ZS5jddwXl{tk1&8586vCp|jnm`)TSE%gEgEmg^K=IOy7mvMkey=!J)+ z<K;i-{PYPSTQ5(wVurCgGqS;EV!FLl=Y|aN<%vh{$=;<&H0EyuvZ`kjIrn}6SkfbM z)iuj_;j|vTB(IJyuS)CiAcX_RE9V{z*9TF#O?{;dd50a7o$t%21vY)Oo^TpsBU!er z;dy{>QDKYNU1Rb4qwPqbB_CkKz>N)=*>j#STUEjoAdeH6F_#*-%p1?{G=o(gZhjP3 z0pi8bsr8RkaTTx4$zFXr>CP1|k`J)e@5i|ir$Ek(7Jq!mW2^F9UL!rcd*w6Q60j^q z>p!vGeYsuExUn`o)k#E%R5)Y<EWb~F6*T8FbAwF!W6DnLab(`F->O8U+#WZkp@9<P z-o*NiR%e&(L(8dvG5dvz%T^}_-r*oT+_GN>{T8nAa$5L1yzr_OLprT5H73CNw#uoY z#Z>WYC0K?$Zv9@T36t6_Ba@{?OO!<;TPI`fd3DZby(w!^8tik2lNCR^9iEU$C&Ozg z?{L4mw+>=emOdLx%}h5Z1in|0C5mf|JHbR|*Lg=-0D$By(J7~s7`|fYF8_H7EMmy_ zh_s{&+tU@33zu{X+f(mmM#>oO3#u*=++!!T9Pi~XGOmYnL9y_Z7UNAZ0zvJKCbje) zY`xzzEM$H=V`Ym7T@v};mN6nd5>9hyeZ=HV=k=u#@v<%Ui8`D!#MaX!$G$+FGI43D z%UUVpH)ma}gf|}3iRVQPLqWoM<WxFR5zgJi^<1Tg$I#ax7j81>00xyn*kf;M^lrQ? z^K_ng(ORsLcf^4sC4*wy$k6>nGSl7l#Nobj0~YTfCeAvTsrzDr4mi=SUM<G~=x;W7 z-Ts)p7&UR{?BQHN#CZj2E)Qu%C**nMbF11_OA7IQZa*ausO#%qhEPki0pQBxmCO9^ zc>Rp`^V^8&+F(t4B8g%4Z9=2C*Zmxl>amw>5Ti>VBHO9voub1OtWZd)NY?YQyipmq z>*wv1t^^S|=6SaVny`+w0H)T4R0BqL%Jwe7XwI~?r8r(Y;sHkP-KhH?ltt$rF3jsT zpPlRZ)IWGc(UJa2C0<J$*{)kL<av!$^`<^*zBYxACS)>ii&(ad5{|ApI5c^eN?nz8 zE@7qY)!}^jwkfJJ92;Hw?D3csR!VxIZa$}oUgDE?<2^anHU=xQ`V#_tMlENu1x&ZJ z^jsm?XPCp}^pc7!-R@)ir$y%ImTssp-`sv4COhrjf;Io4)Hk*KreWc?xIE8`Lb@(q zG|)0;d%d)otOu`Fk8W<5?4yU45B@cdGz0c7JeXuIp_`5-!9YywoP{Ag`AiQyrK4~| z9_0gmhTX{0C43%<*v!;%l+<=1qX3WRkdRr!(EB=>$7z=aT=aC0q8lsXFOK{w4=Aaz zh-9WiftYyw8`nO+4{>MnL87TeVzpCxTn7~<7rlZTEmdp3`Dbx%_XnAPH}0sPs3?o7 ze%-9Q_(k{qX!7lLmelq%Mp)o|_ke)`fjgwm#GgL8_8lnLGN!v2^k0n0=w8Fb>lU;f zspGYWKNs*h9B#XD?LP+P!k{qnK|A>Glz`d9`K-IB_f@IyN(8LgANETXMqT6FlDknr z47(?-rxlb243kG`Q>d%SUoDTjld#g+!^o2f=&fyYBR-#&g#A_;P9h%(1|G{&m&vmp z40>G!DPXpAiL{;YP2FC2s;sseiw*r2*Yen5=xNTlaTxijy+N`tuhMFTkp8n>@y#UX zhWCb7dZ!`+Y*pGsX)46mrN3=ksD>CGz{vKpn{ojNf{4k(Ir{1;&l*8EK+5l?c_^oJ zOk7V$-F<Y*ie4Sz==uqJ2XR*)&P2{p3cjy%;3_(plKOx}S7nYdDrTEX{9tv>;NoBy zt4{^kl0OO7JkAimO3;67KJP5JADMKBn+96$DwbPEBWj;t6h^42Ia&+01a7)|+gI)L zq#KxOT9sW^<wQty@(MiznHERUZg-)rQRO#6JSBSK=;y7E^_R<BFjG`zl#@%#k+(O- z-K#~7$9iNwj-yxY$$C$Z#1>zmY}J)=^DHZT@UF20JcS9@aKbg5T#eD*9R)4sOEWY8 zku&$+x}IaWkG3V=v2(UUXI<sC4?GXZXBoYp&!yz6%~w`A4(kzHzg=VzG=%!r*@Yf# zZ9B84@ORlS3<JJ%kJfA@Lb3eKJ!^M%8SyoE3K@(Zp)z0R5096uTFw)C)|z$6Rgmbn zrYbE5V&(CQ1U<_QJP-!l()iYU%%}%ivOQ%|xWf%*<#eGTHQAgizZw?Gdc!OMUpkKL z&V)780Ova6W8`9pHr7UG-smsy2feQHw5OP=Y`gTgx<QU$QR5tnsz)62qqG1By1$xh zEFtV@ef$;Ow=T@F=J9c(=g7r*oEGc7QTzPrdpK&y%LDdp78NgNUchJ<>ceO|XehGq zmHfe~G~Y70<)Bf8@VS&vhrIRaCO`*vlDH0l&ZO0?x5va<bp|^nmL)@;OZu@`jdi~8 zq?Fd@s5ma30B*Qzws9o9iRBmA2kDMigE=>%6mZ<GVk%*WcUVfZTFMabJy<r+ORGq` z%JoZ|kXLC?ZZdSb_7eJID8>%#L{_VF+<q1PCbpaj4OPCN7P)lT`t#$%`%eN5i62~D z@Xl}U%Xw<VSA8?te~(&=vz(<*d#rUHV8SAS9{HNC4yA{tt%ZmRPn%pkY}73004}VR zZU-wC?~&OClyAKt@IAk0{Jc&nW<Zki!=N=!c>lyQ$_2@rX>c?s&fKKX|6^-kL9z`6 z@>~KDW`rD~$e9)lekF(!eC1jD(D)<_exI-kessqQd6eSpI+*7eBwNgCHmMf28JV;% zHQoo=$S3Taf9x(t0fjtNWP^q6uw~C-*BrwAfLpaaCP_WBUa@!3$NgC?oWVA~z@4&} z#nyMP#ts8Zw|lLA95-gWMY~vTmi@jx0fUy~(p37ASEQs~_Ms;~<Pa_o<6Jo}&a0eF z=4?!YkdcVe$FHb$$t`pgj$=>=vhTRDjUMeMrZxrfe>H)OgXKVxKV(?byA-phz&x2% zM!5oDr3hEfYh$P4r>AFg!Xl0!=Iz-o4O?Z&8QN!I7j_wd4<A?2nUqA0w+_eV^huYl z9!CmLIrjpotifuH6&%(rVb5l(t3!Skx9Wv%oeBdKChl~e0tL?(QmnRl(4LuQgs@Z} zA$q1XUyYDGy3dUVK8c<l`kvZZw~{;XoO!9$?IJkB`m|_&8zx75C>b)reDWf{Ws2B} zPnU;XKkbL^-QsLSW6pYZULf2$xT&Vk9aNn*B@yaUBX!E_@u``Xfx>?@Ud9Q*djBd! z$p*N;@=-|MPzSt}_KZZ<cIi<TR)ks#m<)N;+16-q+iT%YE{jr2H-LI~9dikM+{9>y z&>!m*OrZq0_x^~AaOe8NSLVk$FdBL9Jekxh@>!oojFZQv848CqS{~=2318NM0T-;t zMKY$7S($<~8Jv7+j;_6mHdvRm@><0}|D2+Dn#5Okg5cxx*^;{_o_BBT^aBXsQBSf@ zy_zY$pI-E8E&_Yxa=FJam3`@Tx7WWmppq4C=QBhp2Z-9*4SrZ9!}40J2Y>NoSSSic zlM-^oZ&lR|-+0PE=xwgqJf*%7;f=gf(s&~Je!)Z8I5-%<{$<Uj+fEH@ZEM^CI0^rI zNS_vg4*jG0b-C-@6$ut19n`C<^w~>N3-Z2#)X!$rci@J!XVBT;di)pGqk~)OakK&M z2HuQzv*9)}+M##NuIf-ICE}G<rVoG`*yj06>?oIwxg&(SQzwqsm*^zl>i0u^72vKc z>8L=a>v_s|g3Ayu+16W?BB7X(vT~5qX@!^Gb$ilK8B9O8j%(;*WE8`4E@4~%Vj2M< z1h{u{*uLpRk>~SrQNi}9v`>`d)95a3bRzhA*lhqwW$8L8hb>DVYl?2Q$FqDo(8egA z2lz~`tIXCLA3{nB>M(?70MS;bb<eW1em^HS2gKywAV&tzPcUn}@-H<??=7ynyIQ~a zGUU7u7AKDS9ZSGM^?4;}S*IH5dopgRzmbL>&;9Lvxu7T(x_eczg&Z7GyofmUX_T)h zAXvoNgEe5!b*IbbOM1`Ru|}nZEv%!Hhs``oES!H=E&|5T<yNQ8%#MhEr_Q~Gc3F)r z(r?Dfr7Lk4uMQj6g?oK@tU{i|BV|i?edmJpK92Q|&Dt$@A{)+F8Le|eFZd*HoxMzE z&P*8YN9!^8Sk8?}1$mH=8age+efRjb8JXxEvC9#xD()2>N!(`EF_nYI^|ci8y%hKD z4U>~_Za$HW_d0ay3J+rZqzfGhO|Y{#K_FdJ8O=WmKUX}ZA9YN5OukAcXOO#(+Y?zc z!xM1K;D4Of8D-xVkLlSG+CG)slr^omjx&DP>KcqU{~^eBQJL+<ab7gGb~ijn*?n;W zxYzW^^Clnqz0Nb;q*pLL=Kb;(<(YdYWsPf;cgLYwPmAjzh>9LS?Apj72JBqBc!YvQ zh|F8xZ!Wp(vAa7TH1VyC<*9L8#^MJIu-~5guu434JUmMrL!Q@9UUoz}1>z^ZRQ*f; zRQ)-({!{f&EdGD0{+2ImO8;5)xA-@7{AEq)ALw|+;heQ86JzfR#3{Wq@zW&I7aWO# zx8>i(Q|w1{DqH%oKBd@5#g12~0y8jm-Zs7CY>^EVD|ggjc)Irj-TSx;UJF)%G%Ual zd|-%1AQ9^+F+oU=tsc89uy7(nzo#|>54SRAI=ax1IcU*GGPTgF?D;s|hvRthXTi2= zkQo<ccYjUi^RnTFI_6Je$5$Hl91_(8Gk))K5XWCQyYTE72?5kOY<n>L0T@)_&IQ=z z2u^=4;db*XU<CP6<Z$i1s{Mxlzs3Z@%=rHW^=6=lL0Q^CR6zZU240W@|Mpul@y9>d za4Hc}t5*#<e}TYXOTD1QOJI}(UMu|74E@J&dbnbQ**|FUuL`g@_4fZb3rEi(P!5n% zfRPeG`TUcW(^pdgo$&=_j_{63r0wS`bCJJM<_W&=R{8%hMfk^@Xwd%KCW!PSOYvWz zaethcoJ8V(pTQq{3Khc|=cG7iQ7HRyD#!?^VV1s-)eF(5Un=^DVP@bx5qx^Mf%;r` z1%%O$9LJU~@_~A4PAvQCtjV6gB&Pd+OGI8IVP<ewIj<A^43=K~%eU}fzM?O_92Lrc zyJtp;hzX!yydPGgmJd9_T%^3f`-wUFNF^U6NL<Ti1RRd1lJ`G?TSU4L8^)#f2*B+= zxAhd<wu>1Ok$6FlyBBtM8#Zx1HR-e2qVbZDA-MZfY<&Jn?g*#T(<MS^3r`Od%=NT< zreMp?i%f=mjpPv^)MKrurT}j*f~q-+d|)>6OgM@yh}m;P?J5Lc6}<tbzJ(UEJ@O;3 z5&GYRZg|n~V#0$1Hw3XWc>LWzDzG6Rponx(7fZrlh2>;n&j|QCfBeacyo4gsM*U3^ z{x~cr6T4o(-`h6_NVP}zP4MotAM26^{_(YJw<F&r&o;W-n;&Djz&Dh`!YQo5-VwAS zLEce%WK=|i$b%^Ub^(Rm5Pd;vBH%a1A3(xE$}~jcLAo>yH~zwGy2KaSyS#uX+UQ68 z;-X9jMB|*-Gt-D6FCcE%tNItpH)82WA*&dogui6cf6LGb0yq^A4$MURU-;mD?n{gR zk`@0;CiyQJ?%y(S*xzoDf61YL%PS&Yz01K<e&LIwn~T6%@xn-bWGH^2f0NAkBQ7Ds zCcJ!m|Io&Z5zy;VkriO2L=axywag)S|5F0z1#9mAhXpRea{TE<_;>SLj*gfwj>2ok z7oIp`Lyn&_$A3F&^8Nel#R2<2!N)lNh>}M6M-=XVqF($Z{^9>0$3HX1fB9Av<orRW zE1;5m!)N{p<A^Bq5y<`mYPb6X4L9*6sfM5NYsiUpeuwJ#DpTbD?-F+q0>AHM4$>ZK zkgq*E#vh6|ngU#sFMl<``(J~#ACZ0%U%v9{!BW5&^!tMohcWNQQ9zpWmsnEyUx6R~ z3dH<Lhz7HxN0##HzvhDee_f&tAkIN#hH=tk`IfJ9_=-P(LJyf9X{-B%Y(D<#Lk>nc zLS((&|H>l&Uxpx9@{gu36!O2P@vkfkGoI>iJTFN(Y(%i5J-mPT>s%kv)nO01HGh6? z3;iD-iM5Fz#>;<Km^**!9I^<wKTvnL8Q*t*Pg6@h2W}6|-M90<??4Zw7|ue3=2+D| zH@xu|s!shZ#%N}@_XgHad@?xqfG0hw7gW3d)CQw1O*7mICaoWX9)T-dI|6}!bT@c| zv>NdNk=u{F{+xh{upTb6oBDYqA8iaChBRz2M!aM9fs30q({kS>S2i~LQ{zy+NnV9R z%wo>~zDs@TX9X{}tI@XHkQlF;2?CEdv4#BH$CNg2-2vt*VjiS89T)YiMsngm76Y80 zzi5P6ik8jJiO^sr-w`nDm5W3~#m<?!Prg|OQc$+BO@}`GTHF_6{v@MPfx{53h@tE- z`NhGiAI;KR9lLISUS>Qsjit;(ErQR3mCwDitoPws=TTGXO1=3YDB^-~px2A6D-|1! z*^~Hwtk#F0K-i^&ciw}IASvI9u%c|BceDuLg6BQ-X?37&*~h!5S0GSDx=dz=NAtXs z6}JWpwGF9d$?%Qo@@nx^p!!Dt`Yeqr$?lHLVVekmNXuu%aq(&dX1v$}+{ksOG#hMs zL}XAz1FW+6=2m+{ym%=j?K$`hE)`1Yvc5C^X-?quM*IB@*(MwR=8Tm#k68WJj!Hr_ z$zDa8lOj*j!0*qKZAMd5a424QlZ$Fa)c!*h=SC6an@Jr~Q0_y)S~KtdNyaKsnNJ*z zj=$IQ^#`PO?G6=c=K|8fWWS}oKHs+1H?49Ufc(~Mx+J^@RJTLulyq-;xZx<}j82OO zU`<!gv}$_P&!)b-RJL511#1*|H9$kXcZHVE%Baki2j=*3UTCOgPLDXoMb+#R#~#T^ zpPAdqES(3-Q3*P4rJHZ1ISH~>_Zwy}=m|_Jy9KMXnVB(Com@vKnA>9r1&v`z9gv=^ zxQ<@=X-sop%eg#8v2fR|(REtodW%b3kvAKXs5lI3f(h5+J-yl`Nwo-Ny*rO5OHN6? zG%~xdO1L?RBvpwijho+5g{P#Pl;o43U<9>fwWNl+LTH~C6-*9*)bmf=l*_p1W36p! zErryOIZaBT#nAD!OVTF0$S6yS3u@^GOCgQe!}ZCO)%4OVYwqn5)Npe-^>*g2VN@=g z=z1{(ni?kuTF@;sx<eef#Ec8u;P>OzT95OF6onsfWM$ei+El?Z8}u9;&F>kHCQO*C z1%C4HTJSf1X@|_&J8_$=u8`pTntq+`w9>h!CH_?cm>oyMhN)QP4MMq|jCfn7W^)yS zc_8<M*2O&GX&#&c;C3QOS`|Gi{(4d@=JM9H{x@}12ObI~>%_=;mFN#8-4Hu&l2B_z z3dog4(D!BOhG|K{H@WK~s1rLZA-3<M^a;zblhgCu8udHOR=!AjkpQ~iV|*ZNDe>E0 zzIRE#_lYc8^N3iU7i`Kf-&ahXR`%nd@q$LWp-8fa^ZuN41_ZZ=a#gNbrTQZDlyQ+4 zy7cuYgd}2Od}GIU*dzl8qvGle2A4gPgh9*hjlCw@BU%VE>q$RF>o)?$e%fAOn;aC4 zJUU_)?}FpYtPCDX=seUY&(CzkRmC0BBB^zTce_T(6rnIi*X3`_VmaV7PKUYX@vw(4 zdbaLZzi_^_6t=fqkEsc9u}-8Hn<WgrlTX7>ttZ45E~h9(Q+s=<BNW^64)L4^;dNc# zHBHv_bcu<CKrIPCpo@ElWKok(CsCa{{UNTDZ9PsP!X3Y5Ua+nr!+ezgtIzTi=@q#7 z_$Yp4e_?6Mn<aP(&SBCF13_%1`=OJNbz2*w&mVz>uyEoQ2^XU=zE$n1r+;PU<S3~p z<)+)lRi~B0!2ofb*;XK=qVbYgziez^SBJ1_#$2j6>1Q%2B4%ZgE(>ZkgZd<$g~Ruy zxwJkbY0o;F!>rYi#dg;_?<&UrT3Xn-;8RJ}yb2`(f*I-`JQh0UrI<KEYq%X?FzQ?P zVW5-Nf#3H)FKa+4slS+$6OHbir37~%E4J45RxG}JMnV@e76;*jr@|#wzypcU`7SS) zODUDZptpsVODRW}#7GNo+}&}u$v4Jje-`<plZGFhua?=@8n`?2*>867*Iio0#+w?? z+w6?7cR?d45xk2=7hhDi3cx9(bY#TV5_+WQ`ss^SG>tzDTJxRRddE>n6CrJH6=h36 z^T>Uh#e{niD#@`f!yx>n7Rke=*ovCv><l-&(D8Ip@VGUaNQSY*&faoEh(byYcrHWo zhj&}$sU}vW>vT~&25))@eCH(B<Vt&(t7JgsnhbHEy3@J?W_Uc-)|9^pT^-kO;llN# z5-#J&gRt2_3$V7BGFo5=UDGx00F6>m_%WN-QjzeD)I%#;T_F0ym>ZFUyKtDAv(1p; z4*T>@a(Ii;&<%Di@_5Gwg9?Ww<Z<h^`~)dggEGvA^@H$7c89HVD6VIC$=r%KnDsWa zWb}q7Ovl?|IL&{C+U@CGek^3aKfZW*xYx_=R32S5B)(|@`g?;`Y~kqFsf%lX<#AVs z&vvQAr<%O6uqU|}s$|k=Yu9pY#l%%FQg$nqfr;`$ZpB)NNB{6R(^9m$q^``nO}JQ% zDi1IT9e-RTNVCI5g)oj#HIQ|4bzbr6es25%r2m!tT^9EJ-C1>@@TIfyw#GQE>NV$F z_K<en-fh|3G<%m%0kiQF`Zy^!c%UWJDTT0-(Ud&X*D9$t2sD`<P(d8IMbw66vRn2& z+VV9+-gDk+uP|LP53_(3mbBcQ<rZ1xLLPNwPn-OVKr2<aICXny9vL=xb=e|wN=B?* zvyjz<mRkSx)~f||C3bj4z;&++_HBgi@rkWiR7j-Xg_g@F>{D^yi+*yKeqCiQZu%)C zj?^|nPd%}?NO{xwtfl2o=5loP9^d&CgOFgOSfpj0%60Kt_TLUB2{fHhn5XzH24h<C zwO?@W4konKlhCjO+7y3Rs0Sh#&Zo)qJtLrw<7G*bJomQq?6-Li;p@p|$h&~LSr_hV z%MKP(78lAr@0L2Z<P@zQG%(hVKnYn_4j0)|79$64LMw$4cTyxY^)5!>PU`Ri%mt3I zJnW)fET!DOkdzb?Egn}^oNYYJH{<*<-vWMKTuKSNt*u%=b*v>qNtXFAJ{9q)p9M^= z(^!kuwV{2zri@*y>&(7A9GV=`vIWfckHFlHo?Ddk3GJhwY7nvzdyP2D&SQUch<l<e znPRhW)M2t<e;M4+hRk8&@-0g9=(2C+Si<4=o_q>`$#ZKH(PR8(F*|l~Xa*=R^PcHJ zC4i5H5H#7jhOQ-<kwlXC#&Wk~W$CMnpE=Z}EFiZp+`YrXYHtuj?Lw(b3zW~&_hqZN zhh;RjD>kqyf(2SbX;;sOG2S#6vNtCH-5Ra@OdmSdfd_#JHBVN3>R5AJZ%)p;+uiZ~ zPFE_xcaudkH4-)r!?i}UIhqE4ZODz&&NZ0caT2y5O1D~9V)ikDW$N0CXE{E|qrzrr z@@w3_fVVfZ-f%JH4uhH&JW?^nz(LEgnPg4DoxW+UA(aVXBRi()0lSM!AnGA3E<{wI zi~MERHB^%mR}LIHJ#W#UZBuUgDarHU$AbK?99C+_wR|Fm=716jP8@}LbxDZ<6iD&P z7L7kZ{K<F+tMd&n?_d&3sIJ?4q*qrVEozzyZ}x#ph7DZpK?@ehcAQ&Xm|C0BA?em( z-MvO)MwH_>+I3{1$kTSc7$`PeOhN88lKmYSeyx$XST;sWHS!S?A+wN{1flK_qC>%z z?EvmL#CgO0j*t&C2;sk$3XVsV`x>Lwevug#N7Wyv4<=xACl%S-KeV$q+SQZSzpY~0 zbz738^yWuHT@XE)V`J)l@Bn-lC8+B0EZXkG{CdOz{!S(D^EpD}NA;?l@M_H>BsnX( zLy&=nr-b@aobq6zW9NFhsU>Ds(3;HS*1Oh;E31kDQ)2hH7G@O6*sme<E#NMR09$c3 zsfsS>P3%=klYEt3%${@4{RUXuh;*45*^XYKKXivCia?xNQO5at`*ba2I3COs*%p4Q z5tZ&vwi2RV^Ee4rL8OuJOfw_MeTz<*O`(2X9w_shh~`dPNhzq%dKVjTr$7!+kA7LN z-gwkvb#WIFkdi4dF5NE_<kqK>YE@)v_%u|{DyJi}bzx9=ZI!}B(9g;o;_i(Lp~G_k zn-&?!jRl(_xH#)2t;p5V^1`PB-#7jE#aP@}G-pPztD!tgVh}Wq#|@=D2Z67)Xk#h} zqScOQJ82*S1QuS1;?z;#VD06F(-5Lftg`^msPCrjrk9_msT9R+za1V-e9o60cQ@%k z!<NlsOirMX__5JjdZ>*l_TGC1cgY|PT=C5D#&emwe{|U%GUJG1u>GT4%Z~Y1ObWJ= z<v@<yaSin+es*Kt_tC_F+xN4fa^g$IXnjdDkT|eFn|^vn;(^;?uk4MB)mO}HYIR#P z=e1V9*+RF~CSv*J%M_x=4>zHARJ+d_ceX=;MB`q{&X>Q|tWt!EwH*%ReJrtOBKPao z;_j}Pk>&Rv<L@p#Sxozeb+^M`a&_|PVpwP%BUrJyVq2o2xP##P;av^E;LLhh?8dI? zr=w{qYG=j=CmwPXOqaWN1=}=J2ONIq8E?ftlP6s->9uwV$`WgN@Z(^IwjepF;D=Os z9+I>c4LWvCCasZcVds6^JU8w}{8jCJb~hUKn$hZReEUmJig}7HmD9j4ab;5jjc)&s z)Qtz3d?ztw6w7!;YuOnw{%$N1@=W*cGATTn2V>6>p@$4IV_md*Z;EqunvE$5GOT&B zE>{BGh7KB&y%wVu4MB`U*?U1OYu?g#ztkn199QvU3+fAmADve`SJFi~hdTUgpAzF) zba;49D1hPPUN%YCZFMn`%X3prK98TivoFVM8=HetZ_=Wn<LnjpUr)S9+)z+ZM-c_5 zzlOL@-|sLrQrJa%EyO7#`<2%{IXXbE0w27b?Mm%Mg`niiX|r~s^jC-ZRXp7E45DU= zU1d+2FYk)zFlgWKG<qh}q%|DSHY$=G{eYUX?s~A|d9+n>X0F>MazJa$C~nT<y(}@~ z3%riu2=ztoq?_W?!`N?TDzh@v<Wp3fy!wx_bQj+Iyyf(2K%MQ>C$l)2P*a}e$d-jc zqKiE(^>}NHXib~`0`j<NOrUlsqQ+DW@t{smsKANreqXhy*R+OZqS=%gm8<QtK(lEy zM0d}I9kPd&-)S*N^@gwbdYBc^U!`t0V0T4hlv#9GPrj7-5I^6LJ|s<ep><xEIgJU- zJGmwo<Y%vmHFsRk*X8{z=j6Q@w;;}rrd4sg#8zF3Uw4`mNvIReUBRVhDdXdc1c{72 z4Nkuq=>N`jf*=l&g*Y^&4xM?9Q{vJs3VFfrTF==|ESGZ}#wKcifM7K4`IrqcK$%BV zTEzG2<0QRqi}Y?-=m?<Rwh5ItzF9_)#(|Sn??CF^;pwPV31Tb=CsS;LAgV^Ax6Whn z9gab8%eeDM_V=SOQYr0G9IRY3eivh3v&Sd7ug}+P?{L0MkSmSQ_Gj@+K0@8GYRiAi zr`;*~r%0xHXc}o=faX28=hG?T)&-lg0JoRqZVu=3FnCI`AF`m0V5Mf<S!m}eW%84t zrLm$rBx(VzZuExUrt`zqa`gF5spL+I3y;0Iwi(WD*U}=E67OKW+<sPn_y?V2jWy7) z0P#Yhxk9dz!UvO`B<)sqpx;FDx~;;Ab0NRH`^}7^D8uUPipWljcCA9&#%6syWcV~V zEK28^B5+()i`9AA6=%`1ZBEyvlUSosqvP&w4Q^4B4a1QHf*u;CT~{Dk9%JR&ZGf4a zJ?t#~6j<ZNQV7+B25+<!I;E%>dq(RJgcNN^U<!PsPIr>b9?8}bGm&kzwVHZ$v7B`# zbWbr>qCHmNa%BKKkTkGZoS3gv2B&_K+&Z<lOy#f(00)wdv)n4!TrCl6k;~aM$K0Z! zjy7?Ad9sipGc?iVUkgGw|LTkiCfxPn-AZ(8U~1G*;saS211-JHFH-#CFG_%QauUa+ zrxL{NbQS@XEpes@J_DmQaSzv1CBKsowQFUyl7t+0b&b55`JlJuMEy)|8LL;xlm=Cb zYC!4O)Ljv;$%wWqoKW+xVdee>pPQNY-CcK0-?q2bQ0Yl9yg68FAD%w`q>9mEXrBcT z<DSp90gY&hHj>{=NNa3=nUl_l&X6URHp3Mp!>jWwL9Ez{O46b!nLL?5kB6VH##dB{ zfB&0(m%nPll2kb?l>I#UEK@FpzC0OEmMAE2GM5#jNZ2qJAW)yWq4%ctQ^s@~4uphL zmD1fJ$|_4)z?pnDghi^Lwb+lTB&v$5(5|*q>1xXO)EGn<3nCcF=3?!Si=5CGv);rt z683)9YLkq|O7Aea8|}6WITJeAJM_Xqae#*_eM&i@)8cIGY)*?3%l1H!PWWnMLEO9- z^632b!2RC*XOwgZ@3TJWD(zeU<F)(4d9<d{hbDTxtotq_B-3BHPm-!}>q8QbL3V=l zWa)s`;8l#$-_SbZ4kiyJa_{N*WzCD4oj}3>dEV<@%hfgROeKQd<5BN7M7tpf)?X($ z-QWpUqM$R!CL9D}8jY(<#%Yqb-<>OvF?+}WtICQtgyT4@sf2O6eiPqsvUV0D3YJ_t zmz8QEWGeWO8`>u(nNG?<%KD2ymc?J2w^w!YNQ)Xe>h(?YNMeEuP^r5L<j^=A0NX^> zr~Z?=S!bYCjMWv@kPtB__Bfa_0|wu2xbZYu)++klXV?CRBaDM`Ga-Ja555Cpa}%t0 zo%mBB&_>3dv56?lyKwuiw|a5P#=1W=w`68zPqdtT*FQH<oF8zO-s4$jahdM_90Rg1 z-$#N&D?P^>$nP1B)Zlg>TYx(}00fAON$04xJ3gxFDh1$MDhY%%7;VwZnuY*x=$Hf5 zH3C6QYKAz7Z5P<2x2C47=j|=mx4WS*zV3AXv=QIM?vuAUnn`0Y4j7<EKR)BAAWWDf z&n&$9nMlcIa>G$!TGU=uu5phG!6dj~)8!=Z>V;?vQ=vg9#WU<IlntJO9<$h_4A_CQ zT2_wl8$?9{Xl-Vk$FHP6XYl&@Sq(0$L}Oy~sg_<*mJPKTN?4CI{OFe|Eda{RwO&Y+ zKCI^x&X!nSI3#ai+aQtC2&iUOWY?sHmusi#&Wf>!2|}TR)Ymk!H+6guiHbroRtp#V z7{9+7(P{8Fcvzy;_Kg;9*)0yA<ad`gwIeRB8IRxE+o0jd>gG*$)FNUFU0?gqpNnvd zv;?;j^i!Egb4hREi#=(ZkCEOjW-(6^xjcFPNGhcy1g>S>haIfMDe5+jhp@2LQ&R{B zR6yWr;qd0!>CAgHbAfQXssuOTol_xhJnhlesujKM(E|)0>X_J1&94Onsz51fP{+~f z7DE}gqXiu)pYbxr!*fe_(p`Ljtpy3+jH4Id^BVWX+uhZcm7a+;U$;QT5#uK{_EQ4~ zg^^p>acLi@Qbx`|`*ybdP@zXkk<W}0WXSPh<G3Bh=ZM2*eLb6p9VbRdBKjI$&BCZn z>Q>K>g9uxGZP0~Lj><aAbn2}!f`Qvo!sPx<z!7s-3RCNW#9XPQw#QABtWX#CwszM9 z%M2(Ft#kg%bhg%&Jn7z6dK3$U$LIOco!7)e$FaNv*N-m{S1y_BLkiugJX!cbuv_-7 zY^`8B7Q~$S(OWhp)a^70pLMfUU$<qbnHVINa&u=KHn~a;vB#*Nl(p*j=h)O(6HQc4 zSiL>eyE3+<Om(1n{)piGkahNSS$NgF{LT$jEJJy8>(<@TdIGNLFwbd$<oPW$4qbN4 z2FpBNI{75K)?ufBq#5n9>*g+7)LY$LbjWuFlC{%nARN1xTAt5`luCdsF43mjJJNB? zHl>}?Im6C@B9|*tN`33QHSa@p(dr3hpPGdRSS_gM%_`1ZR4P|(JW-ypvcs?Csl`&= znQ#w_$gDjIDD*BhrfD6aP@YGS^C)}rlWI*k0W&>EN~Qdv2hh4X^t)Er&`@T2kmVy` z*CALh`smGrQfU!#%xWSB_)vXfnDr=U0$mlaHnOhOTI07h$xVsmYEt>fcEfL@Q-{xQ z%C6K@yY|{;oaG3tav8%$*peUb(PHToHM>kj54JT+-3nZ1ixEarpzNg<Y~jq7D!U3a z>6>9z7)C4_$LH7%K;eYw%C#V<w`mDaa-*Iw#sQkf6X7l;;KsuG2xlCKU3pW<6{)qJ zNw>P1$=>BcnalpfyNha0P5>6_eGuf)g&#u?FTL-)Qzh~}wh?3%B~))-uHK#;(?@C6 z(!#WOzT#Z5I=-rFYCP`4_?=kUnu})X@jJ`1FValC+b!Nija2WTi$e=FOq!!hx?>E0 zgr5Se)uCKa4uR^PbfHQw%N{0&r(dou%mB=%Pkkc#S3WfAW^V(kBgGqUE<eC5n6LBi zET{s<wKL5rbj)Qx5Hk~s3+UvHvPC=&9NfRGX%nt@RUPxLRZ6k(k$yzbcsF;H!MR7Z z`B;v%cJw^$=u;9|DR2e5m?gLDFzL>H@M}v?MZ$yMfZhF(!^-bTv8Ifu@U4;h)AMur z!wo6^9Sr5`dmHfcBOvw=&-(fKdB{=RWTnlf^mK_DQos2PVh-%rLxYyBwu6JMm&Sk6 zQuJ;3Jook9KBH4Yo^5WkpOLzjm}FnJjfVSE;%EJ@h5ZfT|4)hk&$iJV|5(`HPyC9D z^$+T32?~_edH|dm4o7O8i13K>uDlU-`V>%dLo1pm_qwH*<K0`y*a+k1kqnE`9s4+H zQL@*u)DAdbgPOO+7-!6~y@F_W>P}ayKwIaJxSasc#-Tj0+o|Sp$GrRLm2Mf5#P;4> z^NA2K)gJ^8fTwTo%&p8hS<MUIXy1<dfRtx0x=tH608P*vhcdQ@BIjO>Fv4@mF(1us zvEYaMFA2EE2&=jH@9`r_0}`u=c6*sq5aadVpaoO^L~RxI_=X^hkUY~PrH(eO$0_r@ zh8^Br<gZSAr~il}0{<muY)%P8P(X;!fgR0JXO9bHOo7LL*?C!kxjE$THZxIAFKn^@ z7X=t&h1UcAs2b|8RM=4%bI_!6)oo!={~l;7jEb7`npOlML4*MhHK?xoRjD41@<-x? zPc<8Y1Oy-PW&B^$2YjU`Kr_^b`O^+G=xJeA?s@xHXGY(DC)E8Tf#Ag$_D>Wo>Pzfs z4qh0-4l}dwzo*G7ik_%|@y>sGRsrSZIP<bKvBDdo(ja?!?2OXy_C4z_{VzSd;(yDi z&FO=2dPdBPzyA^B2TA=o4gGW4qxV7iQ;S{<31hC9t^doBObQ<E@4@s+qVEz!L-jsl z7{L#TK9f;jOCAL;VAil10Q-eQys$iIt|g8F7f{c6`?s*7#RUn@iTVw(q9p`LYUm8) z`}44(KMCTTf9^*!K&(;773ZTk|KSszfo)4f_B!~Z%O0XmE-D#vW^Vjm7tIjNb+DH{ z<u@ds03m&s1q99BsGn$!1nmU``Ogh<A;g~9Js_@cpp6^OW{iMCu4YQao1qPF;uSyu zSpUp3;{8(>Du=5z;tSrX7utjM*q^gbbV(E7ZTxc`?Jk=*%9)4|ajReNfhdA5;M`b( z<A)C>qFXl=8__mOK)Zg7B-%w7ak#Ug!|j~sy&c*(wpb_UFc1+sPSr)cEfDZvdM3>7 z3Lt7{LtA@!r0QCy?D7#r>xRDS=M3Fj4~QMf#VJLAd#8Xh>yP_Z0V7d?h3KuHeerkm z9zB!<(Ra$Ia(^G=KJvY^lU|OW8uUWoM{~d0hP@nL2Kyv`>{r5;PecV5G3IKrE5q*m zF`NF|j92H6C9fz(qQ8AEE2lk!0kLxTTlH5OJ*fut0k4bw5&Y}=6<!-Fz{KaguJx}Q zP<V~cj#8=zV}t+>$;BJZfu-$%9ezEDFUs&T?nNGhkA^GCkRXDf{8#_qZ5e;YKbE!} z37?2x+An&-7fsLKdTpwpuP;$4@G8}>YW-1P68=sjK$iGdf(SmdZ~4ZXe~td*^!ZQw z{{(6?|J&fB0`(CpaUi~d#~wz99x_^R4LfmR4|57?xc<9xTy2Bb6*)qV!Mg15L4E2e zFMrDf<ADR!U)e|TG)4aJ5ls4-di4zT{?|XrgSu^rP;vE1#{FBWiBx;}dkPAFzFrb_ zA%gAGXO{VH#*Vt%YoLyD2%nt#iRl~WOK?PBC_B1IkAXTJk>Ts(T!Bk}YId^0Ua=I! zIz5i^?@s<>vbp&6!6!dagSypIkmLW$TV3fJ!pjIWzZ!OI+P?QG2rm=ac>Q<hUpBP8 zMkxpm`hVFlz1aNwe*R&jQs`gye}aFklGJM%zRLgxTwDC~MqjEDx&QF3i<SV8{f{9H zf8W&}f~X$vpBwnEPcDKhQ1vH8tLRJF{?9GrMG$gJ$+ROFkbXl&>h7`SB}0GIV@C^g z(1X{0t=1Q5OS%Gw*<*OdJpO(Dw@?Zuu-l~?O;tqbfwj5v!}|b&pNLDnFwcwrBq`!i za&v?wxmz-<TzR-OO9Fup;nKy{E{59)dw}Aa!(&_(U4$9+H#TeWXEA6^ET3BUl=IiU zoDG(QeK;-8c)j}UD9zGov#gTjXjMlt59jHU9UCba<WMR;9}dz6A2-rD*RQ5F?!W#; z1iG$x;*K$IWNe(Q$HNw2TWVBTi&wA8pADF=r{-=PIQJre3(0)gs{@M>8nmstEGa`W z)JIr%kK+mZCqvpbY6aq@0zMR`7KQDPQ6AbPF{9CVEuF*2OmcIEWP>b}YUAq%d#oiO zg1_d<eJ=YP-qd{XK5UWG&$oJV3b4OH{&n@O$x)}jD&Irc|4`LT4sk?<mcAf0A}NX@ zas=JTRkzJy!j;CW>3vfB@`Yan^^FefxXrXnybO5eyl#YjRAP}$Q)Dm*L*JVN^)un% ziXF$I=!##nqgfd4KDp=ieNjT(nvn(M=TT<<dQ6fAw>9RzbyQVHTaF)Z;Hlp(j@GYE z&E4y<y7YeC3we1S>%25t`SrtiEyaN|-9;w2%;JmHZoMHj$uC89SYD5YOlEZ$0wi-> z`N#L*jErW^Us<FX!Uhl0r9O2`D=o`*=AOqRI*{u8Ld3fDIacr%8+xYh%t~Ra5NVpy zDx!iY$UA&F6j3R2PwaEkEXs`IEdTxL{p#iF$k2R#1M!E4Nh9}bHz}tlHD;}<Xez^K zo&kz1iC-#F$%Lm};)9Y6rIjoMN{Mw=gJXyI`ASJ-npbZFOPzV3=&L6(ODc;<gP7)2 zbB^*x+UjUEsYOdjPMCD6LOHud;K2M$8;ZIvMTw)COLBdtChVpR;)`)W46ny2l4?(s zk$MY_gYrjYADtRj19t%D-T@Qtgnj5eUS@J9^>;@U@yQb)>E|1>k$aDhTkm;5s>8dB z5Brt6<qI>_fsO%>jRHOqG}a=^-gM-7#cM|M#n%i~C6h*hgKagw5!N)j021D;%iSU- zn*ns1Rg2T?H9TmXbwOVVZuVvWH3gp$_{?Zi6QQ*@B0hvA&Ea-;{&zn1`lIdM*H>n` zlwHPz53%o2Rs}3aw`cEZD^@ro7q0vK$3YUfT6Ba^5bwtf(UIcZ%2Vjm$TvbmCOWuj zK+d)X{=N-QCC542Tf=0ayfx}5jn__Scbm=9Imqi~bx{})vYdTj7t|p^*5o=U=LBna z6E%U}DdgLXeaMf94xx7L;cAOLQs>+y8~_|Fgk9c{Ad!14MVj+aC^rjM(#p9n)c4)d z<d5B^wF>ya+QokoE<ft-N>+h;K60n^_c@K?ukbXInx*87;^8aLE4`TS$_QzZQ86Vb z5{wKfGB47Znv`#iqusP19NN}1xVNH*HW*s`3gXe>TR6l5V9<?`4^2=%?iz*<wKh_( zu5v_Kd8x)k=|c#>n6!@x;;by72wG9u=Tw$3B2Jn1^-F1W&V3-oE-hus=fU0v6v>mj z7`rvvj$G|84U24b>Zg$W`vv~%`w07AKG!Sh2NTN~)6jGoNitI2Hc1C&GZ4n>{Ts#r z_3S9hY1a2KCZloNiba@;v&XC+ZFVJ>$2z~O8)ykGk1xoIEPxZmjNM}w0hTl>OfF8X zZf`QpF)#!&ghn#z2~a(XKFeb@MSDY+?@0Dfb|gBqbtEL>RS_w#5s1Zja2Ay6MsDry z!tS#_^gBIw7vPh<sjz6@oM1!!eD7}}o8}nb%-EFUH7@weyrW|P-<@-X*SkeYRqa;^ z<;tusP%@|?`&}wwYfSEGcGIuQ@A1N3y+P!pRv~5NsppzYf%W>__`5*d&_}n&@4cDF zC9UrEz~F1jV)Hf1rSeUMyT;RBp6kJlmsWXB9v;R5rE7HzAD_At%o8|L-(QfJwV4W@ zw41To(aCZHv^fGDeOhO|7^2?x`L}roa+{9mS+{})O2g2)7TpEIkf|%5RDVZbsO4XI zce)>@>bB=aw==YSZoNkt->$XpZ?Wd%cnm=@>soKu=Rd?Y%ig|RPxa1N@Y2^g=DvC( zOUBc&8kCwJe!kX~a4gbV<YOf@b|2qNYc;?s`^qdYiWE2Up?x(kVE>w7F25#H>NYPQ zpi;v8wN;Wl$7ZRoG~}_!4b*?CX%`IW+WaW<J?y=6Vwr$()(-fFZ30zTklJcI!v%nO z_^EX+v_~ZG_HaDY7}5*|<}Zsk2@lZ4{65S7_4Amn#CaAIl9382S-bO|K9^uHyP*Iz z%rz_KfyL8LA|*q$oSyGDJU`6JPWccy4<$7B+}mK(MCJv3i|)3cXDlW4JiBj(K@FKW zv9*Y*V(@w7(h|^tY(V&gdd_&iqdh)b>%P60Tbcfen@+YCV{O@;1BPpg<u-Mnjn(vC z`2lB{$%g)J0+2n(vb|E`4h1J>1}!S7tZ6W<mhzUmgR@r6E;UMp53#CSEZ@%Z5~7g; zPd^{=@yvhdq5JGnZjiUDC1?hKp74IVNOhb%S;srM>Q_l-CB>Nlq1NhB9`!}XK1peE zs4=_~-M&c_NV|`HS_skzRJ(;T3>-RWXrk;(oR-G^f1O=-Jk{?Pw?(2TLM5qaAbanu ztSeIHtz;x4S=l5FyAYXIBqM~7T~@M_kzLt4^V)uQe7L?{*X8$5uN(J$p7VarIq&m4 z&*$Uw(a}}t4C#C(tuppZXu(CN*DI>*_33%#xm?A(AG!}%OYH)?*$bB^&DrI8iXCL0 zi1kbiTz}W1zp(hJY`Mil{zw_!j8%!sfwy$LtpT)EVy!WCv6=52K{i&sUxGBi@=W&Y z92G`9&Ce#-sG^5H7e5{TQ|Qn4*Z#ei{h?pdI;q~nq`k))@9Oy4OE}B(-Ih)pGk0>S zbye4Kz7XmYp7Jh?RypwZ>I(&u{G5R7U(@@;Ejyko=q_|*eoc*|4_%rrC1q$VU%Kvn z@t9m|<mjQ_P-08^!ehLP{ej(iCk3r6WI3bSqQQ0DCs&~j((m4}$Y0N_n-~p0+xV?3 zOfb4oqLa4ycXk)kb4RO>h1GR88_#N6MLIuNw2`lWT<;qBCTywmUibtXB+S#up{VCf ze3+D`VPk1bnV-PD;tYS9yR7@W9TaVt>Q)1LD?J1ok3ZMxk4Sg7#+7<`b^<JycP6D) zs9l&*_e&&`{w#AM8_S1WUNcgj*~o6=NL8sU%Icgi`GNA+xo<Nhx4bO+;rXC^Y%)%0 zed;fRJFz@C<yf|fv>=Zug;mZ+P4PKqituQ`>?o7tya$q2PM%uzr7=qhB8gMDD{4M% z%{N>#JLqURD3{Z%qL!2U@)HNe181SEGiD%-h{>OLx6?xHZG$FH^TwMriSaEg8N`e% zpN()O_x2sIrzneC)fl1fxW_Xr=-It)*%bwbMzn{trUtl9rX6Uld>UBiT)N06+Y_+% z!(`nmJ11ctI{)(Tn3Oub<*yO<)MUBVW%fXM|HY|0<M){U7`<@OtdERz?NDBtiN5~r z;-aO1Y4E4YGf!p}vvtFX)^aK4hBy|=iIe&VoJ@24qX+jty;j^&Iylq&CRmSn!KVIe zaLf@cQI+PI!q|G<le(`3ndeq6G(w};1!%H@_-jqY^8575vObx-SW{qbWE%^8nV7+p zl6euT?RMZUvzo?JCNsI^`f2`hi142b>Q`ez^<uJZ!$HLKt<$4!)Jz_eaWBllmQDdH zqOLt(U)hJ`^o@=MT&p)(5M=0YweC3|ZcU*y6HSv5*HhJ(J5L%L|1FZ}xY>xf67)f2 z2SpB}ltnG26%&|(jk>mD93;O)a>QfMXIxH@x%NYd)YZgpf&LdxK94?yy<f?cURh=D z7UZ5_@cTre>T%wX;Kjs4+p*3S#Xm3EGgKagJzGBaTVuq*rPFKeaKF#8_s7gh1IMRz zjOLl;8h06^q0UVjrPps9gx0LwR2K%zl4ptN%qmrZ+2yRS&HByucjar(Q3n=vRM<Re z)lX=NGwOXiAenTJs%v0z?p>Vgl)rw`;u+4++;CSDUdB?HQUx;^F53A|TI<G#WS+QC zRWHFLA*SJL;+u66PA|2~Vswi(GU4xL#1>~(8%F;MUOOPBQo-skWO4OMUgU*>WwO3B zi{w?F3<XiKG*i9A=S7!w`pROIxfb$z2VeB|{!-Mb43y=guYO*Av}f`vZE>pYs?L&P z%Bj@0<&I`y(Mds_g#6jgNb6R?x2L^$?(pAtUj0_bduOF!LgVwRbSK@9+z&51zPOb- z7vfxfvGQ-yH({xDH?lN(H6`I6-!7?W^%QlHbLOer=?(~n`lR_nFU-Gt$uV<FD9<s( zq$7O&;Y!QnG>;dm@=q@=&gHzTuCLd6nVoSn&+75BH2oh-bHgP)7HM8@*@nNxjc2Ln z{TecDoUitBr7Md3xXfI@LXj`qK9u>6llA(ckon&Xmi@wi)@mAD%tT()OXYVsIxo&e z*h6{LD{_Q#yDXKo92>9qX(~H2oznkWaz=s1m#MeS^63M-cL8hHTtW`Q9t&#PXuc@0 z&cZaZR8ZUX`7EbxCAm%pW2npJii>)@SLL7V%V++&dREQ(?V?YP)%^5mR>4aT^VjO@ zfy?~$!%k5YEoXdO@4HPFzxB=+qn8`=P}0VWKX7(@>7{hc1?{Oxt3MY8J9YlZm@J89 zv?;WcYf2A(rI}fd_hB8y=W%lJy;!7gE~)iY+uPB%Z*(=I@y9ERg(=%5=iwM5N4aJ5 zK3;_%!DXz=+y+y{qq+URx$^wNln>bQW!usB%RkXKZ`;2@boGaCslcT6`sLc<V~%tV zriXY|$2hKyuRcGdG;xT3Y(j=J;YYnd&nWC>B!Q4+pM?I=vucz-tHQ!4PT!CjR|BaH zUi0HS)uR?W8P@)tm`$w^I#5VYBcG7%|2=vRT$wi0vg9Wh{6h7Vv)G^Cc8x|v&b8+w z6!TK>{c3r`<vTqi6)1+4hHXm*8Y-KPmt{qpt3Ke4novwhc<toJFj-#{enGc-c-Ydl zBYe*Oj&WM%QHP(8d`Jco-Gf*1M&0f;4Jt7t+TJai?H(#lsC(NV&NkYv+WVyJS20-W zO}yy07c?g{t`>66{wimVk86!g*11&nicIKg{6g4_{ZR*jZs^n<PV3@!`}&R>jguXg z*`dCtx?)GFLX0vJrZV*{G@WUA7VKJF=5MIjL_N3neJjIh%cCrO(=L*m=2{}NMc?<Y zMzlF8T!!ECeSesBN9QR(pX!W+<XiElGxx=I$kx13;AL=K;_(I>k<V7^aN6(}N73CA z9qL(a;)C9!@mY*B^HDpJ_4D#ljAw_TXjNSM$){%`7X_2Rwk74|mWOF`Sp5pD&V-Io zYS_Qd^-j38R&g&ZHg?$Yg8#gjk=U88_-+BQ7Tr^YeLlXIN5z*C4pmd+cTD%{ISkaD zryYLFlXgJEdvWzjYeccF9`nRh+_S_pes|tOC!G?{h}G*XcPR~gn*?jD=@|-?zOt68 zboTsa&H1xF+Sb0>qKr)p_NKO{j4MT>ql;^+%mKF6{#Qng=#myYne)|_?j#B5wu^}d z_0ZUg>bshr23xQj@XdEtwI+Tf7inDUE}y($$R5R27u+NEi6GM9xA>F%SnZblzcc2; zFWmo>dEcp7>X7~(B(XF+5P3ac;AOY|)QfUx{ofh0s7k0A<?vWJKF=3}6(^qeu#G}p z*^ywjbM(tzpBR^@z6Y-e(~H%mfceGzM2NhV7_?rx*cQAFUpGq{FHA4};HvR=bu4|* zAH3+0IAm4Q^us6ZokcY7*{svPFS~-c%xJDPQ+tw)lurg~)h4Zsa0V6iC!1FjwX0tH zn=ujg1DxPs7oL;ZRHshmcq>+O`AAs{`Fr}C`Vx8kAw0w#PXb&_+M~_84V?;29S0w* zA8wQlnK+{Rt96trk8DY4tgN-INuG8^`2A{a#+`<%E-OY8xjz=hNWp{A-Ta;TROxfB zb2f6i%44U6=C4fH-p*M9v&pr|b$$>n)M1!je(^fTh-OWDki0NlXCdqPjjo#Pxy0eP zsc(r3oX~~h)v+bpq6_I3Zz%I}4^tX7Cna+Zvad~E$mA8`NjX#c3EUIhRL5)!O)!H} zObynXYE$y_Jx=m-;C(1RKBTSoJ(oj-F7f>Ot^D@8X>$7Yg&zx;W5NoE!s)BERBI<! z=LfD&Ef(1=H8CnEtIhRjMB%464~4Hp52sd>COAyYP*W5VHS*o%NjdrklGe|`aHlJ2 zXjGlf#bRBean>q3ESKl83292LO1@28n22*UEpgqc?CK0@O(y2R?1@6}L}N-zd1;Lk zr$~y~^SW+*x_3O?FMj4#k&9w5g$2c;fT9#b;5xT=o3Q(pRW;j)i2IMaiQDa#^Vft6 z#SGIr+f0Ol37Xd1GctsNiGC`*h7>DD&w50-nw(=c>*EbiIP*oFb<$4uL{YS;CwXMc z{AZ{00-7J8w^PNO*B*jQ!%IPJe;J_{Dg;4`sf2H3QyoEzowi?~%L|~kz^oRk+#Bb6 z*m&k^s8u!HX75-<usg4C3O|H?1P`gr`CL%rQAjj>|H?F)@cX-P+FrG!vRMv&kW$BG z+4CtENIr=OhhMR<UjNnA%Nnfg6TQkb#un_d{%!hOb*#EzPefbxEmCKlYsYdDT<L-{ z3_Wy0>f-!~^RBh9v&J>PJGWY|xvu#3`j1Suf)UBLE<GI~#ip{+Mrm3v&9h!!vCHMZ z_H{1S%RD>9Yp}ebwT>#Add}|MgXjVem71q7dH)(*6iO0le&XpLS{TsdT4>%vlc~i? z=5HCUdrzKjaL$0jWMQc>`_CZ5xg+bVbAQ*1R=^Z1uI>0fa!tL&t^~`q{(9&C#FC#1 zgk3KBwO-FUI7Yh4z64$;4m>9P0d@&U41Njdyc+7SJnQ|?E&;*+%JaW(4#lCvQQLTP zC|L*W&7s|=I;AuI80s=k2A?{A;EnVRd5x0zr<XPD55I^Dx(@p@kq6Q*#}1rJ>v6d3 z@S)i}yQ|I2Hs{80cmDMg-(uH0*H<QH7iS}T1RM>G>K1DSxSUVb$go|?JXKTk({Nyx zqEItTvrTKc+|%n#XpePd%Ios=d$q&P;jU2Dp6`xbp;x2ta5-f18?kk<EZEg6=Dm2D z2QjVqWZU%Y{C7%jidu211{QU<X%J12Z#$>Cv8=DLK-g2m7+PaII?rhkMH8VxY_`vv z2iz3py<VJ`Dtf)%T%v{uH{shzv?Z|iI#8XLC{Nhe|K?)F4XX1}XW*~=)Tua%{CFhB zslw(g{0Yy8s$_Fnye(5mf@3JveY`-TRnnS1-+^D<*3pMOIT!Za=2Ng2da6?E2ayCr zU>`G%UJr*o9eKmN9LoGY*hl!Fu{uHJN6jT+gTh9FeKbUviGls}Q`O@t5bQD0(ku*c z*FaPoA7x@-#&4@XhK<vEg|U~Ev{uHG<*B8Br(vvQQ0dW^zNEAvs>ZzM?~uhen5sW} za5Us9?0=U9d>)yqSAKddx$Z5Xc<gcv?7N>rB{NkSt8S6iAS@j6BvmEX$ASObTVi@x zU$nez#k7L(YS7IH|GGfVDPf;gj`^<-*($6!qCBR?*2^B8sDS;fL32p)ef0Bm_KKpT z_2TdJ7p>t>Z$6-pr*CM$6{xJlyKEQe5EC_Y*qU?1U0JeAmCstZO8V>TRo6g2Nm)qg z_n4uZO(Ztr`LP;44SrXBM2}M4Q290ZyjxMk{VU@!juVgmKq()dAc$h%AR3)2ASocn zOY^?3w6;$CjD$r`jN_307!BU?^Up3!9~el+uH!Ag{##-DbDhACSmL1Jx;cR#m3yzu zFMi4&&s~#i_Z>6B`x+U_Xi&w$@a#1Km45fZ%s53Kd+CK+K8rFnXG0xo6mFAOgr*S; zaJerk)s#BaxZW16n0X7cRECEa%sDM!%zK4{tq;=1QhxNaM5d~j<ssrDoJ6;eEr26x zgV-w`a#Tnhix=Kk?Xh;@UFh_gW9pSGpKtsHNBH(1?!8JL=6luW%Q5w5hOvixIT_*q zV}$>Yi)|1h$xZ+E==J*`#7{2>Bpj@jIVZzN(;s^Z{*F*8eU|<GZ;&5*&$Cs*UV#gH z05WG&+0hEb$4gH#L2TzG;SXqr{S7o|;|pgi<fyBp7h!MBgn#!_nCAo7OFt3ciaA@s zLJc=V5Dfdsb1}#p{t{3LgM{ZCTfW7SY5>bde=iX7SC+?NPB^JQhCi}dO34TILg>w3 zalC@rK6G9NX225mr~<gFS3tBTw-0kn3OueP)Tan~O4`7}jaw_8*&zIpfK@g);gFe8 z=VuvR5b!JfTFIFP%8$o*-i-TLX0y=SNQl5*!73ixK){Xo)dThCbhLc1_k%X!_s8HN zEdgH-H&x{wdE@{QR(CrKp*23C_2^aXm8Qy1ZPW&_Cs-(sKva#1!k)f@^e0flt!o2c z$Kxhd66b2T$W6ftd&V@(9UnW`n7DMFh7e7-k@Kod?RB9yVK4Ti+3;R+26V>catCb= z+z-MzaYEz6VeW=8XN(1s;6H6c{?vZUPknu!XS0M`<`Y**!Kv)8l~QOxP_g?7d^rKf zSJKy5NS`uWrDR_IrzlAW^R4C6rqMxe<D5w6s#Ot#+5v;w!x3BkRy5R;dv<5Q^Re8C zXZ>|U#_-2EJAYT7#c?1L@z#^v-=J-L<h*QngT^e=Qlw?b5fgvT!Mo%Z<iUj^4eI2z zrPVYt*WQW;R#;qK@Q}Nd@w_EsBnPsbdG1e3=Yxh}p?6JfIc^iW#XT|(<OHjatGc{8 zUqHOW<Au8I2@^u~c=ykn5gWXl?eIOw1d@RAP4M<m@DxbdF{=c6R4HF?ar`U@N;~Yd zuW)3)Qr@tFQ40+&k6VaP4fhGM*KhSYEEe1t*K9-H%uq={tv+?UU-=3V8h#&g{UYJ< z_2J2AkGGKU*(Ld|9)EF~^)q$~<5Ue62~~D^tF#{-yTtgKdE8~3re}O#>O+|7JJyEi zafzVuool1E?zgUYFiH)Jg2F>|NnAqlEXZvmJDSD`xJ4W|ET^>YT~bu4K1LOVtK<mg zR`i}EAzF0ER9jVtKqttG{u=TRiCQ~;_^w5g=SE@In5^PaS6zRvdWjsOQ{-CkZ~zy? zpnH6BVwSM9>bXb%l!kcZA1##|AE{V0zh!-S;Q(2AZ*CV}NSx63{-kFY`AOyscID(? zcQ&ilK=(%LhI5^=MQH{51aoC)(_<NLe&EbkztqSu3oXAzsFv#S#lCWmH|-vTImP=d zy`4wV_4v|m2FsiGlkNt5Ey8n-US^-yFmPYX{w6&;Pds$*aWFmDP*Q*7)ms6N%+G(< zx}MBAdPvxH9bH&WyM5_0^O8N(z&&d4V2A$4ePqmoN4Qt_;ps-Ye>mYtz#abNA+PHi znZw<g0_ao0&xZ$3KDRd~Jo}>EaaC0yG_Ty3dY<HQ#ej&2AFK7FCt?MnbCW6(b!FE| zBIu|i*k~xyodd{%Y?AIrF^PztQ#QZwV|di6o!>ae0uN;NK7>Za%Z*f<y52jNped!k zKsF{<d{qE;S2454iNRa(y>{WBL5;ryRV@#N#f$tcSl}d>EoX94<6VU;8}<4J`3X0u zwY(yFCj?o}FpiY2Q*0oH-O4<2<xvsjhPmEdf-bv`X6I^Aw^aVM8!<BEg7wFKWTjf4 zQ0daYl^L<WiA~ni(s93t-s(eY-3aj<Mco_J3WGL@-yc{`@HdzZ&n2Aq5-mO?;Yg}O zfvaiHaeMT@{)USowOZ7nc)kq6B9Tfz-2aR-*xNMSg0PW;L5F6KCM5cwC2L>b4^jZ9 z_br>cY30{U-PGY2B8?Yvwmcg%6)hmOY<=iEG*BdeKkWfu*2QYkz<O~$=&eW9<!$0x z(@Kw+HEtfoHJTs{fQ+`h%uZ&aODOQw!<*!X-1tKA<RKqUmT480AEDcl`pusgZ5iX# zLzT50<YVH#u0B~cJk=0?oKr_zx<@vXEw#8=?||0&;!P;@#3h0%CrQc!5Uz*IoeE&w zC7*MT&YdHK*!iz!<$M$x`XM<fU7t;`T2LbXyTYVF<>l-xvE%#l{FBnkAfAvPk6oC` zxxO46;5B~iO3ytpH*`aog|ge6e>PL(AxESu?iV3LMLdtR$=HMG%tN}}gCP`TIJ(6G zBZ<9lV&q-ho`zXdRccR4wl}FlpaP42M;r0Nu1#MNa=hMf*@C1nV3;r|%o%F&HBtP` zs#X5eeuIVz&)_`s8-(iR&%u>6%WhX_Idjuv@aX)_lNOx+95Y@KPKQ1dV-VNZFk8=h z!Xnu7Np@*P5H}%~>WWlszPpArH=kq2qxr!hi~Ndx?qTD$zgshc|1KnFS38N)v*ZLm zkvPM8-+xAV{Zi2}y6aJ9Oz!v@0#!}4rR3r>g~30dp22v{M&BvABLe^0`kU+Yo~J+A zQCd^l?)YfHwzV`cK2%(YmAi4AbU&SlL&7~af5O_e=HeULVYrKu=|a3Jgke!%BWc1? z@O`CTs0s}Taf=4<Tn?j*vFu2^`r4I_TV`E;Oo)oiSIS7QS~MLm;fpQ>`4jVoPrsCX zOBJmeQvH=jMkn`a-CZgyHq4M%p6^RbCFF&qky$zCpJE`ZE}>LYI0j*x|29v_%fOv( zC)MksgID|hh~G%9Q4FKY>U&SFdGXRGmX3HwdL$ype&6}{!d(X9z&fN&5mq*P$f#To zvbH>q6O%~T1U<XVXF-Xd8*5aj<$Z0|l00JM*EsHY!V13bAXURd7tP!KFIXG+Q!>9S zq)-^^eB!DQGLCsM`O2kR9O^g{THh?9ExtUK-0|GyO2gQ`oYk-}(!R_Taz7b%kh#ep z^I2Ow&tJcJ?-F&1DZI+Bw{f`AO3!URo(M^JR!R4po^TFaBbJ&8C7kp0E%;+XN=G&J z#E$uVOu9^tK>VEp#=;dxBW<fCq?~VD>P{uCgi<ry9&{y0(KWue5USO%kXI|Weu6k` z<!Q@Eq_$h9n6R{HJPvo+oVxtcE`QJA5X#5ykhbOizdkMe#&`1HJ`T}#&u(qB_|9qy zs<ydlH*(ZiYMEk!o=%40DUB0TFa8jIuT-|);&Hl&oa6l!intlY-G#r{M560PUf=J( z$2mmZIm)4-L($YpqhZE&wSld&tLF_5u4dkH*U$V@4L{#0hku)TQw(*SA?0Ilkdgjr z$2vnezVMNeBb?X$>`l}4Ce~T|dNIbOa%K{4;;4^yJq~!>ccG@;lL8cBrD+Z_r@xOE z6V=%1vg4+d-<>J>84XFf&}*=7*rqdWY`&!F`mA7*AKB6(x%$Kzl9^PS&*7cdUwuor za}y3O`u)I*By4_xP3JH_W!!5*S9WVo{PXiJ@8?#n1B851tW>IqZI*g@{Fe&cD<;RG zgMzC#4@Q;2;3Aw#w({o}sY&|y9oLsLuZd3t`eZELRe>%aNGn*P5<ek0{WY@D8?xy5 zGroM1;m0ET;_RH{L1R+2+7RDp{Pq}c6PJQ~nfl~tucC?wf^lPSLi?E`;z<^#!2%bE zr|;BM>*`6153FX7#r6_69Hw9Ol%93w${sD_UOUuKBo;E|M}H-)_g>K<{8VwNhc?w& z`>2hn&bDL|))RXiA2lK^bm8T{F?gSI*d}>7vXMk~c+iv9`;nc-U$?R5G!<PjJnpVZ zf+~G;_q@E}beAfXlbPA;qB1=PxLb?g1tk_psDVDcpH>nyKC2M!qBBjXG}O-K9W^SE zIi^~fXCZj9IJ%C6G>n)oxrC3H`mO|5d|c|X?vl$lQ`hC9EQUx|5<<do3;f~PfTR@- zyuN|<YAb7Rw<eKpZL<$Vk>!;Uyf)MUe(CAbLf}W|wX&@il8-<lsa#)~QIpV_w|_rh zuRjY-O;xR*!Jn%Pcyn>`B#1tZCHq(-KCOY>eA#cgldJFYZG38p8g)Z;`9!IjTzTtP zn(_}eS?HbVbBM!P%(R;8|K1I0@m~#w-7^{{<&qrlIVHY2MkYNbpwwf>tVZuOJSueX zrz5m?7?S((!FmIqh<GzcUFI)h5%Rihq2g;lW~_dL26g9|B=lV6D1%&ZqU=R;>>*4s z2{Fur3$VwmsauJeFvsB;<fIRNdf_Ra;=dBme5L29)8v9c^oT?ukFVB@F-1Zn-O~ZO zK=G_v4gJZ71nNp6l4fB;mgjW7XpXIsH%+GM$hF*eA5)v*RPQG<k+MxtQn5H0LJ-Nn zP;YZG%_yKhYIY^~=)<*f4u_S#X4XUOweG^F#s^Dd2h+}oJTWJO42BOWIH#Q$#IZjU zGa*~T0m^*aW$oDyaWELuSu`a*+6b*;1`pv9X_Wf*^9|eU37zgAIKu%d$@-d9-!Q6F zTqZygV(#HE9UmU-)hr&CSLd{Pk+jL^33x^*BPdE%M*T9~h?~xlLi=nQ=c2D%EtE;* zC)um+MXfUuaGskg9~e7&68h?~US1XPbh8HuSd<<ALG_cyTVn?uj?=bO(%BI-*|a*4 zRWIRxJdjJboTwl^(^X#*X-!lM+3#);9`6CW1Luv^bD4)7E;f~a4j<yq?R-{mJArTU zhU`#PG9JiZe*gEh`PdnA?V*$y63CSiO0cn%zw5i;NNq6{&<iTo_UtF_BN9tZlZ9eZ z9>486mM#;YY%|eb9>3@Av%q;xDUIHwp79M^t^;V@YGtMNtF=pLxLq)H7ol1Gqs)H& z_(uaq*8U~Zi4cK%S@*_m7>%iq^_`o%jMHZE`8`<Xj$2rbchm(tT$_G2^SYv72+_P> zM(23L$-JEAOJ>{wuXUlirVnKYXToIjdW(OI`iojl&UgR4GaT~5y;|qj^j|O&-LmRU zMO(%8qEWn~jwT|HmR*R;Te~Ae{H}}FCTo9TxX9^tG1breFCEeO=#<jj#21kB_3bw6 zQ@o(p&a|Lk_Jrz8S57fc9+MorZkWe%75_lb*Lm`gBcJc-ue0@K+y)hqiZarz(gvMq zb&^%7!|Mk};a1DOfaqvtcHj4?tG9n$FSK7pN=oc|cCA{}Ba>QiW>UV;qX9jGUkNq0 z9oBAksD#fPoPA1sI1SuLU=t`nDCA(j5X2Uy&(S42rbZX@ur~MfBMnXM@;v_sAfaz% z_q^&P?i~S7X;+v)$ax8*7et=4WCZMMt#&eYZcOR_ym~Xbkv!+lQT^%M4qdv0ym~wX z%eL589;pr1x1L!<jNS(+t5A_DixSw>d=X)^^T*F>P~ls55m=2VNMf91#C>8J=xS$X z^(g*#<Z+6gw{E<u5lz39#p+43y27JQ;B|?6clNN!OSqQVMP8~6abXj+`db%wI@+-y zRVdYz+TEenXFBnSi1S1_nQeOVpGPBUMO5v0MQ-iZt+#IQcQL5YkO?-L@g>wNQLNvQ z@}Hy&b~UUaG*+UGh=AD6=2{eG8umZL({kd?U4L!fZ{_;)TUmJ4<NDQaQVS#ev@G;X zqKgAgbbVawv`$u;lp}BR{xbifNK=Rw*3ANuX0Q!DhpGudG#a?xHKm^zp%W<dB5IZt zah)F=tL^y3BUHbZbHQ@f(a=#X=k4s^8~YP8Z(c%59(-?dTueO4Fx38+Yy4s*z0}#Y z-j@_gp5%FKK@1<+emO-A-z4JEeDTilB_m`g&sko%P>dy#m)o^Nj80E`K1NW9@?m6- zQ<ImELqhQntC3N!caVJ-Sok|=iR&{y&G=P`EeW5#ZS_(|QIrB-Fk6TJ6d~teV&8{T zI89!BccbaLC0D&8g;LVqH9)^7b!j{Jz8SoioKZvIaQt_>tmTD2F<*w;FAp<xNu2x1 zn|-s-5Om*2Zy(9p0h2|V{KM&z;zgJAm%I*g83z!`bkPbisK+|C*)KkEpKk5bzn(la zxcH5j;T4glTT)V`{ZK%vIIq#FIYR*1`5*7p0waywO(gG@=Y`&d(6!8-GCUJGnLeOr z=IZ}ssg&IJ!U-`?!76U!{C-@}4edUS2rr`kB&Bf5Qmz>Dp9^V&L-wjg4MP%jRG&u& zGEydcv)xZwffcPuBG>U<bfA|=#B;?t?+?l@J?!oLY9Yo&eR!Chp{M5JlO-Np+r^Fu z348MipO=(}xG&m#)Xd@xhzg;%y%9Y|-k0`G!l;5);`L~te#YXyD|bN$EQU!zVa@v( zD9(Adwi<S`(EY&In<Oucog{|Hb#$NpbOSeyYu<mf|07+WuH>_kTI+oGMjL_Ww*#Q8 z?M7nq`$%JJudC1!_gU@J2ni!MPOxvVllwv#K<t=f9v*~uAToy0Bl3cTYEfQtZ5<OR zZRjAGXcuW(PHpnBF)iY`_P2p*;fC%dXI<*e6UA60_xG&b>Ll<w$gW^{hr7wjW%`+L zp6J<%5%uC>kn6MZ;!Gz7mieD6mg7dEO$^>)&8ngY4-+$UciTJe@3ZX~uasklJ=e0g ziBd}JYu(gffmt-3m$}gdalEDYp_GFy7Ebun+V<b5zfpsr>HU|*TWvsT^qI2Fu}4Oh zwU+0=t;hS~f1L8@rXG1kchO>e(4)k97WUK|CP-+}VhM$U01Nr2Gn1mjWTb=onyJ@e zD<R&Y>7Oe%6ZTIs^;?iV6|A?rz2?gY>X2C~ceMZBGt<rE+Ql0{{A^N#f8}v|vPhQ| zL*r`p7X#^rT+@X7hDqn?A_|c90LwS9Tu-zmW%n0D$AGk`Pal@Svr`fE4+j)Q@TQfL zFHU^ya9f$ZS@)(>;iQG${re;A;o{wC0tU9Ps7Ji~bw&&>c(%J<2?yuoNa=nK@=SEm zNeCCadWgY^)<meUx2^vxqz+u*v`}IWN)9iikkd|ZwrdL$O2#p<kf)AZo;`l&_*#}k zcR7g4+H5t7>lEmrz05$`2hXrUg|qi`ep-tjE-UO1U@$4|^qU(wENSN3epfr3w&`^~ zTh98geOdTwX;)^HrD|)cT4%b+JlaKVx>BKG<XKtGw?re(f%3TrWG=3*fQ33evI=rf zn6zK*?HcET#2XS=@=Y-{9g}+*wLi<<h&+{|21@dK5gH@fOF|{w+f(O~yM8pN<2d(m z3h1N3-;C${^W}f#LUX==JL@GiEgt+jGfMXGHtnHs2T42?8J>^Ueu_^r+IX+cFm#m8 z^b;1oG?$ei6uLdpqU`!mH09UB?@5#ia_I~_4{S&ZLPvFkRoUEV>a#mX_1K>z#kjI7 zW&M(B4vJ;U)s6%4#C^L}z?EX5)7vVk)t$%AD%1vQF7eG#$fdY8X_aSektd#|-T17Z zuN<^$?QPi^iVv#oxu2ZgypPAbqfW4K@mi)?(DgVq%F6B~O&wdo{TV{$_OD&@+}=CN zi9Zd;(X%nrUgK(})qDM(`SHc)k4CugD-3^(-|3Z(j-$3-Y~|s@yTKWd@~QKtLD6wN zU!k935vEVW4%8>vUCS;}XuhA6Zo@6C-C@vNwvzex{bGH*zS!fxrpu2?123BX9{w<_ z^|am|^r~RA<7Oheu-B*D(yAp&;b~zp*`60Tm%7a)t_2>7TU4_7eXi&C^N*(kXJn~` zm-0%4B<~Tb<cVZ{<f-~K720#TR4S@OEjMmOI~gacj%)F-YxT*R8X}uUZOD3<Z+Oj6 zLdfem*L9|)_2Oro#c@xi){1AM*YT6TDuC6jAKW!JI%H;X$HvqE%*6$}NMs5&G}k{W zWnpS+pl=O(%eTQvE5w&h>YLuOvVvV%vV&i5!V%Eq?@hxI#lgkFeMJVp-gHo10|u~o zpe}sJ!UhHt{m1J4TMuAgLr1?6*$vv@0UV862p=2&7h-G>eC#(L;b>BImD?uUaIwL{ z1(yqM50?bRCGID7Z*0H-7b7&Z<!;bOE;vzeG>$TgOZ<M!TsZ!ZxWp%7^9F7eQ(WGi z+zlFtiw2wvo)(JByO5o@V2cY56frlssJJAIWA|n=E@<e)zTKdKxTGV-z6Bfm0}dj` zAa^lw;f7sSb-<EiIMG||A?hWDZf9?hR)M&nq0@<Xg9hSK03RE_2gM~l9y1s0wU5gU zw}+^g56Ifjyp7EpxK*@z0niTucY_Av5{Z~cB@~yBuXf^sHOEjo26z*bi`|>e9798A zaqR~EpL)?mamh-;%w-p~kIHy}OAgH3PMXXO`-{-cxS*q<yFp`$%cqH*xa>VHxrje$ z#oS-uTrkHa4-Y#R8!*5HRr~44+E2w;FFbpz{d8o#q+Q?H8>CgV+TTFW?*<KULG>4a zOMxk-xbW<)?*rZxl4A2_OYNiTWdrTL8}xtb1>Ro(E`<v_alx8nsEh}^DOSer&1R0F zp+8IS28}5$UxYDp*+uQ6&Fq(UY~F0CeKd55>Tb|LTu|#VATA}9J8{7lmo4itATDKN z*uB||3u-;Kf%e=D8i)&8?dREJ;==oX)qei@oxMR?#Z>$G-*$ukPrbnV3lNt|Cd^#0 z_x&yFF<@rDA?60NCi5Z`1>k~KFID%kae?~;#0Ay&Gmx5GZL|{?Y&o`N2a<u*<QmwT zW~W{W=dzh&sCwBzJMIPz#09k;1Gv;oW9EXr_Hj8k*JFS;b*b3A*;4zcdI8WMU~i4s zc_!d!OmV6IwG)@U$ECp;yEmBQ(lEOlG{6N_`#@YO7%*|++gt4eGh5NHvo}bqXtfWZ zzgp}D4RAsAeSpi44$NG3QTu2a|NR~|Z?^6G0GB2#%E5<F4xnC8{RQCC^mivN*m7)3 ze*wH{J&c{pW{#oNOH0ab(3s-#Qx-E9?6r@sV?X<`d9$VV(W2U>vl}!J7u0?~6RF8< zKX&4REiPO7OD0m2JI-PEW-~6R{sN#oPwoZ{#06FR0GIE7Fmd7ETkQkhG-BE1BCVp; zK7jtgxic3e8i)(3zW`i%Fz#RYcd_Q*(q8~?eq-71Bdr2lQ1t?!yJ)a+*|zTkT>b=M zjtjg)@A}*l@TQjoyEmIThN_nhG~!)Km{&mXnb_D@;v(w>z8(X(^kM8T{D_@77A|NR z-*1DB%a+<lLk}Q!hC6ZDw(kR62K6z=1->)K!Ub(+hhXM*T7|%82y<M9>~@33RQp{n zn79c1U$x)OzOy$-tC(uPyKXmVfD5Yc192IJADM4^HZHJ>+TYS&fQ%nPoa<oD5~Njt z3tCji4r1eis(qkd;QRfL$o?|+bSExYa}1U7ACdiKybHTGn>mJto@m+)8Z9mWm&t0( zT=vxW0WN<L=hHjIWlQa&;<ADMg^dfU_P1Pr0k}*d&a<|={vv>=(w%F6%Xu>3%?y^^ z6w)eMf7w7|S<NBQXtfV;88*NW7w$`Yt9`(m5$YXh7O;v|`y1%2-Jp?Nw)7W(3lwpR zvrSxJqwZqOkCyQZ@348ZWzCO<UX0oe8i)&OzYlPkO@pH`alu;qXc@n>^DY-LLz}ga zhF;$JYA1jO;(}U_0bG_nFmu6P`&;%eK$ff`)^`|dALh-L+DEO&Hqc_&;)1Gu9JIKs zwe7@Z?{QhT#qJH}xUBDdg%B|l8|yI~)S4fN%bX%6E*$???a!_5%mrx`Q|-@3>;?_g z3##uUx!~d4!OUeBwf}F%<KgYRw&dnOC<<ENN9GtF-p(hu02-(l)P6q;(f9H2F|92T zIBYq#rCzcSeIJhyv5vr8FK{lKIfkm24fIKDaY3#5w_M)`xDe%GjtlnM-?AP9ydl1U z&6_Q?kBaIBx_&okATFr=KEP$4%uZae#bwKWAMl0*YsPO`^Ft9=g5h@u0BF*kci6C8 zfun(XK|~dYPE(dy4Mz!f4+-9WuYb6Z8(Q2og&lP2!;)d=1q`gz;D^WO5XOWM#-_HN z)~zOiN<o8?UBe0mD8wR0)j&}o%f(260~-Y&gfUeV1<LK$`6vn;8yED^pi~Z6C~#~< z0gb|8B8(I`_e|k%A{IY5F;Y0fgB6M?k&b-9NP!C*1+;9SX2IeI*9HZ2s1H^sCJNO5 zp0A>!&b?;}G}~Y7-f9w4Ezsm+p}>tXk&ga*w1OIs2O9;ngg#n=#Sfm1D4;=Um9avR zQNTM5ufT&~L<K6qMT8b{1$WDemuyp!u;|h4EqWvXH+5k9V_lR%)4OIcKXFj1XpK}w z`Zc)wTYNxHqYe1>UR2Q<2ce3`5L+v>w!76NS|)5jx4-VW6^f>c0EOeM7%A*Q6#+kv zZ+`^`<;OOv2tZG4AA_w>fC8ek;ab5m0ibXKei*iW6tJpd5F!^e5ylWvVCaDx+aU^@ zs)z<XX@wPvCMf^~Mq-Q<_MnQuR5F@l@naiR+<-P>g<_&`iWeh=J*Xn!$EipxesFI{ z3QE`kC=)JLD5gYW`uA`Hm3>%M5iJ`|V@v2wRYZp(8q5wk$O8yF9wR~(;p-A>9E2*K zdHhclkRk?Gq+P3`H4Z`*ncr?Zktl<hRFP%-WH$Z3RFP$;WJa3Aq>3y%B{LEVWCCiT z1=QoY?UNY}+Pf;AL!1F%2q)4cKmpYsfn+|vQ%^ubF(vbP#3CE5H^D)BSH%kmiQRz$ zW>viCv~3z8OAF}@CP`sM9EWd9VehKQy8Sf|lpmN?kri=szz*!WHWpfFD+D&g>Cv_n z_O6O-+aF}1C}37a_U-RO+zJJvumG17Tx(Q&Wk*!twiNcRikA$rWCLbZytITBilne* ztq7<hhaVieg~G<tZttqdm9ecCD1&IKxQtvYawE>&xA=gZdmFIzUe=1s$h9K(_Ll&* znnY8@4d~7b9Jn_?CZJRipui*b&orXO+k+|se((%zJCR#WqNySP<waby!%&a#3EkX5 zqt=Q51zt=mcI&;UBH#z#92N@Os3HL6e~uN3riuUsf#VpXum@EH{18B_7j~G+ZB%gs zx>HYpM`0UP1Skmpdy<Tb`W{pf@Ix>Y%T#WoiU3rI1}hYZ0$L&oAu4d&a^8a~0)7Z% zOXzJ>5rB#yj)Zmy;5KW;W#n2>B<!C=+F06I?|!Ygj9e><ZGRMjGWf45!VYc_=ZfOn zC$s6ER1szjv3C>SDVdQb|5e3J=uXLug#N3F8x$nAPi8o1_o}$zhs5L#8HqIcuPSap zC3orxNa(-Gyg@+<(~5mBs<`2Y6hdM#WF*`VbXDAdUVe#%J?BPm+FWRDP>??RPckDV z1wP*HRdK@)X<saUpsV60bmz0}?No7tf(+tp8%<K+6n3wQ8-B<XV4;AniW^YblUSiZ zB5ke}Hz>&dyZuB(VfU)I;fLJLI|pkpx+-o!<q<oF9dd9}6*tcnVNk>lT0RcWeoGWK zmUg>WMVKkX4qBmd+lfRO{8tq(1S3^Z5%E_LTYT8)kA{0#E5eK+R8jH8wq9&C`L8N& zLU&%^z`X%7VY41LC@5+EGmWV6u&Uzbna+kEN}sV%*gRK6gDRiK3jH^kHz+7S!$@KG zJLnAxDsEW(Kv%^LC}{gTc2E<#p^CVOWX4m`l--~JO29|~t18AJjz;eyj3E+9wFV1? zO;tpLUcHYMiYbxQFzuTR_n?X!Q>nI7kF){9O=7AAwVn4(a43=jdKA<#?VAi?5{Bhi z4s9y08DW{qO;tpLYV5}fMN&YWE5arYp^6&+-e0t_v@^s;VDoAV%oIWuZ!~P{1<D|r zD!xLhq9!LCh^C6LhcWC$6<;A$QS<e-ULcDKX%a0HHlW+zx3U$AmdpSJty>r=>_HU) zKeV=QFDO5@QAGf%eGv=$%^mb+i@yL+&<?;zVGpVZ_@RT4Sd4wzaGSLv%nyVb=^n#E zVH;HhDCmB`NMR4E2>7AbgvF0-R1tuJA7X`KN~D_z_S@FN?$?SNQ+X3{!`%*1*hUor z3O5I_Q20j`L9liLMB$bK#whGT6#+kPTVU~H8&w3L`czn<fGVDbcSQI#Xcz^AD(dI_ zLt$fSw+B^xg;Yg@j%~d_8APh$u?uh?SW6K;@WL-%8;ZbzMC!5+ZrwNDSQGr`O$M;{ zAE?1m?13oMBjasRTV8>UgTt^7j^f-hRs>?3k2V%3>c!bOI0Oc86j3Z{to+h#$Ku+G zBC>>|c+@sg_-`e)MR9LMF$}^{Tv8}ha>lkOo~@{3h&XPUL;Pw)Gx!g$f8GWGo5LRX zSeNioV+|IfjRi;jcMdPYQJgcIC<45QZBhT7LsK}4C=-QZL})#lhda+9!CCHY$NKLa zP9f%y2{qP5#Dzb!vHmlMsCyygHIUu;KoNW_G)1!6(;Z?kV{mcDA-04Y!=Ok=lmB*S z0E$O`1}hX0)y?y5fC3J2Ge!znyYuFyyA2Bab`p0uh0X5#FBFe_=fhz*6axjkgWI<@ z_;^^mGnyZV)UZtD=AH!|y7Ouo?#;igZ6gXKu++zpNLah`9e6IoZ#4v>fa|r>&35ov z+3e2$Lh&e$U}2Abz70^oqr8Wa!tT2>KmqSF3l=}nyYnX02P+g)B7OdgkpkB4jFt^w z0<rk9*`3j#B@9@hND8Pu3yeLYJC`K?Lt$f!p}*@r3(ORJ&w?wq{R47P2I1ZLf3u%k AQUCw| literal 0 HcmV?d00001 diff --git a/bu2kdarkscalar_darkscalar2hh/velo.C b/bu2kdarkscalar_darkscalar2hh/velo.C new file mode 100644 index 0000000000..a8840d4b3e --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/velo.C @@ -0,0 +1,782 @@ +// Written by Philip Ilten and Mike Williams, 20/06/2017. +// Toolset to check if a secondary vertex is consistent with having +// originated from the VELO material. See README for an overview. +#include "velo.h" + +//============================================================================= +// ModuleMaterial::Sensor class. + +// Default constructor. +ModuleMaterial::Sensor::Sensor() {;} + +// Primary constructor. +ModuleMaterial::Sensor::Sensor(TFile &file, string mod, string sns) : + z(sns == "0" ? 4 : 7), half(0) { + TObjString *exp; TVectorD *par; + par = (TVectorD*)file.Get((mod + "_a_z_par").c_str()); + if (!par || par->GetNrows() < z + 1) return; + z = (*par)[z]; + exp = (TObjString*)file.Get((mod + "_" + sns + "_l_fnc").c_str()); + par = (TVectorD*)file.Get((mod + "_" + sns + "_l_par").c_str()); + if (!exp || !par) return; + fncl = TF1(mod.c_str(), exp->GetString().Data(), -100, 100); + for (int idx = 0; idx < par->GetNrows(); ++idx) + fncl.SetParameter(idx, (*par)[idx]); + exp = (TObjString*)file.Get((mod + "_" + sns + "_u_fnc").c_str()); + par = (TVectorD*)file.Get((mod + "_" + sns + "_u_par").c_str()); + if (!exp || !par) return; + fncu = TF1(mod.c_str(), exp->GetString().Data(), -100, 100); + for (int idx = 0; idx < par->GetNrows(); ++idx) + fncu.SetParameter(idx, (*par)[idx]); + half = fncl.GetNpar() > fncu.GetNpar() ? 1 : -1; +}; + +// Operator for sorting by the given z-value. +bool ModuleMaterial::Sensor::operator<(const Sensor& sns) const { + return z < sns.z;} + +// Configure the tool. +void ModuleMaterial::Sensor::config(double xc, double yc, bool rel) { + if (rel) {yc = fncl.GetParameter(0) + yc; xc = fncl.GetParameter(1) + xc;} + fncl.SetParameter(0, yc); fncl.SetParameter(1, xc); + fncu.SetParameter(0, yc); fncu.SetParameter(1, xc); +} + +// Configure the tool. +void ModuleMaterial::Sensor::config(const TF1 &fnc) { + if (half > 0) fncl = TF1(fnc); + else fncu = TF1(fnc); +} + +// Return if an x,y-point falls within a module sensor. +bool ModuleMaterial::Sensor::inside(double x, double y) { + if (x < fncl.Eval(y)) return false; + if (x > fncu.Eval(y)) return false; + return true; +} + +// Return the x-position for the edge of the sensor. +double ModuleMaterial::Sensor::x(double y, int edge) { + if (edge == 0) return (half > 0 ? fncl : fncu).Eval(y); + else return (edge == -1 ? fncl : fncu).Eval(y); +} + +// Return a parameter for the sensor function. +double ModuleMaterial::Sensor::p(unsigned int par, int edge) { + if (edge == 0) return (half > 0 ? fncl : fncu).GetParameter(par); + else return (edge == -1 ? fncl : fncu).GetParameter(par); +} + +//============================================================================= +// ModuleMaterial::Integral class. + +// Return the probability integral for a given half. +double ModuleMaterial::Integral::integral(int half) { + vector<double> xy0(2, -sigma), xy1(2, sigma); + double pre(0), vzmin(vz - abs(sigma*dvz)), vzmax(vz + abs(sigma*dvz)); + double dmin(numeric_limits<double>::infinity()); + geo = 0; + for (int sns = 0; sns < (int)mzs->size(); ++sns) { + if (mzs->at(sns).half != half) continue; + double mz(mzs->at(sns).z); + if (abs(mz - vz) < dmin) {dmin = abs(vz - mz); geo = &mzs->at(sns);} + if (vzmax < mz) break; + if (vzmin > mz) continue; + pre += ((TMath::Erf(sqrt(0.5)*(vz - (mz - width/2))/dvz) - + TMath::Erf(sqrt(0.5)*(vz - (mz + width/2))/dvz))/2); + } + if (!geo) return 0; + else return pre*inr->Integral(&xy0[0], &xy1[0]); +} + +// Constructor. +ModuleMaterial::Integral::Integral(vector<Sensor> *mzsIn) : + mzs(mzsIn), fnc("", this, -100, 100, -100, 100, 0, ""), wrp(fnc), inr(0) {;} + +// Destructor. +ModuleMaterial::Integral::~Integral() {if (inr) delete inr;} + +// Return the probability function for a given half. +double ModuleMaterial::Integral::operator()(double *t, double *) { + double sx(t[0]), sy(t[1]); + if (geo->inside(sx*dvx + vx, sy*dvy + vy)) + return TMath::Gaus(sx, 0, 1, true)*TMath::Gaus(sy, 0, 1, true); + else return 0; +} + +// Configure the tool. +void ModuleMaterial::Integral::config +(double dvminIn, double sigmaIn, double widthIn, double tolIn, + ROOT::Math::IntegrationMultiDim::Type algIn) { + dvmin = abs(dvminIn); sigma = abs(sigmaIn); + width = abs(widthIn); tol = abs(tolIn); + if (alg != algIn || !inr) { + if (inr) delete inr; + alg = algIn; inr = new ROOT::Math::IntegratorMultiDim(alg); + inr->SetFunction(wrp); + } + inr->SetRelTolerance(tol); +} + +// Return the probability integral. +double ModuleMaterial::Integral::integral +(double vxIn, double vyIn, double vzIn, + double dvxIn, double dvyIn, double dvzIn, int halfIn) { + vx = vxIn; vy = vyIn; vz = vzIn; + dvx = abs(dvxIn); dvy = abs(dvyIn), dvz = abs(dvzIn); + dvx = dvx > dvmin ? dvx : dvmin; + dvy = dvy > dvmin ? dvy : dvmin; + dvz = dvz > dvmin ? dvz : dvmin; + if (dvx == 0 || dvy == 0 || dvz == 0) return 0; + double ingr(0); + if (halfIn == 0 || halfIn == -1) ingr += integral(-1); + if (halfIn == 0 || halfIn == -1) ingr += integral( 1); + return ingr > 1 ? 1 : ingr; +} + +//============================================================================= +// ModuleMaterial::Distance class. + +// Return the distance for a given half. +void ModuleMaterial::Distance::distance(int half, TLorentzVector &s) { + geo = 0; + for (int sns = 0; sns < (int)mzs->size(); ++sns) { + if (mzs->at(sns).half != half) continue; + if (abs(mzs->at(sns).z - vz) < s.T()) { + geo = &mzs->at(sns); + s.SetXYZT(0, 0, mzs->at(sns).z, abs(mzs->at(sns).z - vz)); + } + } + double x(vx), y(vy), z(s.Z()), ymin, ymax; + if (geo && !geo->inside(vx, vy)) { + fnc.Update(); + ymin = (geo->half > 0 ? geo->fncl : geo->fncu).GetParameter(0); + ymax = (geo->half < 0 ? geo->fncl : geo->fncu).GetParameter(2); + if (y < ymin) {ymax = -ymax; swap(ymin, ymax);} + y = fnc.GetMinimumX(ymin, ymax, tol, itr, logt); + x = geo->x(y, 0); + } + s.SetXYZT(x, y, z, 0); + x = (x - vx)/dvx; y = (y - vy)/dvy; z = (z - vz)/dvz; + s.SetT(sqrt(x*x + y*y + z*z)); +} + +// Default constructor. +ModuleMaterial::Distance::Distance(vector<Sensor> *mzsIn) : + mzs(mzsIn), fnc("", this, -50, 50, 0, "") {fnc.SetNpx(1000);} + +// Return the transverse distance. +double ModuleMaterial::Distance::operator()(double *t, double *) { + double x((geo->x(t[0], 0) - vx)/dvx), y((t[0] - vy)/dvy); + return sqrt(x*x + y*y); +} + +// Configure the tool. +void ModuleMaterial::Distance::config +(double dvminIn, double tolIn, int itrIn, bool logtIn) { + dvmin = abs(dvminIn); tol = abs(tolIn); itr = abs(itrIn); logt = logtIn; +} + +// Return the distance. +TLorentzVector ModuleMaterial::Distance::distance +(double vxIn, double vyIn, double vzIn, double dvxIn, double dvyIn, + double dvzIn, int halfIn) { + vx = vxIn; vy = vyIn; vz = vzIn; + dvx = abs(dvx); dvy = abs(dvy), dvz = abs(dvz); + dvx = dvxIn > dvmin ? dvxIn : dvmin; + dvy = dvyIn > dvmin ? dvyIn : dvmin; + dvz = dvzIn > dvmin ? dvzIn : dvmin; + TLorentzVector sl(0, 0, 0, numeric_limits<double>::infinity()), + su(0, 0, 0, numeric_limits<double>::infinity()); + if (dvx == 0 || dvy == 0 || dvz == 0) return sl; + if (halfIn == 0 || halfIn == -1) distance(-1, sl); + if (halfIn == 0 || halfIn == 1) distance( 1, su); + return sl.T() < su.T() ? sl : su; +} + +//============================================================================= +// ModuleMaterial class. + +// Constructor. +ModuleMaterial::ModuleMaterial(string pars) : ingr(&mzs), dist(&mzs) { + configIntegral(); configDistance(); + TFile file(pars.c_str()); + TList *keys = file.GetListOfKeys(); + for (TObjLink *lnk = keys->FirstLink(); lnk; lnk = lnk->Next()) { + TKey *key((TKey*)lnk->GetObject()); + string name(key->GetName()), mod, sns; + if (name.find("module") == string::npos) continue; + if (name.find("_l_fnc") == string::npos) continue; + mod = name.substr(0, name.length() - 8); + sns = name.substr(name.length() - 7, 1); + mzs.push_back(Sensor(file, mod, sns)); + } + file.Close(); + sort(mzs.begin(), mzs.end()); +} + +// Return the index for the sensor nearest a given z-position. +int ModuleMaterial::sensor(double z, int half) { + int idx(0); double dmin(numeric_limits<double>::infinity()); + for (int sns = 0; sns < (int)mzs.size(); ++sns) { + if (half != 0 && mzs[sns].half != half) continue; + if (abs(mzs[sns].z - z) < dmin) {idx = sns; dmin = abs(mzs[sns].z - z);} + } + return idx; +} + +// Return if an x,y-point falls within a module sensor. +bool ModuleMaterial::inside(unsigned int idx, double x, double y) { + if (idx > mzs.size()) return false; + return mzs[idx].inside(x, y); +} + +// Return the x-position for the edge of a module sensor. +double ModuleMaterial::x(unsigned int idx, double y, int edge) { + if (idx > mzs.size()) return 0; + else return mzs[idx].x(y, edge); +} + +// Return the z-position for a module sensor. +double ModuleMaterial::z(unsigned int idx) { + if (idx > mzs.size()) return 0; + else return mzs[idx].z; +} + +// Return a parameter for a module sensor function. +double ModuleMaterial::p(unsigned int idx, int par, int edge) { + if (idx > mzs.size()) return 0; + else return mzs[idx].p(par, edge); +} + +// Configure the technical sensor parameters. +void ModuleMaterial::configSensor(double xc, double yc, bool rel, int half) { + for (int sns = 0; sns < (int)mzs.size(); ++sns) { + if (half != 0 && mzs[sns].half != half) continue; + mzs[sns].config(xc, yc, rel); + } +} + +// Configure the technical sensor parameters. +void ModuleMaterial::configSensor(FoilMaterial &foil) { + for (int sns = 0; sns < (int)mzs.size(); ++sns) { + vector<FoilMaterial::Segment> *fzs = + mzs[sns].half < 0 ? &foil.flzs : &foil.fuzs; + for (int seg = 0; seg < (int)fzs->size(); ++seg) + if ((*fzs)[seg].valid(mzs[sns].z)) { + (*fzs)[seg].x(0, mzs[sns].z); mzs[sns].config((*fzs)[seg].fnc); break;} + } +} + +// Configure the technical probability integral parameters. +void ModuleMaterial::configIntegral +(double dvmin, double sigma, double width, double tol, + ROOT::Math::IntegrationMultiDim::Type alg) { + ingr.config(dvmin, sigma, width, tol, alg); +} + +// Configure the technical distance parameters. +void ModuleMaterial::configDistance +(double dvmin, double tol, int itr, bool logt) { + dist.config(dvmin, tol, itr, logt); +} + +// Return the probability integral. +double ModuleMaterial::integral(double vx, double vy, double vz, + double dvx, double dvy, double dvz, int half) { + return ingr.integral(vx, vy, vz, dvx, dvy, dvz, half); +} + +// Return the intersection. +TLorentzVector ModuleMaterial::intersect(double vx, double vy, double vz, + double fx, double fy, double fz, + int half) { + TLorentzVector s(0, 0, 0, 0); + if (fz == 0) return s; + double f(sqrt(fx*fx + fy*fy + fz*fz)); f = f == 0 ? 1 : f; + fx /= f; fy /= f; fz /= f; + for (int sns = 0; sns < (int)mzs.size(); ++sns) { + if (half != 0 && mzs[sns].half != half) continue; + double mz(mzs[sns].z), t((mz - vz)/fz); + if (t > 0 && mzs[sns].inside(vx + fx*t, vy + fy*t) && + (s.T() == 0 || t < abs(s.T()))) + s.SetXYZT(vx + fx*t, vy + fy*t, mz, mzs[sns].half*t); + } + if (s.T() != 0) s.SetT(s.T() > 0 ? 1 : -1); + return s; +} + +// Return the distance. +TLorentzVector ModuleMaterial::distance(double vx, double vy, double vz, + double dvx, double dvy, double dvz, + int half) { + return dist.distance(vx, vy, vz, dvx, dvy, dvz, half); +} + +//============================================================================= +// FoilMaterial::Segment class. + +// Default constructor. +FoilMaterial::Segment::Segment() {;} + +// Primary constructor. +FoilMaterial::Segment::Segment(TFile &file, string pre) : zmin(-1), zmax(-1) { + TObjString *exp((TObjString*)file.Get((pre + "_fnc").c_str())); + TVectorD *lim((TVectorD*)file.Get((pre + "_lim").c_str())), *par(0); + TGraph *spl(0); + if (!exp || !lim || lim->GetNrows() != 2) return; + zmin = (*lim)[0]; zmax = (*lim)[1]; + fnc = TF1(pre.c_str(), exp->GetString().Data(), -100, 100); pre += "_"; + for (int idx = 0; idx < 6; ++idx) { + stringstream stridx; stridx << idx; exp = 0; par = 0; + exp = (TObjString*)file.Get((pre + stridx.str() + "_fnc").c_str()); + par = (TVectorD*)file.Get((pre + stridx.str() + "_par").c_str()); + spl = (TGraph*)file.Get((pre.substr(0, pre.size() - 2) + "a_" + + stridx.str() + "_spl").c_str()); + if (!exp || !par) return; + fncs.push_back(TF1((pre + stridx.str()).c_str(), + exp->GetString().Data(), zmin, zmax)); + if (spl) + spls.push_back(TSpline3((pre + stridx.str() + "_par").c_str(), spl)); + else + spls.push_back(TSpline3()); + mths.push_back(0); + for (int jdx = 0; jdx < par->GetNrows(); ++jdx) + fncs[idx].SetParameter(jdx, (*par)[jdx]); + } +}; + +// Configure the tool. +void FoilMaterial::Segment::config(int par, int mth) { + if (abs(par) < (int)fncs.size()) mths[par] = mth; +} + +// Return if a z-position is in the segment. +bool FoilMaterial::Segment::valid(double &z) {return z >= zmin && z < zmax;} + +// Return the x-position for the segment. +double FoilMaterial::Segment::x(double y, double z) { + for (int idx = 0; idx < (int)fncs.size(); ++idx) { + if (mths[idx] == 1 && z < spls[idx].GetXmax() && z > spls[idx].GetXmin()) + fnc.SetParameter(idx, spls[idx].Eval(z)); + else fnc.SetParameter(idx, fncs[idx].Eval(z)); + } + return fnc.Eval(y); +} + +// Return a parameter for the segment function. +double FoilMaterial::Segment::p(unsigned int par, double z) { + if (par >= fncs.size()) return 0; + if (mths[par] == 1 && z < spls[par].GetXmax() && z > spls[par].GetXmin()) + return spls[par].Eval(z); + return fncs[par].Eval(z); +} + +//============================================================================= +// FoilMaterial::Integral class. + +// Return the probability integral in one dimension. +double FoilMaterial::Integral::integral +(double v, double dv, double t, double dt) { + return (TMath::Erf(sqrt(0.5)*(v - (t - dt/2))/dv) - + TMath::Erf(sqrt(0.5)*(v - (t + dt/2))/dv))/2; +} + +// Default constructor. +FoilMaterial::Integral::Integral(FoilMaterial *foilIn) : + foil(foilIn), fnc("", this, -100, 100, -100, 100, 0, ""), wrp(fnc), inr(0) {;} + +// Destructor. +FoilMaterial::Integral::~Integral() {if (inr) delete inr;} + +// Return the probability function for a given half. +double FoilMaterial::Integral::operator()(double *t, double *) { + double sy(t[0]), sz(t[1]); + double y(vy + sy*dvy), z(vz + sz*dvz), xf(foil->x(y, z, half)); + return integral(vx, dvx, xf, width)* + TMath::Gaus(sy, 0, 1, true)*TMath::Gaus(sz, 0, 1, true); +} + +// Configure the tool. +void FoilMaterial::Integral::config +(double dvminIn, double sigmaIn, double stepIn, double widthIn, double tolIn, + ROOT::Math::IntegrationMultiDim::Type algIn) { + dvmin = abs(dvminIn); sigma = abs(sigmaIn); step = abs(stepIn); + width = abs(widthIn); tol = abs(tolIn); + if (alg != algIn || !inr) { + if (inr) delete inr; + alg = algIn; inr = new ROOT::Math::IntegratorMultiDim(alg); + inr->SetFunction(wrp); + } + inr->SetRelTolerance(tol); +} + +// Return the proibability integral. +double FoilMaterial::Integral::integral(double vxIn, double vyIn, double vzIn, + double dvxIn, double dvyIn, double dvzIn, + int method) { + vx = vxIn; vy = vyIn; vz = vzIn; + dvx = abs(dvxIn); dvy = abs(dvyIn); dvz = abs(dvzIn); + dvx = dvx > dvmin ? dvx : dvmin; dvy = dvy > dvmin ? dvy : dvmin; + dvz = dvz > dvmin ? dvz : dvmin; + double ingr(0); + if (method == 0) { + vector<double> yz0(2, -sigma), yz1(2, sigma); + for (int z0 = -sigma; z0 < sigma; ++z0) { + yz0[1] = z0; yz1[1] = z0 + step; + half = -1; ingr += inr->Integral(&yz0[0], &yz1[0]); + half = 1; ingr += inr->Integral(&yz0[0], &yz1[0]); + } + } else if (method == 1) { + int yn(2*ceil(sigma*dvy/step)+1), zn(2*ceil(sigma*dvz/step)+1); + double ymin(vy-(yn-1)/2*step), zmin(vz-(zn-1)/2*step), + yds[yn], zd, y, z(zmin); + for (int yi = 0; yi < yn; ++yi) + yds[yi] = integral(vy, dvy, ymin + yi*step, step); + for (int zi = 0; zi < zn; ++zi) { + y = ymin; zd = integral(vz, dvz, z, step); + for (int yi = 0; yi < yn && zd != 0; ++yi) { + if (yds[yi] != 0) { + ingr += zd*yds[yi]*(integral(vx, dvx, foil->x(y, z, -1), width) + + integral(vx, dvx, foil->x(y, z, 1), width)); + } y += step; + } z += step; + } + } else if (method == 2) { + ingr = TMath::Gaus(foil->x(vy, vz, -1)/dvx, 0, 1, true) + + TMath::Gaus(foil->x(vy, vz, 1)/dvx, 0, 1, true); + } + return ingr > 1 ? 1 : ingr; +} + +//============================================================================= +// FoilMaterial::Intersect class. + +// Return the intersection for a given half. +TLorentzVector FoilMaterial::Intersect::intersect(int halfIn) { + half = halfIn; + double txmin(((half > 0 ? -5 : -15) - vx)), + txmax(((half > 0 ? 15 : 5) - vx)), tymin((-50 - vy)), tymax((50 - vy)), + tzmin((-316 - vz)), tzmax((550 - vz)), tmin(0); + if (fx != 0 && txmax/fx < txmin/fx) swap(txmin, txmax); + if (fy != 0 && tymax/fy < tymin/fy) swap(tymin, tymax); + if (fz != 0 && tzmax/fz < tzmin/fz) swap(tzmin, tzmax); + double tmax(fx == 0 ? 1e20 : txmax/fx); + if (fx != 0 && txmin/fx > tmin) tmin = txmin/fx; + if (fy != 0 && tymin/fy > tmin) tmin = tymin/fy; + if (fz != 0 && tzmin/fz > tmin) tmin = tzmin/fz; + if (fy != 0 && tymax/fy < tmax) tmax = tymax/fy; + if (fz != 0 && tzmax/fz < tmax) tmax = tzmax/fz; + double t(fnc.GetMinimumX(tmin, tmax, tol, itr, logt)), s(fnc.Eval(t)); + if (tmax <= tmin) return TLorentzVector(0, 0, 0, -1); + if (s != s || s > 0.01) return TLorentzVector(0, 0, 0, -1); + else return TLorentzVector(vx + fx*t, vy + fy*t, vz + fz*t, t); +} + +// Default constructor. +FoilMaterial::Intersect::Intersect(FoilMaterial *foilIn) : foil(foilIn) { + stringstream str; str << this; + fnc = TF1(str.str().c_str(), this, 0, 1000, 0, ""); +} + +// Return the distance from the foil along the flight direction. +double FoilMaterial::Intersect::operator()(double *t, double *) { + return abs(foil->x(vy+fy*t[0], vz+fz*t[0], half) - (vx+fx*t[0])); +} + +// Configure the tool. +void FoilMaterial::Intersect::config(double tolIn, int itrIn, bool logtIn) { + tol = abs(tolIn); itr = abs(itrIn); logt = logtIn; +}; + +// Return the intersection. +TLorentzVector FoilMaterial::Intersect::intersect +(double vxIn, double vyIn, double vzIn, double fxIn, double fyIn, double fzIn, + int halfIn) { + double f(sqrt(fxIn*fxIn + fyIn*fyIn + fzIn*fzIn)); f = f > 0 ? f : 1; + vx = vxIn; vy = vyIn; vz = vzIn; fx = fxIn/f; fy = fyIn/f; fz = fzIn/f; + if (halfIn == 0) { + TLorentzVector sl(intersect(-1)), su(intersect(1)); + if (sl.T() >= 0 && (sl.T() < su.T() || su.T() < 0)) + {sl.SetT(-1); return sl;} + else if (su.T() >= 0 && (su.T() < sl.T() || sl.T() < 0)) + {su.SetT(1); return su;} + else return TLorentzVector(0, 0, 0, 0); + } + TLorentzVector s(intersect(halfIn)); + if (s.T() >= 0) {s.SetT(halfIn); return s;} + else return TLorentzVector(0, 0, 0, 0); +} + +//============================================================================= +// FoilMaterial::Distance class. + +// Return the distance for a given half. +void FoilMaterial::Distance::distance +(int halfIn, int method, int dir, TLorentzVector &s, double &n, double &u) { + half = halfIn; + double d, dmin(numeric_limits<double>::infinity()), y(vy), z(vz), + zmax(vz + (dir != -1)*sigma*(dvz < dvmax ? dvz : dvmax)); + double ymin(foil->p(0, vz, half)), ymax(50); + double t[2] = {y, z - (dir != 1)*sigma*(dvz < dvmax ? dvz : dvmax) - step}; + if (y < ymin) {ymax = -ymax; ymin += 0.1; swap(ymin, ymax);} + else ymin -= 0.1; + min->SetLimitedVariable(0, "y", y, step, ymin, ymax); + while (t[1] <= zmax) { + t[1] += step; d = (*this)(t); + if (d < dmin) {dmin = d; z = t[1];} + if (method == 2) { + min->SetVariableValues(t); min->FixVariable(1); min->Minimize(); + u += 1/(min->MinValue() < d ? min->MinValue() : d); ++n; + min->ReleaseVariable(1); + } + } + min->SetVariableValue(0, vy); min->SetVariableValue(1, vz); min->Minimize(); + if (min->MinValue() < dmin) { + dmin = min->MinValue(); y = min->X()[0]; z = min->X()[1];} + min->SetVariableValue(0, y); min->SetVariableValue(1, z); min->Minimize(); + if (min->MinValue() < dmin) { + dmin = min->MinValue(); y = min->X()[0]; z = min->X()[1];} + if (dmin < s.T()) s.SetXYZT(foil->x(y, z, half), y, z, dmin); + if (method == 1) {u += 1/dmin; ++n;} +} + +// Default constructor. +FoilMaterial::Distance::Distance(FoilMaterial *foilIn) : + foil(foilIn), fnc(this, &FoilMaterial::Distance::operator(), 2), min(0) {;} + +// Return the distance from the foil in uncertainty space. +double FoilMaterial::Distance::operator()(const double *t) { + double x((vx - foil->x(t[0], t[1], half))/dvx), y((vy - t[0])/dvy), + z((vz - t[1])/dvz); + return sqrt(x*x + y*y + z*z); +} + +// Configure the tool. +void FoilMaterial::Distance::config +(double dvminIn, double dvmaxIn, double sigmaIn, double stepIn, + double tolIn, int itrIn, string libIn, string algIn) { + dvmin = abs(dvminIn); dvmax = abs(dvmaxIn); + sigma = abs(sigmaIn); step = abs(stepIn); + if (libIn != lib || alg != algIn || !min) { + lib = libIn; alg = algIn; + min = ROOT::Math::Factory::CreateMinimizer(lib.c_str(), alg.c_str()); + } + min->SetFunction(fnc); min->SetPrintLevel(0); min->SetValidError(false); + min->SetMaxIterations(itrIn); min->SetMaxFunctionCalls(itrIn); + min->SetTolerance(tolIn); +} + +// Return the distance. +TLorentzVector FoilMaterial::Distance::distance +(double vxIn, double vyIn, double vzIn, double dvxIn, double dvyIn, + double dvzIn, int halfIn, int method, int dir) { + vx = vxIn; vy = vyIn; vz = vzIn; + dvx = abs(dvxIn); dvy = abs(dvyIn), dvz = abs(dvzIn); + dvx = dvx > dvmin ? dvx : dvmin; + dvy = dvy > dvmin ? dvy : dvmin; + dvz = dvz > dvmin ? dvz : dvmin; + min->SetLimitedVariable(0, "y", vy, step, vy - sigma*dvy, vy + sigma*dvy); + min->SetLimitedVariable(1, "z", vz, step, + vz - (dir != 1)*sigma*(dvz < dvmax ? dvz : dvmax), + vz + (dir != -1)*sigma*(dvz < dvmax ? dvz : dvmax)); + double n(0), u(0); + TLorentzVector s(0, 0, 0, numeric_limits<double>::infinity()); + if (dvx == 0 || dvy == 0 || dvz == 0) return s; + level = gErrorIgnoreLevel; + gErrorIgnoreLevel = 1001; + if (halfIn == 0 || halfIn == -1) distance(-1, method, dir, s, n, u); + if (halfIn == 0 || halfIn == 1) distance( 1, method, dir, s, n, u); + if (method != 0) s.SetT(n/(2*u)); + gErrorIgnoreLevel = level; + return s; +} + +//============================================================================= +// FoilMaterial class. + +// Default constructor. +FoilMaterial::FoilMaterial(string pars) : ingr(this), insc(this), dist(this) { + configIntegral(); configIntersect(); configDistance(); + vector<string> segs(4, "b"); segs[1] = "c"; segs[2] = "t"; segs[3] = "f"; + TFile file(pars.c_str()); + for (int seg = 0; seg < (int)segs.size(); ++seg) { + flzs.push_back(Segment(file, "foil_l_" + segs[seg])); + fuzs.push_back(Segment(file, "foil_u_" + segs[seg])); + } + configSegment(); + file.Close(); +} + +// Return the x-position of the foil. +double FoilMaterial::x(double y, double z, int half) { + vector<Segment> *fzs = half < 0 ? &flzs : &fuzs; + for (int seg = 0; seg < (int)fzs->size(); ++seg) + if ((*fzs)[seg].valid(z)) return (*fzs)[seg].x(y, z); + return 0; +} + +// Return a parameter for the foil function. +double FoilMaterial::p(unsigned int par, double z, int half) { + vector<Segment> *fzs = half < 0 ? &flzs : &fuzs; + for (int seg = 0; seg < (int)fzs->size(); ++seg) + if ((*fzs)[seg].valid(z)) return (*fzs)[seg].p(par, z); + return 0; +} + +// Configure the technical segment parameters. +void FoilMaterial::configSegment(int half, string seg, int par, int mth) { + if (half == 0) { + configSegment(-1, seg, par, mth); configSegment(1, seg, par, mth); return; + } + if (seg == "a") { + vector<string> segs(4, "b"); segs[1] = "c"; segs[2] = "t"; segs[3] = "f"; + for (int idx = 0; idx < (int)segs.size(); ++idx) + configSegment(half, segs[idx], par, mth); return; + } + if (par < 0) { + for (int idx = 0; idx < 6; ++idx) configSegment(half, seg, idx, mth); + return; + } + map<string, int> segs; segs["c"] = 1; segs["t"] = 2; segs["f"] = 3; + (half < 0 ? flzs : fuzs)[segs[seg]].config(par, mth); +} + +// Configure the technical probability integral parameters. +void FoilMaterial::configIntegral +(double dvmin, double sigma, double step, double width, double tol, + ROOT::Math::IntegrationMultiDim::Type alg) { + ingr.config(dvmin, sigma, step, width, tol, alg); +} + + +// Configure the technical intersection parameters. +void FoilMaterial::configIntersect(double tol, int itr, bool logt) { + insc.config(tol, itr, logt); +} + +// Configure the technical distance parameters. +void FoilMaterial::configDistance +(double dvmin, double dvmax, double sigma, double step, double tol, int itr, + string lib, string alg) { + dist.config(dvmin, dvmax, sigma, step, tol, itr, lib, alg); +} + +// Return the probability integral. +double FoilMaterial::integral +(double vx, double vy, double vz, double dvx, double dvy, double dvz, + int method) { + return ingr.integral(vx, vy, vz, dvx, dvy, dvz, method); +} + +// Return the intersection. +TLorentzVector FoilMaterial::intersect +(double vx, double vy, double vz, double fx, double fy, double fz, int half) { + return insc.intersect(vx, vy, vz, fx, fy, fz, half); +} + +// Return the distance. +TLorentzVector FoilMaterial::distance +(double vx, double vy, double vz, double dvx, double dvy, double dvz, int half, + int method, int dir) { + return dist.distance(vx, vy, vz, dvx, dvy, dvz, half, method, dir); +} + +//============================================================================= +// VeloMaterial class. + +// Default constructor. +VeloMaterial::VeloMaterial(string pars) : foil(pars), modf(pars), modm(pars), + rfnc("fnc", "[0] + [1]*exp(-[2]*x) + [3]*exp(-[4]*x)", 0.04, 0.6) { + modf.configSensor(foil); + rfnc.SetParameters(0.184, 0.163, 0.266, 1.001, 2.173); +} + +// Transform a vertex position. +void VeloMaterial::transform(double &vx, double &vy, double &vz, + int year, int run) { + + // If no year is specified, do not perform shift. + if (year < 0) return; + + // Start with 2016 shift, all other shifts relative to 2016. + // Applies reverse of 2016 shift to 2017 and 2018 data, then applies year/run + // number specific shift + if (year != 2016) {vx += 0.8368; vy -= 0.17431; vz -= 0.8551;} + if (year == 2017) {vx -= 0.8287; vy += 0.08594; vz -= 2.6381;} + else if (year == 2018 && run < 214390) {vx -= 0.8341; vy -= 0.09406; vz += 5.1581;} + else if (year == 2018) {vx -= 0.8622; vy -= 0.18155; vz += 5.0085;} + +} + +// Return the distance. +double VeloMaterial::distance(double vx, double vy, double vz, + double fx, double fy, double fz, + double fx1, double fy1, double fz1, + double fx2, double fy2, double fz2, + double dvx, double dvy, double dvz, + int year, int run) { + + // Determine the material distance. + transform(vx, vy, vz, year, run); + if (dvz <= 1) dvz = 1; + if (vz > 300 && dvx < 0.5) dvx = 0.5; + if (vz > 300 && dvy < 0.5) dvy = 0.5; + double d = 1/(1/modf.distance(vx, vy, vz, dvx, dvy, dvz, -1).T() + + 1/modf.distance(vx, vy, vz, dvx, dvy, dvz, +1).T() + + 1/foil.distance(vx, vy, vz, dvx, dvy, dvz, -1, 0, -1).T() + + 1/foil.distance(vx, vy, vz, dvx, dvy, dvz, -1, 0, +1).T() + + 1/foil.distance(vx, vy, vz, dvx, dvy, dvz, +1, 0, -1).T() + + 1/foil.distance(vx, vy, vz, dvx, dvy, dvz, +1, 0, +1).T()); + + // Determine the radial distance between daughter hits in first module. + double mz1 = modm.intersect(vx, vy, vz, fx1, fy1, fz1).Z(); + double mz2 = modm.intersect(vx, vy, vz, fx2, fy2, fz2).Z(); + double hzm = min(mz1 - vz, mz2 - vz); + double hx1 = vx + fx1/fz1*hzm; + double hy1 = vy + fy1/fz1*hzm; + double hx2 = vx + fx2/fz2*hzm; + double hy2 = vy + fy2/fz2*hzm; + double drh = sqrt(pow(hx1 - hx2, 2) + pow(hy1 - hy2, 2)); + + // Scale for r-dependence. + double rh = modm.intersect(vx, vy, vz, fx, fy, fz).Perp(); + double rp = 0.04 + 0.05*(rh - 8)/(35 - 8); + double pp = rh > 18 ? 0.0023 : 0.0046; + d *= 0.65*0.23/min(0.23, max(0.7, rfnc.Eval(drh/sqrt(rp*rp + rh*pp*rh*pp)))); + if (rh > 0 && rh <= 8) d /= 0.95; + if (rh > 8 && rh <= 11) d /= 0.72; + if (rh > 13 && rh <= 15) d /= 0.75; + if (rh > 16 && rh <= 18) d /= 1.45; + if (rh > 20) d /= 0.78; + return d; +} + +// Return true if the flight direction of a vertex intersects a foil tip. +bool VeloMaterial::tip(double vx, double vy, double vz, + double fx, double fy, double fz, + int year, int run) { + transform(vx, vy, vz, year, run); + double mz(modf.z(modf.sensor(vz))); + return abs(foil.intersect(vx, vy, vz, fx, fy, fz).Z() - mz) < 1 || + abs(foil.intersect(vx, vy, vz, -fx, -fy, -fz).Z() - mz) < 1; +} + +// Return true if the expected first hit is missed. +bool VeloMaterial::miss(double vx, double vy, double vz, + double fx, double fy, double fz, double mz, + int year, int run) { + transform(vx, vy, vz, year, run); + double hz(-numeric_limits<double>::infinity()), hr(0), z(vz); + while(hr < 8.170){ + TLorentzVector d(modm.intersect(vx + (z - vz)*fx/fz, vy + (z - vz)*fy/fz, + z, fx, fy, fz)); + if(d.T() == 0) break; + int idx(modm.sensor(d.Z())); + double mx(modm.p(idx, 1)), my(modm.p(idx, 0)); + hr = sqrt((d.X() - mx)*(d.X() - mx) + (d.Y() - my)*(d.Y() - my)); + hz = d.Z(); + z = d.Z() + 1; + } + return abs(hz - mz) > 1; +} diff --git a/bu2kdarkscalar_darkscalar2hh/velo.h b/bu2kdarkscalar_darkscalar2hh/velo.h new file mode 100644 index 0000000000..35170dbd89 --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/velo.h @@ -0,0 +1,799 @@ +// Written by Philip Ilten and Mike Williams, 20/06/2017. +// Toolset to check if a secondary vertex is consistent with having +// originated from the VELO material. See README for an overview. +#ifndef VELOMATERIAL_H +#define VELOMATERIAL_H +#include <limits> +#include <algorithm> +#include "TFile.h" +#include "TKey.h" +#include "TObjString.h" +#include "TVectorD.h" +#include "TLorentzVector.h" +#include "TF1.h" +#include "TF2.h" +#include "TMath.h" +#include "TError.h" +#include "TGraph.h" +#include "TSpline.h" +#include "Math/WrappedTF1.h" +#include "Math/BrentMinimizer1D.h" +#include "Math/WrappedMultiTF1.h" +#include "Math/IntegratorMultiDim.h" +#include "Math/Minimizer.h" +#include "Math/Factory.h" +#include "Math/Functor.h" +#include "Minuit2/Minuit2Minimizer.h" +using namespace std; + +// Pre-declare material class for sensor configuration. +class FoilMaterial; + +//============================================================================= +/* + Class to access the parameterized VELO module material. +*/ +class ModuleMaterial { + +public: + + //=========================================================================== + /* + Class to hold the module sensor geometry. + + Details on the module sensor geometry description can be found in + the DBASE source: + + DBASE/Det/XmlDDDB/DDDB/Velo/Geom/LogVol/GenericModule.xml + DBASE/Det/XmlDDDB/DDDB/Velo/GeomParam/VeloSensorParam.xml + + Here, the module geometry was taken from the EDMS design + documents with number 401568 v.4 that can be found at the + following link. + + https://edms.cern.ch/ui/#!master/navigator/document?D:1100625139: + 1100625139:subDocs + */ + class Sensor { + + public: + + // Configurable Members. + TF1 fncl, fncu; ///< Functions defining the lower/upper sensor edges. + double z; ///< Sensor z-position. + int half; ///< Lower/upper VELO half. + + // Methods. + /// Default constructor. + Sensor(); + + /* + Primary constructor. + \param ifile: TFile containing all the needed parameters. + \param mod: string of the module to build, e.g. "module_01". + \param sns: string of the sensor to build from the module, e.g. "0". + */ + Sensor(TFile &file, string mod, string sns); + + /// Operator for sorting by the given z-value. + bool operator<(const Sensor& sns) const; + + /// Configure the tool. + void config(double xc, double yc, bool rel = true); + + /// Configure the tool. + void config(const TF1 &fnc); + + /* + Return if an x,y-point falls within a module sensor. + \param [x,y]: x,y-position to evaluate. + */ + bool inside(double x, double y); + + /* + Return the x-position for the edge of the sensor. + \param y: y-position to evaluate. + \param edge: -/+1 for the lower/upper edge, 0 for the inside edge. + */ + double x(double y, int edge = 0); + + /* + Return a parameter for the sensor function. + \param par: parameter index. + \param edge: -/+1 for the lower/upper edge, 0 for the inside edge. + */ + double p(unsigned int par, int edge = 0); + }; + + //=========================================================================== + /* + Class to calculate the probability integral for a vertex to be + produced from VELO modules. + + The probability density function for the vertex is assumed to be a + 3D Gaussian and the integration is performed in the x,y-plane as + the z-direction pre-factor remains constant. + */ + class Integral { + + // Members. + vector<Sensor> *mzs; ///< Module sensors. + Sensor *geo; ///< Sensor geometry for integration. + TF2 fnc; ///< Function used for integration. + ROOT::Math::WrappedMultiTF1 wrp; ///< Wrapper for the function. + ROOT::Math::IntegratorMultiDim *inr; ///< Integrator for the function. + double vx, vy, vz, dvx, dvy, dvz; ///< Vertex position and uncertainty. + + /* + Return the probability integral for a given half. + \param halfIn: -/+1 for the lower/upper VELO half in x. + */ + double integral(int half); + + public: + + // Configurable members. + double dvmin; ///< Minimum allowed vertex uncertainty. + double sigma; ///< Number of standard deviations to calculate over. + double width; ///< Width of the module in mm. + double tol; ///< Integration tolerance. + /// Integration algorithm: adaptive, VEGAS, MISER, or plain. + ROOT::Math::IntegrationMultiDim::Type alg; + + // Methods. + /* + Default constructor. + \param mzsIn: module sensors. + */ + Integral(vector<Sensor> *mzs); + + ///< Destructor. + ~Integral(); + + /* + Return the probability function for a given half. The passed + coordinates are not in mm but are in sigma from the origin of a + 2D normal distribution. + \param t: the variables sx and sy passed as an array of length 2. + */ + double operator()(double *t, double *); + + /// Configure the tool. + void config(double dvminIn, double sigmaIn, double mwIn, + double tolIn, ROOT::Math::IntegrationMultiDim::Type algIn); + + /// Return the probability integral. + double integral(double vxIn, double vyIn, double vzIn, double dvxIn, + double dvyIn, double dvzIn, int halfIn); + }; + + //=========================================================================== + /* + Class to calculate the minimum distance in uncertainty space + between a vertex and the VELO modules. + + First the lower and upper modules which minimize the distance in z + are found. This is the same in normal or uncertainty space. Next, + the minimum transverse distance is found for each module in + uncertainty space. The minimum distance between the the lower and + upper modules is returned. + */ + class Distance { + + // Members. + vector<Sensor> *mzs; ///< Module sensors. + Sensor *geo; ///< Sensor geometry for intersection. + TF1 fnc; ///< Wrapped function to minimize. + double vx, vy, vz, dvx, dvy, dvz; ///< Vertex position and uncertianty. + + /* + Return the distance for a given half. + \param halfIn: -/+1 for the lower/upper VELO half in x. + \param mzs: module z-values. + \param s: distance vector for updating. + */ + void distance(int half, TLorentzVector &s); + + public: + + // Configurable members. + double dvmin; ///< Minimum allowed vertex uncertainty. + double tol; ///< Minimization tolerance. + int itr; ///< Minimization iterations. + bool logt; ///< Flag to use a log scale in minimization. + + // Methods. + /* + Default constructor. + \param mzsIn: module sensors. + */ + Distance(vector<Sensor> *mzsIn); + + /* + Return the transverse distance. + \param t: the variable sy passed as an array of length 1. + */ + double operator()(double *t, double *); + + /// Configure the tool. + void config(double dvminIn, double tolIn, int itrIn, bool logtIn); + + /// Return the distance. + TLorentzVector distance(double vxIn, double vyIn, double vzIn, + double dvxIn, double dvyIn, double dvzIn, + int halfIn); + }; + + //=========================================================================== +private: + + // Members. + vector<Sensor> mzs; ///< Module sensors. + Integral ingr; ///< Internal integral tool. + Distance dist; ///< Internal distance tool. + +public: + + // Methods. + /* + Default constructor. + \param pars: name of the fit parameters file. + */ + ModuleMaterial(string pars = "pars.root"); + + /* + Return the index for the sensor nearest a given z-position. + \param z: z-position to evaluate. + \param half: -/+1 for the lower/upper VELO half in x, 0 for both. + */ + int sensor(double z, int half = 0); + + /* + Return if an x,y-point falls within a module sensor. + \param idx: index of the module sensor. + \param x: x-position to evaluate. + \param y: y-position to evaluate. + */ + bool inside(unsigned int idx, double x, double y); + + /* + Return the x-position for the edge of a module sensor. + \param idx: index of the module sensor. + \param y: y-position to evaluate. + \param edge: -/+1 for the lower/upper edge, 0 for the inside edge. + */ + double x(unsigned int idx, double y, int edge = 0); + + /* + Return the z-position for a module sensor. + \param idx: index of the module sensor. + */ + double z(unsigned int idx); + + /* + Return a parameter for a module sensor function. + \param idx: index of the module sensor. + \param par: parameter index. + \param edge: -/+1 for the lower/upper edge, 0 for the inside edge. + */ + double p(unsigned int idx, int par, int edge = 0); + + /* + Configure the technical sensor parameters. This method either sets + the absolute x,y-position for the center of each sensor or shifts + the center from the nominal position. This can be applied to + either or both halves of the VELO sensors. + \param xc: x-position of the sensor center in mm. + \param yc: y-position of the sensor center in mm. + \param rel: if true, shift the sensors from their nominal positions. + \param half: -/+1 for the lower/upper VELO half in x, 0 for both. + */ + void configSensor(double xc = 0, double yc = 0, bool rel = true, + int half = 0); + + /* + Configure the technical sensor parameters. The sensor edge is set + as the foil edge at the given z-position for each sensor. + \param foil: the foil material map. + */ + void configSensor(FoilMaterial &foil); + + /* + Configure the technical probability integral parameters. + \param dvmin: minimum allowed vertex uncertainty. + \param sigma: number of standard deviations to calculate over. + \param width: width of the module in mm. + \param tol: integration tolerance. + \param alg: integration algorithm: adaptive, VEGAS, MISER, or plain. + */ + void configIntegral(double dvmin = 0.25, double sigma = 5, + double width = 1, double tol = 0.01, + ROOT::Math::IntegrationMultiDim::Type alg = + ROOT::Math::IntegrationMultiDim::kADAPTIVE); + + /* + Configure the technical distance parameters. + \param dvmin: minimum allowed vertex uncertainty. + \param tol: minimization tolerance. + \param itr: minimization iterations + \param logt: flag to use a log scale in minimization. + */ + void configDistance(double dvmin = 0.25, double tol = 1e-10, int itr = 100, + bool logt = false); + + /* + Return the probability integral. + \param v[x,y,z]: x,y,z-positions of the vertex. + \param d[vx,vy,vz]: uncertainties on the x,y,z-positions of the vertex. + \param half: -/+1 for the lower/upper VELO half in x, 0 for both. + */ + double integral(double vx, double vy, double vz, + double dvx, double dvy, double dvz, int half); + + /* + Return the intersection. A 4-vector of the intersection point is + returned, where the T (or E) component gives the VELO half (-/+1 + for lower/upper) or 0 if no intersection is found. + \param v[x,y,z]: x,y,z-positions of the vertex. + \param f[x,y,z]: flight direction. + \param half: -/+1 for the lower/upper VELO half in x, 0 for both. + */ + TLorentzVector intersect(double vx, double vy, double vz, + double fx, double fy, double fz, int half = 0); + + /* + Return the distance. The point of minimum distance on the material + in uncertainty space is returned where the T (or E) component + gives the distance. + \param v[x,y,z]: x,y,z-positions of the vertex. + \param d[vx,vy,vz]: x,y,z-uncertainties of the vertex position. + \param half: -/+1 for the lower/upper VELO half in x, 0 for both. + */ + TLorentzVector distance(double vx, double vy, double vz, + double dvx = 1, double dvy = 1, double dvz = 1, + int half = 0); +}; + +//============================================================================= +/* + Class to access the parameterized VELO foil material. +*/ +class FoilMaterial { + friend class ModuleMaterial; + +public: + + //=========================================================================== + /* + Class to hold the foil segment geometry. + + This class is built from the parameters and functions provided in + the input parameter file passed to the primary constructor. The + class holds the function for the x,y-slice as well as the + parameters for this slice as a function of z-position. + */ + class Segment { + friend class ModuleMaterial; + + // Members. + vector<TF1> fncs; ///< Vector of functions for the slice parameters. + vector<TSpline3> spls; ///< Vector of splines for the slice parameters. + vector<int> mths; ///< Vector of methods for parameter evaluation. + TF1 fnc; ///< The slice function. + double zmin, zmax; ///< Range of validity in z-position for the segment. + + public: + + // Methods. + /// Default constructor. + Segment(); + + /* + Primary constructor. + \param ifile: TFile containing all the needed parameters. + \param pre: string of the foil segment to build, e.g. "foil_l_c". + */ + Segment(TFile &file, string pre); + + /// Configure the tool. + void config(int par, int mth); + + /* + Return if a z-position is in the segment. + \param z: z-position to evaluate. + */ + bool valid(double &z); + + /* + Return the x-position for the segment. + \param [y,z]: y,z-position to evaluate. + */ + double x(double y, double z); + + /* + Return a parameter for the segment function. + \param par: parameter index. + \param z: z-position to evaluate. + */ + double p(unsigned int par, double z); + }; + + //=========================================================================== + /* + Class to calculate the probability integral for a vertex to be + produced from the VELO foil. + + The probability density function for the vertex is assumed to be a + 3D Gaussian and the integration is performed in the y,z-plane as + the x-direction pre-factor remains constant. + */ + class Integral { + + // Members. + FoilMaterial *foil; ///< Foil geometry. + TF2 fnc; ///< Function used for integration. + ROOT::Math::WrappedMultiTF1 wrp; ///< Wrapper for the function. + ROOT::Math::IntegratorMultiDim *inr; ///< Integrator for the function. + double vx, vy, vz, dvx, dvy, dvz; ///< Vertex position and uncertainty. + int half; ///< Lower/upper VELO half to evaluate. + + /* + Return the probability integral in one dimension. + \param v: vertex position. + \param dv: vertex position uncertainty. + \param t: material position. + \param dt: material position width. + */ + double integral(double v, double dv, double t, double dt); + + public: + + // Configurable members. + double dvmin; ///< Minimum allowed vertex uncertainty. + double sigma; ///< Number of standard deviations to calculate over. + double step; ///< Step distance in sigma. + double width; ///< Width of the foil in mm. + double tol; ///< Integration tolerance. + /// Integration algorithm: adaptive, VEGAS, MISER, or plain. + ROOT::Math::IntegrationMultiDim::Type alg; + + // Methods. + /* + Default constructor. + \param foilIn: foil geometry. + */ + Integral(FoilMaterial *foilIn); + + ///< Destructor. + ~Integral(); + + /* + Return the probability function for a given half. The passed + coordinates are not in mm but are in sigma from the origin of a + 2D normal distribution. + \param t: the variables sy and sz passed as an array of length 2. + */ + double operator()(double *t, double *); + + /// Configure the tool. + void config(double dvminIn, double sigmaIn, double stepIn, double widthIn, + double tolIn, ROOT::Math::IntegrationMultiDim::Type algIn); + + /// Return the probability integral. + double integral(double vxIn, double vyIn, double vzIn, double dvxIn, + double dvyIn, double dvzIn, int methodIn); + }; + + //=========================================================================== + /* + Class to calculate the intersection of a particle, defined with a + vertex and flight direction, with the VELO foil. + */ + class Intersect { + + // Members. + FoilMaterial *foil; ///< Foil geometry. + TF1 fnc; ///< Wrapped function to minimize. + double vx, vy, vz, fx, fy, fz; ///< Vertex position and flight direction. + int half; ///< Lower/upper VELO half to evalulate. + + /* + Return the intersection for a given half. + \param halfIn: -/+1 for the lower/upper VELO half in x. + */ + TLorentzVector intersect(int halfIn); + + public: + + // Configurable members. + double tol; ///< Minimization tolerance. + int itr; ///< Minimization iterations. + bool logt; ///< Flag to use a log scale in minimization. + + // Methods. + /* + Default constructor. + \param foilIn: foil geometry. + */ + Intersect(FoilMaterial *foilIn); + + /* + Return the distance from the foil along the flight direction. + \param t: the variable t passed as an array of length 1. + */ + double operator()(double *t, double *); + + /// Configure the tool. + void config(double tolIn, int itrIn, bool logtIn); + + /// Return the intersection. + TLorentzVector intersect(double vxIn, double vyIn, double vzIn, + double fxIn, double fyIn, double fzIn, + int halfIn); + }; + + //=========================================================================== + /// Class to calculate the distance between a vertex and the foil. + class Distance { + + // Members. + FoilMaterial *foil; ///< Representation of the foil. + ROOT::Math::Functor fnc; ///< Wrapped function to minimize. + ROOT::Math::Minimizer *min; ///< Minimizer. + double vx, vy, vz, dvx, dvy, dvz; ///< Vertex position and uncertainty. + int half; ///< Lower/upper VELO half to evaluate. + int level; ///< Saved ROOT reporting level. + + /* + Return the distance for a given half. + \param halfIn: -1 for the lower half and +1 for the upper foil half. + \param method: distance method. + \param dir: minimizing z-direction: 0 both, -/+1 backwards/forward. + \param s: vector containt + \param n: number of all distances. + \param u: sum of all distances. + */ + void distance(int halfIn, int method, int dir, TLorentzVector &s, + double &n, double &u); + + public: + + // Configurable members. + double dvmin; ///< Minimum allowed vertex uncertainty. + double dvmax; ///< Maximum vertex uncertainty for minimization limits. + double sigma; ///< Number of standard deviations to calculate over. + double step; ///< Minimization step size along z-direction in mm. + double tol; ///< Minimization tolerance. + double itr; ///< Minimization iterations. + string lib; ///< Minimization library, see alg for options. + /* + Minimization algorithm. The available algorithms for each library are: + \param Minuit2: Migrad, Simplex, Combined, Scan, Fumili + \param GSLMultiMin: ConjugateFR, ConjugatePR, BFGS, BFGS2, + SteepestDescent + \param GSLMultiFit: + \param GSLSimAn: + */ + string alg; + + // Methods. + /* + Default constructor. + \param foilIn: foil geometry. + */ + Distance(FoilMaterial *foilIn); + + /* + Return the distance from the foil in uncertainty space. + \param t: the variables y and z passed as an array of length 2. + */ + double operator()(const double *t); + + /// Configure the tool. + void config(double dvminIn, double dvmax, double sigmaIn, double stepIn, + double tolIn, int itrIn, string libIn, string algIn); + + /// Return the distance. + TLorentzVector distance(double vxIn, double vyIn, double vzIn, + double dvxIn, double dvyIn, double dvzIn, + int halfIn, int method, int dir); + }; + + //=========================================================================== +protected: + + // Members. + vector<Segment> flzs, fuzs; ///< Lower/upper foil segment functions. + Integral ingr; ///< Internal integral tool. + Intersect insc; ///< Internal intersection tool. + Distance dist; ///< Internal distance tool. + +public: + + // Methods. + /* + Default constructor. + \param pars: name of the fit parameters file. + */ + FoilMaterial(string pars = "pars.root"); + + /* + Return the x-position of the foil. + \param [y,z]: y,z-position to evaluate. + \param half: -/+1 for the lower/upper VELO half in x. + */ + double x(double y, double z, int half); + + /* + Return a parameter for the foil function. + \param par: parameter index. + \param z: z-position to evaluate. + \param half: -/+1 for the lower/upper VELO half in x. + */ + double p(unsigned int par, double z, int half); + + /* + Configure the technical segment parameters. + \param half: -/+1 for the lower/upper VELO half in x, 0 for both. + \param seg: name of the segment: "b", "c", "t", "f". If "a" all segments + are configured. + \param par: parameter to configure, -1 for all. + \param mth: method for parameter evaluation: 0 for fitted function and + 1 for interpolation. + */ + void configSegment(int half = 0, string seg = "a", int par = -1, int mth = 0); + + /* + Configure the technical probability integral parameters. + \param dvmin: minimum allowed vertex uncertainty. + \param sigma: number of standard deviations to calculate over. + \param step: integration step size along z-direction in sigma. + \param width: width of the foil in mm. + \param tol: integration tolerance. + \param alg: integration algorithm. + */ + void configIntegral(double dvmin = 0.25, double sigma = 5, double step = 1, + double width = 1, double tol = 1e-2, + ROOT::Math::IntegrationMultiDim::Type alg = + ROOT::Math::IntegrationMultiDim::kADAPTIVE); + + /* + Configure the technical intersection parameters. + \param tol: minimization tolerance. + \param itr: minimization iterations + \param logt: flag to use a log scale in minimization. + */ + void configIntersect(double tol = 1e-10, int itr = 100, bool logt = false); + + /* + Configure the technical distance parameters. + \param dvmin: minimum allowed vertex uncertainty. + \param dvmax: maximum vertex uncertainty for minimization limits. + \param sigma: number of standard deviations to calculate over. + \param step: minimization step size along z-direction in mm. + \param tol: minimization tolerance. + \param itr: minimization iterations. + \param lib: library used for minimization. + \param alg: algorithm used for minimization. + */ + void configDistance(double dvmin = 0.25, double dvmax = 100, + double sigma = 5, double step = 1, + double tol = 1e-2, int itr = 100, + string lib = "Minuit2", string alg = "Migrad"); + + /* + Return the probability integral. + \param v[x,y,z]: x,y,z-positions of the vertex. + \param d[vx,vy,vz]: uncertainties on the x,y,z-positions of the vertex. + */ + double integral(double vx, double vy, double vz, + double dvx, double dvy, double dvz, int method = 0); + + /* + Return the intersection. A 4-vector of the intersection point is + returned, where the T (or E) component gives the VELO half (-/+1 + for lower/upper) or 0 if no intersection is found. + \param v[x,y,z]: x,y,z-positions of the vertex. + \param f[x,y,z]: flight direction. + \param half: -/+1 for the lower/upper VELO half in x, 0 for both. + */ + TLorentzVector intersect(double vx, double vy, double vz, + double fx, double fy, double fz, int half = 0); + + /* + Return the distance. The point of minimum distance on the material + in uncertainty space is returned where the T (or E) component + gives the distance. + \param v[x,y,z]: x,y,z-positions of the vertex. + \param d[vx,vy,vz]: x,y,z-uncertainties of the vertex position. + \param half: -/+1 for the lower/upper VELO half in x, 0 for both. + \param method: distance method. + \param dir: minimizing z-direction: 0 both, -/+1 backwards/forward. + Three distance methods are implemented. (1) The minimum + distance. (2) The inverse of the sum of lower and upper minimum + distance reciprocals. (3) Same as the previous method, but now + rather than the minimum distances, it is summed over all distances. + */ + TLorentzVector distance(double vx, double vy, double vz, + double dvx = 1, double dvy = 1, double dvz = 1, + int half = 0, int method = 0, int dir = 0); +}; + +//============================================================================= +/* + Class to access the parameterized VELO material, both module and + foil, as implemented in the initial inclusive dimuon dark photon + analysis, LHCb-ANA-2017-027. +*/ +class VeloMaterial { + +public: + + /* + Default constructor. + \param pars: name of the fit parameters file. + */ + VeloMaterial(string pars = "pars.root"); + + /* + Transform a vertex relative to the VELO for a given the run number. + \param year: data taking year for VELO shift, if 0 do not shift. + \param run: run number for VELO shift. + */ + void transform(double &vx, double &vy, double &vz, int year, int run); + + /* + Return the distance. This is the harmonic mean of the following + six distances: upper module, lower module, forward upper foil, + forward lower foil, backward upper foil, backward lower foil. + \param v[x,y,z]: x,y,z-positions of the vertex. + \param f[x,y,z]: flight direction for the vertex. + \param f[x,y,z]1: flight direction for the first daughter. + \param f[x,y,z]1: flight direction for the second daughter. + \param d[vx,vy,vz]: x,y,z-uncertainties of the vertex position. + \param year: data taking year for VELO shift. + \param run: run number for VELO shift. + */ + double distance(double vx, double vy, double vz, + double fx, double fy, double fz, + double fx1, double fy1, double fz1, + double fx2, double fy2, double fz2, + double dvx, double dvy, double dvz, + int year, int run); + + /* + Return true if the flight direction of a vertex intersects a foil + tip, i.e. intersects the foil within 1 mm of where the modules are + located. + \param v[x,y,z]: x,y,z-positions of the vertex. + \param f[x,y,z]: flight direction. + \param year: data taking year for VELO shift. + \param run: run number for VELO shift. + */ + bool tip(double vx, double vy, double vz, + double fx, double fy, double fz, + int year, int run); + + /* + Return true if the expected first hit is missed, given a flight direction + and vertex. The active area of the sensors begins at 8.170 mm. + \param v[x,y,z]: x,y,z-positions of the vertex. + \param f[x,y,z]: flight direction. + \param mz: z-position of the module containing the first hit. + \param year: data taking year for VELO shift. + \param run: run number for VELO shift. + */ + bool miss(double vx, double vy, double vz, + double fx, double fy, double fz, double mz, + int year, int run); + + private: + + // Members. + FoilMaterial foil; ///< Foil material tool. + ModuleMaterial modf; ///< Module material tool extended to the foil. + ModuleMaterial modm; ///< Module material tool with standard geometry. + TF1 rfnc; ///< Function used for r-dependence. +}; + +#endif // VELOMATERIAL_H -- GitLab From c61622acbfe70ff50ab3c02642d3edaf8768a770 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 27 Sep 2024 14:51:15 +0100 Subject: [PATCH 26/70] reduce print statements and clean up README --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 28 ++--- bu2kdarkscalar_darkscalar2hh/README.md | 138 +++++++++++-------------- bu2kdarkscalar_darkscalar2hh/job.py | 6 +- 3 files changed, 78 insertions(+), 94 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 7ed3d85d0b..16152ce1c8 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -344,20 +344,20 @@ class Ntuple: """ if mcp: # mcp: monte carlo particle, this is a boolean if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B - print(f"mcp.pid is set to {mcp.pid}") + #print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): #only when we inspect pid do we know the charges associated to hadron0 and hadron1 if mcp.pid in (-211, -321): dptype = "hm" - print(f"found mcp particle which is hm with pid {mcp.pid}") + #print(f"found mcp particle which is hm with pid {mcp.pid}") elif mcp.pid in (211, 321): dptype = "hp" - print(f"found mcp particle which is hp with pid {mcp.pid}") + #print(f"found mcp particle which is hp with pid {mcp.pid}") elif dptype == "K": if mcp.pid in (321, -321): dptype = "K" - print(f"found mcp particle which is K with pid {mcp.pid}") + #print(f"found mcp particle which is K with pid {mcp.pid}") pass else: raise ValueError(f"mcp.pid {mcp.pid} not recognized") @@ -842,20 +842,20 @@ def fill_ntuple( hh: "str", genTool, ): - print(f"hh is {hh}") + #print(f"hh is {hh}") """Assign values to the ntuple.""" year = int(DaVinci().DataType) def get_particles(candloc): - print(f"candidate_loc is {candloc}") + #print(f"candidate_loc is {candloc}") try: particles = tes[candloc] #print(particles) len(particles) size = len(particles) - print("found {0} particles".format(size)) + #print("found {0} particles".format(size)) except: - print("no particles found") + #print("no particles found") particles = [] return particles @@ -872,14 +872,14 @@ def fill_ntuple( # Fill the particles. all_Bparticles = get_particles(candidate_loc) - print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") + #print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") if DaVinci().Simulation: mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool #print(mct) mct.linkpars() mcpstore = get_particles('/Event/AllStreams/MC/Particles') - print(f"got {len(mcpstore)} B mesons from mcp store") + #print(f"got {len(mcpstore)} B mesons from mcp store") mct.deltarlink(mcpstore) mct.categorize() else: @@ -889,20 +889,20 @@ def fill_ntuple( isfilled = False for all_Bparticle in all_Bparticles: - print("its working") + #print("its working") args, kwargs = [all_Bparticle], {"year": year, "run": rnum, "mctool": mct} boolVal = ntuple.fillPrt(*args, **kwargs, check=True) - print("I find bool is {0}".format(boolVal)) + #print("I find bool is {0}".format(boolVal)) # if not all parricles are linked if ntuple.fillPrt(*args, **kwargs, check=True): ntuple.fillPrt(*args, **kwargs) isfilled = True - print("isfilled has been set to {0}".format(isfilled)) + #print("isfilled has been set to {0}".format(isfilled)) if len(all_Bparticles) > 0 and isfilled: - print("conditions for filling tuple met") + #print("conditions for filling tuple met") ntuple.fill() ntuple.clear() diff --git a/bu2kdarkscalar_darkscalar2hh/README.md b/bu2kdarkscalar_darkscalar2hh/README.md index 053db7f67e..f31cb6f5e5 100644 --- a/bu2kdarkscalar_darkscalar2hh/README.md +++ b/bu2kdarkscalar_darkscalar2hh/README.md @@ -48,81 +48,65 @@ the data for the first daughter particle (prt0) would be stored at `tree.trk_<va ### List of branches added to the ntuple (INCOMPLETE) -*Br 0 :tag_idx_pvr : vector Vector for tags in the event giving the index of the associated pv in the array of associated ‘pvr’ objects found in this event. There should be one entry per tag. - -*Br 1 :tag_idx_prt0 : vector Vector for tags in the event giving the index of the first daughter particle -*Br 2 :tag_idx_prt1 : vector …second daughter… - -*Br 3 :tag_pre_prt0 : vector Vector for tags in the event giving the prefix of the first daughter (0=tag, 1=trk) -*Br 4 :tag_pre_prt1 : vector …second daughter… - -*Br 5 :tag_pid : vector Vector for tags in the event giving the particle ID number. - -*Br 6 :tag_px : vector x momentum of the tag -*Br 7 :tag_py : vector y -*Br 8 :tag_pz : vector z -*Br 9 :tag_e : vector computed energy using particle ID and four-momentum - -*Br 10 :tag_x : vector x position of the endvertex associated to the tag -*Br 11 :tag_y : vector y -*Br 12 :tag_z : vector z - -*Br 13 :tag_dx : vector uncertainty on x position, determined from its covariance matrix -*Br 14 :tag_dy : vector -*Br 15 :tag_dz : vector - -*Br 16 :tag_m : vector mass after vertex-fit -*Br 17 :tag_dm : vector ‘error’ on the vertex fit mass - - -*Br 18 :tag_mm : vector ‘measured-mass’ (i.e. before vertex fit) -*Br 19 :tag_dm : vector ‘error’ on the measured mass - -*Br 20 :tag_doca : vector maximum doca between the momenta of the daughters of this vertex -*Br 21 :tag_chi2 : vector chi2 of the vertex fit - -*Br 20 :tag_dtf_chi2 : vector chi2 of the DTF refit which requires that the particle should originate from its PV. -1 if fails - -*Br 21 :tag_ip : vector use loki distance calculator to find IP wrt associated PV -*Br 22 :tag_ip_chi2 : vector chi2 of that IP - -*Br 23 :tag_fd : vector uses same calculator to find distance between this vertex and pv -*Br 24 :tag_fd_chi2 : vector chi2 of that FD - -*Br 25 :tag_vt_tip : vector returns true if flight distance of a vertex intercepts a foil tip -*Br 26 :tag_vt_d : vector (offset, uncertainty weighted, scaled) material distance -*Br 27 :tag_tos0 : vector the index corresponds to the L0, Hlt1, or Hlt2 triggers -*Br 31 :tag_tis0 : vector ??? - -*Br 32 :trk_idx_pvr : vector index of the associated pvr in the list of pvr’s found for this event -*Br 33 :trk_pid : vector pid of the charged particles: always 11 (electron) -*Br 34 :trk_px : vector momentum component before any refitting -*Br 35 :trk_py : vector -*Br 36 :trk_pz : vector -*Br 37 :trk_e : vector -*Br 38 :trk_pnn_e : vector probnne -*Br 39 :trk_pnn_pi : vector probnnpi -*Br 40 :trk_pnn_ghost : vector probnnghost -*Br 41 :trk_ip : vector ip with respect to associated PV, calculated using the LoKi distance calculator -*Br 42 :trk_ip_chi2 : vector ipchi2 as above -*Br 43 :trk_vt_miss : vector returns true if the expected first hit of a track is missed, given a flight direction and vertex - -*Br 44 :trk_x0 : vector ??? position for first hit in the VELO -*Br 45 :trk_y0 : vector y… -*Br 46 :trk_z0 : vector z… -*Br 47 :trk_t0 : vector -*Br 48 :trk_p0 : vector - -*Br 49 :trk_x1 : vector ??? position for second hit in the VELO -*Br 50 :trk_y1 : vector -*Br 51 :trk_z1 : vector -*Br 52 :trk_t1 : vector -*Br 53 :trk_p1 : vector - -*Br 59 :pvr_x : vector position and uncertainty on any associated primary vertex related to a filled particle -*Br 60 :pvr_y : vector -*Br 61 :pvr_z : vector -*Br 62 :pvr_dx : vector -*Br 63 :pvr_dy : vector -*Br 64 :pvr_dz : vector +| Branch Number | Branch Name | Associated Data Type | Description | +|:----:|:-------------:|:------:|:----------------------------------:| +| 0 | tag_idx_pvr | vector | each element of vector corresponds to a tag in the event and gives the index of the tag's associated PV in the array of associated ‘pvr’ objects found in this event. There should be one entry per tag! | +| 1 | tag_idx_prt0 | vector | each element of vector corresponds to a tag in the event and gives the index of the first daughter particle | +| 2 | tag_idx_prt1 | vector | each element of vector corresponds to a tag in the event and gives the index of the second daughter particle | +| 3 | tag_pre_prt0 | vector | each element of vector corresponds to a tag in the event and gives the prefix of the first daughter (0=tag, 1=trk) | +| 4 | tag_pre_prt1 | vector | each element of vector corresponds to a tag in the event and gives the prefix of the second daughter (0=tag, 1=trk) | +| 5 | tag_pid | vector | each element of vector corresponds to a tag in the event and gives the particle ID number of that tag | +| 6 | tag_px | vector | x momentum of the tag | +| 7 | tag_py | vector | y momentum of the tag | +| 8 | tag_pz | vector | z momentum of the tag | +| 9 | tag_e | vector | computed energy using particle ID and four-momentum of the tag | +| 10 | tag_x | vector | x position of the end vertex associated to the tag | +| 11 | tag_y | vector | y position of the end vertex associated to the tag | +| 12 | tag_z | vector | z position of the end vertex associated to the tag | +| 13 | tag_dx | vector | uncertainty on x position of the end vertex associated to the tag, determined from its covariance matrix | +| 14 | tag_dy | vector | uncertainty on y position of the end vertex associated to the tag, determined from its covariance matrix | +| 15 | tag_dz | vector | uncertainty on z position of the end vertex associated to the tag, determined from its covariance matrix | +| 16 | tag_m | vector | mass after vertex fit | +| 17 | tag_dm | vector | uncertainty on the vertex fit mass | +| 18 | tag_mm | vector | measured mass (i.e. before vertex fit) | +| 19 | tag_dm | vector | uncertainty on the measured mass | +| 20 | tag_doca | vector | maximum doca between the momenta of the daughters of tag | +| 21 | tag_chi2 | vector | chi2 of the vertex fit | +| 20 | tag_dtf_chi2 | vector | chi2 of the DTF refit which requires that the particle should originate from its PV. -1 if fails | +| 21 | tag_ip | vector | use LoKi distance calculator to find IP wrt associated PV | +| 22 | tag_ip_chi2 | vector | chi2 of IP | +| 23 | tag_fd | vector | use LoKi distance calculator to find distance between this vertex and PV | +| 24 | tag_fd_chi2 | vector | chi2 of flight distance | +| 25 | tag_vt_tip | vector | returns true if flight distance of a vertex intercepts a foil tip | +| 26 | tag_vt_d | vector | (offset, uncertainty weighted, scaled) material distance | +| 27 | tag_tos0 | vector | the index corresponds to the L0, Hlt1, or Hlt2 triggers +| 31 | tag_tis0 | vector | ??? +| 32 | trk_idx_pvr | vector | index of the associated pvr in the list of pvr’s found for this event +| 33 | trk_pid | vector | pid of the charged particle tracks (should be pion or kaon) | +| 34 | trk_px | vector | x 4-momentum component before any refitting | +| 35 | trk_py | vector | y 4-momentum component before any refitting | +| 36 | trk_pz | vector | z 4-momentum component before any refitting | +| 37 | trk_e | vector | energy 4-momentum component before any refitting | +| 38 | trk_pnn_e | vector | probnne | +| 39 | trk_pnn_pi | vector | probnnpi | +| 40 | trk_pnn_ghost | vector | probnnghost | +| 41 | trk_ip | vector | IP with respect to associated PV, calculated using the LoKi distance calculator +| 42 | trk_ip_chi2 | vector | ipchi2 as above +| 43 | trk_vt_miss | vector | returns true if the expected first hit of a track is missed, given a flight direction and vertex +| 44 | trk_x0 | vector | x position of first hit in the VELO | +| 45 | trk_y0 | vector | y position of first hit in the VELO | +| 46 | trk_z0 | vector | z position of first hit in the VELO | +| 47 | trk_t0 | vector | time position of first hit in the VELO | +| 48 | trk_p0 | vector | momentum of first hit in the VELO | +| 49 | trk_x1 | vector | x position of second hit in the VELO | +| 50 | trk_y1 | vector | y position of second hit in the VELO | +| 51 | trk_z1 | vector | z position of second hit in the VELO | +| 52 | trk_t1 | vector | time position of second hit in the VELO | +| 53 | trk_p1 | vector | momentum position of second hit in the VELO | +| 59 | pvr_x | vector | x position of any associated primary vertex related to a filled particle | +| 60 | pvr_y | vector | y position of any associated primary vertex related to a filled particle| +| 61 | pvr_z | vector | z position of any associated primary vertex related to a filled particle| +| 62 | pvr_dx | vector | uncertainty on x positon of any associated primary vertex related to a filled particle | +| 63 | pvr_dy | vector | uncertainty on y positon of any associated primary vertex related to a filled particle | +| 64 | pvr_dz | vector | uncertainty on z positon of any associated primary vertex related to a filled particle | diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 4f83d69785..fb7c28691f 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -93,7 +93,7 @@ try: shutil.copy(this_dir / "pars.root", Path.cwd()) except ModuleNotFoundError: NOfEvents = DaVinci().EvtMax - print(f"number of events is {NOfEvents}") + #print(f"number of events is {NOfEvents}") output_filename = DaVinci().TupleFile # FIXME: Turn off lumi tuple or it overwrites ntuple. Cleverer solution possible @@ -155,10 +155,10 @@ while (NOfEvents == -1) or (event < NOfEvents): if not bool(tes["/Event"]): break event += 1 - print(f"found event {event}") + #print(f"found event {event}") fill_ntuple(ntuple, tes, candloc, hh, genTool) - print("filled") + #print("filled") # Close. -- GitLab From a8cf00e26c19154b8c1ae9a53c46fe71db7bad11 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 2 Oct 2024 18:06:57 +0100 Subject: [PATCH 27/70] comment out print statements --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 46 ++++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 45e2273fdf..8f9afeb0c6 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -184,7 +184,7 @@ class MCEvent: of MCEvent [bool] Bs : list of combined all_Bs with daughter particles in daughter list [list(MCPart)] """ - print(f"hh is {hh}") + #print(f"hh is {hh}") self.hcuts = False self.links = False self.iscategorized = False @@ -205,7 +205,7 @@ class MCEvent: for ddp in dp.endVertex().outgoingParticlesVector(): hadron = MCPart(ddp) hadron.update(newpnnk=PROBNNk(ddp),newpnnpi=PROBNNpi(ddp),newpnng=PROBNNghost(ddp),newtrgp=TRGHOSTPROB(ddp)) - print(hadron.pid) + #print(hadron.pid) if hh == "KK": hhPID = 321 @@ -218,7 +218,7 @@ class MCEvent: dps[0] = MCPart(dp,dl=hadrons,sv=svcoor) B = MCPart(all_B,dl=dps) self.Bs += [B] - print(f"self.Bs is {self.Bs}") + #print(f"self.Bs is {self.Bs}") def linkpars(self): """ @@ -254,12 +254,12 @@ class MCEvent: if MCID(rel_K) == B.dl[1].pid: B.dl[1].update(newclink=True) else: B.dl[1].update(newclink=False) - print(f"type of B dl dl is {type(B.dl[0].dl[0])}") - print(f"type of B is {type(B)}") - print(f"type is {type(B.dl[0])}") - print(f"B dl is {B.dl}") - print(f"B dl dl is {B.dl[0].dl}") - print(f"B is {B}") + #print(f"type of B dl dl is {type(B.dl[0].dl[0])}") + #print(f"type of B is {type(B)}") + #print(f"type is {type(B.dl[0])}") + #print(f"B dl is {B.dl}") + #print(f"B dl dl is {B.dl[0].dl}") + #print(f"B is {B}") rels = self.mctool.relatedMCPs(B.dl[0].dl[0].par) w = 0 rel_hadron_minus = None @@ -375,36 +375,36 @@ class MCEvent: if not self.links: self.linkpars() for B in self.Bs: - print("looping over Bs") + #print("looping over Bs") if not B.alllinked: - print("not all Bs are linked") + #print("not all Bs are linked") for recp in [B.dl[1],B.dl[0].dl[0],B.dl[0].dl[1]]: - print("looping over recps") + #print("looping over recps") if not recp.lpar: - print("if recp has no linked particle") + #print("if recp has no linked particle") mindr = 99999999999 parlink = None - print(f"mcp store is {mcpstore}") + #print(f"mcp store is {mcpstore}") #print(f"mcp store contains {len(mcpstore)} mc particles") thereExistsMCPIDequalRECPID = False for mcp in mcpstore: - print(f"mc particle is {mcp}") - print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") + #print(f"mc particle is {mcp}") + #print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") if MCID(mcp) == recp.pid: thereExistsMCPIDequalRECPID = True - print("mc pid is same as rec particle pid") + #print("mc pid is same as rec particle pid") dphi = MCPHI(mcp) - PHI(recp.par) deta = MCETA(mcp) - ETA(recp.par) deltar = sqrt(dphi**2 + deta**2) - print(f"deltar is {deltar}") - print(f"mindr is {mindr}") + #print(f"deltar is {deltar}") + #print(f"mindr is {mindr}") if deltar < mindr: mindr = deltar - print(f"mindr is {mindr}") + #print(f"mindr is {mindr}") parlink = mcp - print(f"parlink is {parlink}") + #print(f"parlink is {parlink}") if thereExistsMCPIDequalRECPID == False: - print("no mc particle with same pid as rec particle") + #print("no mc particle with same pid as rec particle") break recp.update(newlpar=MCPart(parlink,orvrt=parlink.originVertex().type())) if MCID(parlink) == recp.pid: recp.update(newclink=True) @@ -425,7 +425,7 @@ class MCEvent: # i guess L418 shouldn't be being called - is this checking if all particles have been linked? # in which case we should be checking if all particles have been linked to the same particle type if thereExistsMCPIDequalRECPID == False: - print("no mc particle with same pid as rec particle") + #print("no mc particle with same pid as rec particle") break if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: -- GitLab From 1c7acaedb6a6ab98c2b5445d8c8bdf7664e43790 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 4 Oct 2024 13:32:38 +0100 Subject: [PATCH 28/70] selreports not found for mc, comment out for now --- bu2kdarkscalar_darkscalar2hh/job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index fb7c28691f..4f76ad366f 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -133,7 +133,8 @@ for stage in ("Hlt1", "Hlt2", "Strip/Phys"): ToolSvc().addTool(TriggerTisTos, stage + "TriggerTisTos") tool = getattr(ToolSvc(), stage + "TriggerTisTos") tool.HltDecReportsLocation = f"/Event/{stage}/DecReports" - tool.HltSelReportsLocation = f"/Event/{stage}/SelReports" + if not DaVinci().Simulation: + tool.HltSelReportsLocation = f"/Event/{stage}/SelReports" # ================================================================================= # Run. -- GitLab From 1e6b548c65adf50058f55b5d9efa7c61f24b96d7 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 7 Oct 2024 14:31:02 +0100 Subject: [PATCH 29/70] minor --- bu2kdarkscalar_darkscalar2hh/job.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 4f76ad366f..fb7c28691f 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -133,8 +133,7 @@ for stage in ("Hlt1", "Hlt2", "Strip/Phys"): ToolSvc().addTool(TriggerTisTos, stage + "TriggerTisTos") tool = getattr(ToolSvc(), stage + "TriggerTisTos") tool.HltDecReportsLocation = f"/Event/{stage}/DecReports" - if not DaVinci().Simulation: - tool.HltSelReportsLocation = f"/Event/{stage}/SelReports" + tool.HltSelReportsLocation = f"/Event/{stage}/SelReports" # ================================================================================= # Run. -- GitLab From 92cc67a16d5f5cd816f5ce2d96f2500b36d2c167 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 11 Oct 2024 16:14:09 +0100 Subject: [PATCH 30/70] new jobs --- bu2kdarkscalar_darkscalar2hh/info.yaml | 27 ++------------------------ 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index acd6d538d9..fe7ae9b3b4 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,6 +1,5 @@ {%- set polarities = ["Down", "Up"]%} -{%- set datasets = ["PiPi", "KK"]%} -{%- set MCdatasets = [('PiPi', 18, 12103024, 6500, '18', '34NoPrescalingFlagged')]%} +{%- set MCdatasets = [('KK', 18, 12103012, 6500, '18', '34NoPrescalingFlagged')]%} {%- for polarity in polarities %} {%- for hh, year, eventnumber, energy, reco, strip in MCdatasets %} @@ -13,7 +12,7 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: inform: - eleanor.whiter@cern.ch input: - bk_query: /MC/20{{year}}/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Nu1.6-25ns-Pythia8/Sim09h/Trig0x617d18a4/Reco{{year}}/Turbo05-WithTurcal/Stripping{{strip}}/{{eventnumber}}/ALLSTREAMS.MDST + bk_query: /MC/20{{year}}/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Nu1.6-25ns-Pythia8/Sim10d/Trig0x617d18a4/Reco{{year}}/Turbo05-WithTurcal/Stripping{{strip}}/{{eventnumber}}/ALLSTREAMS.MDST options: command: - python @@ -24,26 +23,4 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: {%- endfor %} - -{%- for hh in datasets %} - -my_B2KDarkScalar_DarkScalar2{{hh}}_2018_Mag{{polarity}}_Data_job: - application: DaVinci/v46r8 - wg: QEE - automatically_configure: yes - turbo: no - inform: - - eleanor.whiter@cern.ch - input: - bk_query: "/LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34r0p1/90000000/LEPTONIC.MDST" - options: - command: - - python - files: - - job.py - - hh_{{hh}}.py - output: b2kdarkscalar.root - -{%- endfor %} - {%- endfor %} \ No newline at end of file -- GitLab From 82789f3f142e444cc906f92256ad9fd715297270 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 6 Nov 2024 10:43:01 +0000 Subject: [PATCH 31/70] tweaks --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 2 ++ bu2kdarkscalar_darkscalar2hh/info.yaml | 27 +++++++++++++++++++- bu2kdarkscalar_darkscalar2hh/job.py | 6 +++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 8f9afeb0c6..2cc6a3d58f 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -535,6 +535,8 @@ class MCEvent: if K.lpar.mother == hadron_minus.lpar.mother.mother.par == hadron_plus.lpar.mother.mother.par: # Check K, hm and hp all came from the same particle if abs(K.lpar.mother.pid) == 521: # check this was a B self.dec: B.update(newbgtype=0) + + else: B.update(newbgtype=9) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index fe7ae9b3b4..455cfcbfaf 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,5 +1,10 @@ {%- set polarities = ["Down", "Up"]%} -{%- set MCdatasets = [('KK', 18, 12103012, 6500, '18', '34NoPrescalingFlagged')]%} +{%- set MCdatasets = [('KK', 18, 12103012, 6500, '18', '34NoPrescalingFlagged'), + ('KK', 18, 12103047, 6500, '18', '34NoPrescalingFlagged'), + ('KK', 18, 12103056, 6500, '18', '34NoPrescalingFlagged')]%} + + +/MC/2018/12103047/Beam6500GeV-2018-MagDown-Nu1.6-25ns-Pythia8/Sim10d/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/ALLSTREAMS.MDST {%- for polarity in polarities %} {%- for hh, year, eventnumber, energy, reco, strip in MCdatasets %} @@ -23,4 +28,24 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: {%- endfor %} + +my_B2KDarkScalar_DarkScalar2{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: + application: DaVinci/v46r8 + wg: QEE + automatically_configure: yes + turbo: no + inform: + - eleanor.whiter@cern.ch + input: + bk_query: /MC/20{{year}}/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Nu1.6-25ns-Pythia8/Sim10d/Trig0x617d18a4/Reco{{year}}/Turbo05-WithTurcal/Stripping{{strip}}/{{eventnumber}}/ALLSTREAMS.MDST + options: + command: + - python + files: + - job.py + - hh_{{hh}}.py + output: b2kdarkscalar_darkscalar2hh.root + +{%- endfor %} + {%- endfor %} \ No newline at end of file diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index fb7c28691f..96467ab384 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -121,9 +121,11 @@ elif DaVinci().InputType == "DST": stream = 'AllStreams' """ if hh == "KK": - candloc = '/Event/' + stream + '/Phys/B2KX2KKDarkBosonLine/Particles' + line ='B2KX2KKDarkBosonLine' elif hh == "PiPi": - candloc = '/Event/' + stream + '/Phys/B2KX2PiPiDarkBosonLine/Particles' + line = 'B2KX2PiPiDarkBosonLine' + +candloc = '/Event/' + stream + '/Phys/' + line + '/Particles' print(f"Using candidate location: {candloc}") -- GitLab From 914d9fa26ad4f5e3e073994a03ef1aad756428f6 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 6 Nov 2024 10:50:21 +0000 Subject: [PATCH 32/70] change format --- bu2kdarkscalar_darkscalar2hh/info.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 455cfcbfaf..4c2e93d1c3 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,13 +1,13 @@ {%- set polarities = ["Down", "Up"]%} -{%- set MCdatasets = [('KK', 18, 12103012, 6500, '18', '34NoPrescalingFlagged'), - ('KK', 18, 12103047, 6500, '18', '34NoPrescalingFlagged'), +{%- set MCnormalisationDatasets = [('KK', 18, 12103012, 6500, '18', '34NoPrescalingFlagged')]%} +{%- set MCsignalDatasets = [('KK', 18, 12103047, 6500, '18', '34NoPrescalingFlagged'), ('KK', 18, 12103056, 6500, '18', '34NoPrescalingFlagged')]%} /MC/2018/12103047/Beam6500GeV-2018-MagDown-Nu1.6-25ns-Pythia8/Sim10d/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/ALLSTREAMS.MDST {%- for polarity in polarities %} -{%- for hh, year, eventnumber, energy, reco, strip in MCdatasets %} +{%- for hh, year, eventnumber, energy, reco, strip in MCnormalisationDatasets %} my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: application: DaVinci/v46r8 @@ -28,6 +28,7 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: {%- endfor %} +{%- for hh, year, eventnumber, energy, reco, strip in MCsignalDatasets %} my_B2KDarkScalar_DarkScalar2{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: application: DaVinci/v46r8 -- GitLab From 742d3dd23f1ac11363885d526a2b62156d67737f Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 6 Nov 2024 11:09:35 +0000 Subject: [PATCH 33/70] fix --- bu2kdarkscalar_darkscalar2hh/info.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 4c2e93d1c3..d9a2b7d37a 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,10 +1,9 @@ {%- set polarities = ["Down", "Up"]%} {%- set MCnormalisationDatasets = [('KK', 18, 12103012, 6500, '18', '34NoPrescalingFlagged')]%} {%- set MCsignalDatasets = [('KK', 18, 12103047, 6500, '18', '34NoPrescalingFlagged'), - ('KK', 18, 12103056, 6500, '18', '34NoPrescalingFlagged')]%} + ('PiPi', 18, 12103056, 6500, '18', '34NoPrescalingFlagged')]%} -/MC/2018/12103047/Beam6500GeV-2018-MagDown-Nu1.6-25ns-Pythia8/Sim10d/Trig0x617d18a4/Reco18/Turbo05-WithTurcal/Stripping34NoPrescalingFlagged/ALLSTREAMS.MDST {%- for polarity in polarities %} {%- for hh, year, eventnumber, energy, reco, strip in MCnormalisationDatasets %} -- GitLab From 73e6a7cb6f95cd823109f8fcc470b9b0894a66a5 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 11 Nov 2024 15:45:24 +0000 Subject: [PATCH 34/70] test --- bu2kdarkscalar_darkscalar2hh/info.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index d9a2b7d37a..ae377d3eec 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -3,8 +3,6 @@ {%- set MCsignalDatasets = [('KK', 18, 12103047, 6500, '18', '34NoPrescalingFlagged'), ('PiPi', 18, 12103056, 6500, '18', '34NoPrescalingFlagged')]%} - - {%- for polarity in polarities %} {%- for hh, year, eventnumber, energy, reco, strip in MCnormalisationDatasets %} -- GitLab From cee258e09a1fc1a910681204b02abbe5c893d61c Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 12 Nov 2024 10:26:12 +0000 Subject: [PATCH 35/70] debug --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 48 ++++++++++---------- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 32 ++++++------- bu2kdarkscalar_darkscalar2hh/job.py | 6 +-- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 2cc6a3d58f..a0eca08aac 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -184,7 +184,7 @@ class MCEvent: of MCEvent [bool] Bs : list of combined all_Bs with daughter particles in daughter list [list(MCPart)] """ - #print(f"hh is {hh}") + print(f"hh is {hh}") self.hcuts = False self.links = False self.iscategorized = False @@ -205,7 +205,7 @@ class MCEvent: for ddp in dp.endVertex().outgoingParticlesVector(): hadron = MCPart(ddp) hadron.update(newpnnk=PROBNNk(ddp),newpnnpi=PROBNNpi(ddp),newpnng=PROBNNghost(ddp),newtrgp=TRGHOSTPROB(ddp)) - #print(hadron.pid) + print(hadron.pid) if hh == "KK": hhPID = 321 @@ -218,7 +218,7 @@ class MCEvent: dps[0] = MCPart(dp,dl=hadrons,sv=svcoor) B = MCPart(all_B,dl=dps) self.Bs += [B] - #print(f"self.Bs is {self.Bs}") + print(f"self.Bs is {self.Bs}") def linkpars(self): """ @@ -254,12 +254,12 @@ class MCEvent: if MCID(rel_K) == B.dl[1].pid: B.dl[1].update(newclink=True) else: B.dl[1].update(newclink=False) - #print(f"type of B dl dl is {type(B.dl[0].dl[0])}") - #print(f"type of B is {type(B)}") - #print(f"type is {type(B.dl[0])}") - #print(f"B dl is {B.dl}") - #print(f"B dl dl is {B.dl[0].dl}") - #print(f"B is {B}") + print(f"type of B dl dl is {type(B.dl[0].dl[0])}") + print(f"type of B is {type(B)}") + print(f"type is {type(B.dl[0])}") + print(f"B dl is {B.dl}") + print(f"B dl dl is {B.dl[0].dl}") + print(f"B is {B}") rels = self.mctool.relatedMCPs(B.dl[0].dl[0].par) w = 0 rel_hadron_minus = None @@ -375,36 +375,36 @@ class MCEvent: if not self.links: self.linkpars() for B in self.Bs: - #print("looping over Bs") + print("looping over Bs") if not B.alllinked: - #print("not all Bs are linked") + print("not all Bs are linked") for recp in [B.dl[1],B.dl[0].dl[0],B.dl[0].dl[1]]: - #print("looping over recps") + print("looping over recps") if not recp.lpar: - #print("if recp has no linked particle") + print("if recp has no linked particle") mindr = 99999999999 parlink = None - #print(f"mcp store is {mcpstore}") - #print(f"mcp store contains {len(mcpstore)} mc particles") + print(f"mcp store is {mcpstore}") + print(f"mcp store contains {len(mcpstore)} mc particles") thereExistsMCPIDequalRECPID = False for mcp in mcpstore: - #print(f"mc particle is {mcp}") - #print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") + print(f"mc particle is {mcp}") + print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") if MCID(mcp) == recp.pid: thereExistsMCPIDequalRECPID = True - #print("mc pid is same as rec particle pid") + print("mc pid is same as rec particle pid") dphi = MCPHI(mcp) - PHI(recp.par) deta = MCETA(mcp) - ETA(recp.par) deltar = sqrt(dphi**2 + deta**2) - #print(f"deltar is {deltar}") - #print(f"mindr is {mindr}") + print(f"deltar is {deltar}") + print(f"mindr is {mindr}") if deltar < mindr: mindr = deltar - #print(f"mindr is {mindr}") + print(f"mindr is {mindr}") parlink = mcp - #print(f"parlink is {parlink}") + print(f"parlink is {parlink}") if thereExistsMCPIDequalRECPID == False: - #print("no mc particle with same pid as rec particle") + print("no mc particle with same pid as rec particle") break recp.update(newlpar=MCPart(parlink,orvrt=parlink.originVertex().type())) if MCID(parlink) == recp.pid: recp.update(newclink=True) @@ -425,7 +425,7 @@ class MCEvent: # i guess L418 shouldn't be being called - is this checking if all particles have been linked? # in which case we should be checking if all particles have been linked to the same particle type if thereExistsMCPIDequalRECPID == False: - #print("no mc particle with same pid as rec particle") + print("no mc particle with same pid as rec particle") break if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 16152ce1c8..a841c1285c 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -344,20 +344,20 @@ class Ntuple: """ if mcp: # mcp: monte carlo particle, this is a boolean if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B - #print(f"mcp.pid is set to {mcp.pid}") + print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): #only when we inspect pid do we know the charges associated to hadron0 and hadron1 if mcp.pid in (-211, -321): dptype = "hm" - #print(f"found mcp particle which is hm with pid {mcp.pid}") + print(f"found mcp particle which is hm with pid {mcp.pid}") elif mcp.pid in (211, 321): dptype = "hp" - #print(f"found mcp particle which is hp with pid {mcp.pid}") + print(f"found mcp particle which is hp with pid {mcp.pid}") elif dptype == "K": if mcp.pid in (321, -321): dptype = "K" - #print(f"found mcp particle which is K with pid {mcp.pid}") + print(f"found mcp particle which is K with pid {mcp.pid}") pass else: raise ValueError(f"mcp.pid {mcp.pid} not recognized") @@ -842,20 +842,20 @@ def fill_ntuple( hh: "str", genTool, ): - #print(f"hh is {hh}") + print(f"hh is {hh}") """Assign values to the ntuple.""" year = int(DaVinci().DataType) def get_particles(candloc): - #print(f"candidate_loc is {candloc}") + print(f"candidate_loc is {candloc}") try: particles = tes[candloc] - #print(particles) + print(particles) len(particles) size = len(particles) - #print("found {0} particles".format(size)) + print("found {0} particles".format(size)) except: - #print("no particles found") + print("no particles found") particles = [] return particles @@ -872,14 +872,14 @@ def fill_ntuple( # Fill the particles. all_Bparticles = get_particles(candidate_loc) - #print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") + print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") if DaVinci().Simulation: mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool - #print(mct) + print(mct) mct.linkpars() mcpstore = get_particles('/Event/AllStreams/MC/Particles') - #print(f"got {len(mcpstore)} B mesons from mcp store") + print(f"got {len(mcpstore)} B mesons from mcp store") mct.deltarlink(mcpstore) mct.categorize() else: @@ -889,20 +889,20 @@ def fill_ntuple( isfilled = False for all_Bparticle in all_Bparticles: - #print("its working") + print("its working") args, kwargs = [all_Bparticle], {"year": year, "run": rnum, "mctool": mct} boolVal = ntuple.fillPrt(*args, **kwargs, check=True) - #print("I find bool is {0}".format(boolVal)) + print("I find bool is {0}".format(boolVal)) # if not all parricles are linked if ntuple.fillPrt(*args, **kwargs, check=True): ntuple.fillPrt(*args, **kwargs) isfilled = True - #print("isfilled has been set to {0}".format(isfilled)) + print("isfilled has been set to {0}".format(isfilled)) if len(all_Bparticles) > 0 and isfilled: - #print("conditions for filling tuple met") + print("conditions for filling tuple met") ntuple.fill() ntuple.clear() diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 96467ab384..b33ad14a7c 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -93,7 +93,7 @@ try: shutil.copy(this_dir / "pars.root", Path.cwd()) except ModuleNotFoundError: NOfEvents = DaVinci().EvtMax - #print(f"number of events is {NOfEvents}") + print(f"number of events is {NOfEvents}") output_filename = DaVinci().TupleFile # FIXME: Turn off lumi tuple or it overwrites ntuple. Cleverer solution possible @@ -157,10 +157,10 @@ while (NOfEvents == -1) or (event < NOfEvents): if not bool(tes["/Event"]): break event += 1 - #print(f"found event {event}") + print(f"found event {event}") fill_ntuple(ntuple, tes, candloc, hh, genTool) - #print("filled") + print("filled") # Close. -- GitLab From 84aebcc6a5b9ab370d1e21442a8b9f6746ec0f09 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 18 Nov 2024 09:57:51 +0000 Subject: [PATCH 36/70] add break statement to stop multiple linking --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 70 ++++++++++++-------- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 32 ++++----- bu2kdarkscalar_darkscalar2hh/job.py | 8 +-- 3 files changed, 62 insertions(+), 48 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index a0eca08aac..69ca1317ff 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -184,7 +184,7 @@ class MCEvent: of MCEvent [bool] Bs : list of combined all_Bs with daughter particles in daughter list [list(MCPart)] """ - print(f"hh is {hh}") + #(f"hh is {hh}") self.hcuts = False self.links = False self.iscategorized = False @@ -205,7 +205,7 @@ class MCEvent: for ddp in dp.endVertex().outgoingParticlesVector(): hadron = MCPart(ddp) hadron.update(newpnnk=PROBNNk(ddp),newpnnpi=PROBNNpi(ddp),newpnng=PROBNNghost(ddp),newtrgp=TRGHOSTPROB(ddp)) - print(hadron.pid) + #print(hadron.pid) if hh == "KK": hhPID = 321 @@ -218,7 +218,7 @@ class MCEvent: dps[0] = MCPart(dp,dl=hadrons,sv=svcoor) B = MCPart(all_B,dl=dps) self.Bs += [B] - print(f"self.Bs is {self.Bs}") + #print(f"self.Bs is {self.Bs}") def linkpars(self): """ @@ -231,7 +231,9 @@ class MCEvent: """ self.allreglinked = True for B in self.Bs: - rels = self.mctool.relatedMCPs(B.dl[1].par) + rels = self.mctool.relatedMCPs(B.dl[1].par, '/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles') + #rels = self.mctool.relatedMCPs() + #print(f"rels is {rels}") w = 0 rel_K = None for rel in rels: @@ -254,13 +256,7 @@ class MCEvent: if MCID(rel_K) == B.dl[1].pid: B.dl[1].update(newclink=True) else: B.dl[1].update(newclink=False) - print(f"type of B dl dl is {type(B.dl[0].dl[0])}") - print(f"type of B is {type(B)}") - print(f"type is {type(B.dl[0])}") - print(f"B dl is {B.dl}") - print(f"B dl dl is {B.dl[0].dl}") - print(f"B is {B}") - rels = self.mctool.relatedMCPs(B.dl[0].dl[0].par) + rels = self.mctool.relatedMCPs(B.dl[0].dl[0].par, '/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles') w = 0 rel_hadron_minus = None for rel in rels: @@ -282,7 +278,7 @@ class MCEvent: if MCID(rel_hadron_minus) == B.dl[0].dl[0].pid: B.dl[0].dl[0].update(newclink=True) else: B.dl[0].dl[0].update(newclink=False) - rels = self.mctool.relatedMCPs(B.dl[0].dl[1].par) + rels = self.mctool.relatedMCPs(B.dl[0].dl[1].par, '/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles') w = 0 rel_hadron_plus = None for rel in rels: @@ -372,39 +368,44 @@ class MCEvent: self: MCEvent instance mcpstore: list of MC particles from the TES """ - if not self.links: self.linkpars() + if not self.links: self.linkpars() # if haven't run linkpars run it + count = 1 for B in self.Bs: - print("looping over Bs") + #print(f"looping over number {count} B particles stored in stripping line") + count += 1 if not B.alllinked: - print("not all Bs are linked") + #print("not all Bs are linked to MC particles already by linkpars so we need delta r matching") + recpcount = 0 for recp in [B.dl[1],B.dl[0].dl[0],B.dl[0].dl[1]]: - print("looping over recps") + #print(f"looping over {recpcount} recps") + recpcount += 1 if not recp.lpar: - print("if recp has no linked particle") + #print(f"if {abs(recp.pid)} has no linked particle") mindr = 99999999999 parlink = None - print(f"mcp store is {mcpstore}") - print(f"mcp store contains {len(mcpstore)} mc particles") + #print(f"mcp store is {mcpstore}") + #print(f"mcp store contains {len(mcpstore)} mc particles") thereExistsMCPIDequalRECPID = False for mcp in mcpstore: - print(f"mc particle is {mcp}") - print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") + #print(f"mc particle is {mcp}") + #print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") if MCID(mcp) == recp.pid: thereExistsMCPIDequalRECPID = True - print("mc pid is same as rec particle pid") + #print("found mc particle with same pid is same as rec particle pid") dphi = MCPHI(mcp) - PHI(recp.par) deta = MCETA(mcp) - ETA(recp.par) deltar = sqrt(dphi**2 + deta**2) - print(f"deltar is {deltar}") - print(f"mindr is {mindr}") + #print(f"deltar is {deltar}") + #print(f"mindr is {mindr}") if deltar < mindr: mindr = deltar - print(f"mindr is {mindr}") + #print(f"mindr is {mindr}") parlink = mcp - print(f"parlink is {parlink}") + #print(f"assign the rec particle this mc particle which is {parlink}") + break if thereExistsMCPIDequalRECPID == False: - print("no mc particle with same pid as rec particle") + #print("no mc particle with same pid as rec particle") break recp.update(newlpar=MCPart(parlink,orvrt=parlink.originVertex().type())) if MCID(parlink) == recp.pid: recp.update(newclink=True) @@ -413,22 +414,29 @@ class MCEvent: try: parlink.mother().originVertex().type() recp.lpar.update(newmother=MCPart(parlink.mother(),orvrt=parlink.mother().originVertex().type())) + if parlink.mother().originVertex().type() == 1: + #print("WARNING mother has origin vertex from pp collision") if parlink.mother().originVertex().type() != 1: + print("mother has origin vertex type != 1. i.e. not from pp collision") try: parlink.mother().mother().originVertex().type() recp.lpar.mother.update(newmother=MCPart(parlink.mother().mother(),orvrt=parlink.mother().mother().originVertex().type())) + #print("updating mother") + #print(f"mother of {recp.pid} is {recp.lpar.mother}") except: recp.lpar.mother.update(newmother=None) + #print("WARNING recp has no mother") except: recp.lpar.update(newmother=None) # i guess L418 shouldn't be being called - is this checking if all particles have been linked? # in which case we should be checking if all particles have been linked to the same particle type if thereExistsMCPIDequalRECPID == False: - print("no mc particle with same pid as rec particle") + #print("no mc particle with same pid as rec particle") break if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: + #print("all particles have been linked") B.update(newisdrmatched=True) if B.dl[1].lpar.pid == B.dl[1].pid and B.dl[0].dl[0].lpar.pid == B.dl[0].dl[0].pid and B.dl[0].dl[1].lpar.pid == B.dl[0].dl[1].pid: B.update(newcorlinked=True) @@ -516,9 +524,13 @@ class MCEvent: 1: MC normalisation (B+/- -> K+/- h- h+) 9: there was an error in MC truth matching """ + + #print("begin categorizing") if not self.links: self.linkpars() for B in self.Bs: + #print("categorise") + if self.corlin(B): K = B.dl[1] hadron_minus = B.dl[0].dl[0] @@ -526,6 +538,8 @@ class MCEvent: try: fillKpid = K.lpar.mother.pid except: fillKpid = 0 + #print(f"kaons mum is {K.lpar.mother}") + if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(K.lpar.par.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle and B (accessed by K origin vertex) has 3 decay products #if K is K+ this must be a B+, if K is K- this must be a B- if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: B.update(newbgtype=1) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index a841c1285c..16152ce1c8 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -344,20 +344,20 @@ class Ntuple: """ if mcp: # mcp: monte carlo particle, this is a boolean if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B - print(f"mcp.pid is set to {mcp.pid}") + #print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): #only when we inspect pid do we know the charges associated to hadron0 and hadron1 if mcp.pid in (-211, -321): dptype = "hm" - print(f"found mcp particle which is hm with pid {mcp.pid}") + #print(f"found mcp particle which is hm with pid {mcp.pid}") elif mcp.pid in (211, 321): dptype = "hp" - print(f"found mcp particle which is hp with pid {mcp.pid}") + #print(f"found mcp particle which is hp with pid {mcp.pid}") elif dptype == "K": if mcp.pid in (321, -321): dptype = "K" - print(f"found mcp particle which is K with pid {mcp.pid}") + #print(f"found mcp particle which is K with pid {mcp.pid}") pass else: raise ValueError(f"mcp.pid {mcp.pid} not recognized") @@ -842,20 +842,20 @@ def fill_ntuple( hh: "str", genTool, ): - print(f"hh is {hh}") + #print(f"hh is {hh}") """Assign values to the ntuple.""" year = int(DaVinci().DataType) def get_particles(candloc): - print(f"candidate_loc is {candloc}") + #print(f"candidate_loc is {candloc}") try: particles = tes[candloc] - print(particles) + #print(particles) len(particles) size = len(particles) - print("found {0} particles".format(size)) + #print("found {0} particles".format(size)) except: - print("no particles found") + #print("no particles found") particles = [] return particles @@ -872,14 +872,14 @@ def fill_ntuple( # Fill the particles. all_Bparticles = get_particles(candidate_loc) - print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") + #print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") if DaVinci().Simulation: mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool - print(mct) + #print(mct) mct.linkpars() mcpstore = get_particles('/Event/AllStreams/MC/Particles') - print(f"got {len(mcpstore)} B mesons from mcp store") + #print(f"got {len(mcpstore)} B mesons from mcp store") mct.deltarlink(mcpstore) mct.categorize() else: @@ -889,20 +889,20 @@ def fill_ntuple( isfilled = False for all_Bparticle in all_Bparticles: - print("its working") + #print("its working") args, kwargs = [all_Bparticle], {"year": year, "run": rnum, "mctool": mct} boolVal = ntuple.fillPrt(*args, **kwargs, check=True) - print("I find bool is {0}".format(boolVal)) + #print("I find bool is {0}".format(boolVal)) # if not all parricles are linked if ntuple.fillPrt(*args, **kwargs, check=True): ntuple.fillPrt(*args, **kwargs) isfilled = True - print("isfilled has been set to {0}".format(isfilled)) + #print("isfilled has been set to {0}".format(isfilled)) if len(all_Bparticles) > 0 and isfilled: - print("conditions for filling tuple met") + #print("conditions for filling tuple met") ntuple.fill() ntuple.clear() diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index b33ad14a7c..2c29a56ec3 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -12,8 +12,8 @@ import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -#import sys -#sys.path.append("../") +import sys +sys.path.append("../") from bu2kdarkscalar_darkscalar2hh.Ntuple import fill_ntuple, Ntuple @@ -157,10 +157,10 @@ while (NOfEvents == -1) or (event < NOfEvents): if not bool(tes["/Event"]): break event += 1 - print(f"found event {event}") + #print(f"found event {event}") fill_ntuple(ntuple, tes, candloc, hh, genTool) - print("filled") + #print("filled") # Close. -- GitLab From 74806a6e947d0ed10932c80d5243de5a3653dd57 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 18 Nov 2024 10:02:26 +0000 Subject: [PATCH 37/70] remove uneccasry line --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 69ca1317ff..015ca6d64f 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -233,7 +233,8 @@ class MCEvent: for B in self.Bs: rels = self.mctool.relatedMCPs(B.dl[1].par, '/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles') #rels = self.mctool.relatedMCPs() - #print(f"rels is {rels}") + print(f"rels is {rels}") + print(f"type rels is {type(rels)}") w = 0 rel_K = None for rel in rels: @@ -414,7 +415,6 @@ class MCEvent: try: parlink.mother().originVertex().type() recp.lpar.update(newmother=MCPart(parlink.mother(),orvrt=parlink.mother().originVertex().type())) - if parlink.mother().originVertex().type() == 1: #print("WARNING mother has origin vertex from pp collision") if parlink.mother().originVertex().type() != 1: print("mother has origin vertex type != 1. i.e. not from pp collision") -- GitLab From 6c0f7915e96c028a30cf5f9d0dbe6cd60448da53 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 18 Nov 2024 10:07:50 +0000 Subject: [PATCH 38/70] remove print --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 015ca6d64f..b54e6a1723 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -417,7 +417,7 @@ class MCEvent: recp.lpar.update(newmother=MCPart(parlink.mother(),orvrt=parlink.mother().originVertex().type())) #print("WARNING mother has origin vertex from pp collision") if parlink.mother().originVertex().type() != 1: - print("mother has origin vertex type != 1. i.e. not from pp collision") + #print("mother has origin vertex type != 1. i.e. not from pp collision") try: parlink.mother().mother().originVertex().type() recp.lpar.mother.update(newmother=MCPart(parlink.mother().mother(),orvrt=parlink.mother().mother().originVertex().type())) -- GitLab From a893f6d2d18b1d4d38ac176fdff802e6637a3eb9 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 18 Nov 2024 10:10:33 +0000 Subject: [PATCH 39/70] comment out lines when running on grid --- bu2kdarkscalar_darkscalar2hh/job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 2c29a56ec3..0ba52fe587 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -12,8 +12,8 @@ import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -import sys -sys.path.append("../") +#import sys +#sys.path.append("../") from bu2kdarkscalar_darkscalar2hh.Ntuple import fill_ntuple, Ntuple -- GitLab From 690a9e7c9e127ea2e612bcc08b66348ab1fa3bbc Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 4 Dec 2024 16:08:52 +0000 Subject: [PATCH 40/70] minor --- bu2kdarkscalar_darkscalar2hh/job.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 0ba52fe587..8d82cfa7ea 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -9,11 +9,10 @@ from typing import Union from Configurables import DaVinci, ToolSvc, TriggerTisTos import GaudiPython import ROOT - #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -#import sys -#sys.path.append("../") +import sys +sys.path.append("../") from bu2kdarkscalar_darkscalar2hh.Ntuple import fill_ntuple, Ntuple -- GitLab From f73c1cea7bd061e54535fb238f2d9bbfe152a6b4 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 4 Dec 2024 16:10:54 +0000 Subject: [PATCH 41/70] these lines comment out when remote --- bu2kdarkscalar_darkscalar2hh/job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 8d82cfa7ea..8edce87696 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -11,8 +11,8 @@ import GaudiPython import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -import sys -sys.path.append("../") +#import sys +#sys.path.append("../") from bu2kdarkscalar_darkscalar2hh.Ntuple import fill_ntuple, Ntuple -- GitLab From 480ce123057318a4585dd74f1f27083635468f74 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 6 Dec 2024 16:37:49 +0000 Subject: [PATCH 42/70] with comments - event 22! --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 2 +- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 4 ++-- bu2kdarkscalar_darkscalar2hh/job.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index b54e6a1723..684c36b1ca 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -231,7 +231,7 @@ class MCEvent: """ self.allreglinked = True for B in self.Bs: - rels = self.mctool.relatedMCPs(B.dl[1].par, '/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles') + rels = self.mctool.relatedMCPs(B.dl[1].par) #rels = self.mctool.relatedMCPs() print(f"rels is {rels}") print(f"type rels is {type(rels)}") diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 16152ce1c8..7c0a79bfd9 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -872,14 +872,14 @@ def fill_ntuple( # Fill the particles. all_Bparticles = get_particles(candidate_loc) - #print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") + print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") if DaVinci().Simulation: mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool #print(mct) mct.linkpars() mcpstore = get_particles('/Event/AllStreams/MC/Particles') - #print(f"got {len(mcpstore)} B mesons from mcp store") + print(f"got {len(mcpstore)} B mesons from mcp store") mct.deltarlink(mcpstore) mct.categorize() else: diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 8edce87696..e940dd3f36 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -11,8 +11,8 @@ import GaudiPython import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -#import sys -#sys.path.append("../") +import sys +sys.path.append("../") from bu2kdarkscalar_darkscalar2hh.Ntuple import fill_ntuple, Ntuple @@ -156,7 +156,7 @@ while (NOfEvents == -1) or (event < NOfEvents): if not bool(tes["/Event"]): break event += 1 - #print(f"found event {event}") + print(f"found event {event}") fill_ntuple(ntuple, tes, candloc, hh, genTool) #print("filled") -- GitLab From 952e2b879bd51b3d5bcbcc43274b61386ee8caac Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 21 Jan 2025 11:15:44 +0000 Subject: [PATCH 43/70] test --- bu2kdarkscalar_darkscalar2hh/job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index e940dd3f36..6a9f252b55 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -125,7 +125,7 @@ elif hh == "PiPi": line = 'B2KX2PiPiDarkBosonLine' candloc = '/Event/' + stream + '/Phys/' + line + '/Particles' - +#candloc = '/Event/' + '/Phys/' + line + '/Particles' print(f"Using candidate location: {candloc}") # ================================================================================= -- GitLab From 1118f17209403c7c59a7600e64412fabb51b7cad Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 21 Jan 2025 11:32:26 +0000 Subject: [PATCH 44/70] correct --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 684c36b1ca..b440ec7599 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -257,7 +257,7 @@ class MCEvent: if MCID(rel_K) == B.dl[1].pid: B.dl[1].update(newclink=True) else: B.dl[1].update(newclink=False) - rels = self.mctool.relatedMCPs(B.dl[0].dl[0].par, '/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles') + rels = self.mctool.relatedMCPs(B.dl[0].dl[0].par) w = 0 rel_hadron_minus = None for rel in rels: @@ -279,7 +279,7 @@ class MCEvent: if MCID(rel_hadron_minus) == B.dl[0].dl[0].pid: B.dl[0].dl[0].update(newclink=True) else: B.dl[0].dl[0].update(newclink=False) - rels = self.mctool.relatedMCPs(B.dl[0].dl[1].par, '/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles') + rels = self.mctool.relatedMCPs(B.dl[0].dl[1].par) w = 0 rel_hadron_plus = None for rel in rels: -- GitLab From b3f64792796f9ba09d99b08524e7ede62a487d31 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 22 Jan 2025 12:03:20 +0000 Subject: [PATCH 45/70] fix for when K mother none --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 34 ++++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index b440ec7599..f9cd99b99d 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -382,7 +382,7 @@ class MCEvent: #print(f"looping over {recpcount} recps") recpcount += 1 if not recp.lpar: - #print(f"if {abs(recp.pid)} has no linked particle") + print(f"if {abs(recp.pid)} has no linked particle") mindr = 99999999999 parlink = None #print(f"mcp store is {mcpstore}") @@ -415,7 +415,7 @@ class MCEvent: try: parlink.mother().originVertex().type() recp.lpar.update(newmother=MCPart(parlink.mother(),orvrt=parlink.mother().originVertex().type())) - #print("WARNING mother has origin vertex from pp collision") + # print("WARNING mother has origin vertex from pp collision") if parlink.mother().originVertex().type() != 1: #print("mother has origin vertex type != 1. i.e. not from pp collision") try: @@ -428,15 +428,16 @@ class MCEvent: #print("WARNING recp has no mother") except: recp.lpar.update(newmother=None) + #print("except") # i guess L418 shouldn't be being called - is this checking if all particles have been linked? # in which case we should be checking if all particles have been linked to the same particle type if thereExistsMCPIDequalRECPID == False: - #print("no mc particle with same pid as rec particle") + print("no mc particle with same pid as rec particle") break if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: - #print("all particles have been linked") + print("all particles have been linked") B.update(newisdrmatched=True) if B.dl[1].lpar.pid == B.dl[1].pid and B.dl[0].dl[0].lpar.pid == B.dl[0].dl[0].pid and B.dl[0].dl[1].lpar.pid == B.dl[0].dl[1].pid: B.update(newcorlinked=True) @@ -535,20 +536,25 @@ class MCEvent: K = B.dl[1] hadron_minus = B.dl[0].dl[0] hadron_plus = B.dl[0].dl[1] - try: fillKpid = K.lpar.mother.pid - except: fillKpid = 0 + try: Kmotherpid = K.lpar.mother.pid + except: Kmotherpid = 0 - #print(f"kaons mum is {K.lpar.mother}") + # Note the reason I write this as a try except is because I don't know if the mother of K will always exist - if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(K.lpar.par.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle and B (accessed by K origin vertex) has 3 decay products + print(f"kaons mum is {K.lpar.mother}") + + if Kmotherpid == 0: + B.update(newbgtype=9) + + elif abs(Kmotherpid) == 521: #if K mother is a B + if hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par == K.lpar.mother.par and len(K.lpar.par.originVertex().products()) == 3: # if h+ and h- and K have the same mother particle and B (accessed by K origin vertex) has 3 decay products #if K is K+ this must be a B+, if K is K- this must be a B- - if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: B.update(newbgtype=1) - elif K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: B.update(newbgtype=1) + if K.lpar.mother.pid == hadron_minus.lpar.mother.pid == +521: self.dec: B.update(newbgtype=1) + elif K.lpar.mother.pid == hadron_minus.lpar.mother.pid == -521: self.dec: B.update(newbgtype=1) - elif hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par and hadron_minus.lpar.mother.pid == 310: # Check h+ and h- came from the same particle and that particle was a Ks - if K.lpar.mother == hadron_minus.lpar.mother.mother.par == hadron_plus.lpar.mother.mother.par: # Check K, hm and hp all came from the same particle - if abs(K.lpar.mother.pid) == 521: # check this was a B - self.dec: B.update(newbgtype=0) + elif hadron_minus.lpar.mother.par == hadron_plus.lpar.mother.par and hadron_minus.lpar.mother.pid == 310: # Check h+ and h- came from the same particle and that particle was a Ks + if K.lpar.mother == hadron_minus.lpar.mother.mother.par == hadron_plus.lpar.mother.mother.par and abs(K.lpar.mother.pid) == 521: # Check K, hm mother (Ks) and hp mother (Ks) all came from the same particle and check this was a B + self.dec: B.update(newbgtype=0) else: B.update(newbgtype=9) -- GitLab From f49909b221f1101efef548170137557f9ef1f7f4 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Thu, 23 Jan 2025 12:49:49 +0000 Subject: [PATCH 46/70] removed print statements --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 8 ++++---- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 4 ++-- bu2kdarkscalar_darkscalar2hh/job.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index f9cd99b99d..a821778424 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -233,8 +233,8 @@ class MCEvent: for B in self.Bs: rels = self.mctool.relatedMCPs(B.dl[1].par) #rels = self.mctool.relatedMCPs() - print(f"rels is {rels}") - print(f"type rels is {type(rels)}") + #print(f"rels is {rels}") + #print(f"type rels is {type(rels)}") w = 0 rel_K = None for rel in rels: @@ -433,11 +433,11 @@ class MCEvent: # i guess L418 shouldn't be being called - is this checking if all particles have been linked? # in which case we should be checking if all particles have been linked to the same particle type if thereExistsMCPIDequalRECPID == False: - print("no mc particle with same pid as rec particle") + #print("no mc particle with same pid as rec particle") break if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: - print("all particles have been linked") + #print("all particles have been linked") B.update(newisdrmatched=True) if B.dl[1].lpar.pid == B.dl[1].pid and B.dl[0].dl[0].lpar.pid == B.dl[0].dl[0].pid and B.dl[0].dl[1].lpar.pid == B.dl[0].dl[1].pid: B.update(newcorlinked=True) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 7c0a79bfd9..16152ce1c8 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -872,14 +872,14 @@ def fill_ntuple( # Fill the particles. all_Bparticles = get_particles(candidate_loc) - print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") + #print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") if DaVinci().Simulation: mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool #print(mct) mct.linkpars() mcpstore = get_particles('/Event/AllStreams/MC/Particles') - print(f"got {len(mcpstore)} B mesons from mcp store") + #print(f"got {len(mcpstore)} B mesons from mcp store") mct.deltarlink(mcpstore) mct.categorize() else: diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 6a9f252b55..736e947087 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -156,7 +156,7 @@ while (NOfEvents == -1) or (event < NOfEvents): if not bool(tes["/Event"]): break event += 1 - print(f"found event {event}") + #print(f"found event {event}") fill_ntuple(ntuple, tes, candloc, hh, genTool) #print("filled") -- GitLab From d51030c9a1b0c5d503fbb149a333e31104b9c1bd Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Thu, 23 Jan 2025 15:28:10 +0000 Subject: [PATCH 47/70] removed print --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index a821778424..8e5dae3035 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -382,7 +382,7 @@ class MCEvent: #print(f"looping over {recpcount} recps") recpcount += 1 if not recp.lpar: - print(f"if {abs(recp.pid)} has no linked particle") + #print(f"if {abs(recp.pid)} has no linked particle") mindr = 99999999999 parlink = None #print(f"mcp store is {mcpstore}") @@ -541,7 +541,7 @@ class MCEvent: # Note the reason I write this as a try except is because I don't know if the mother of K will always exist - print(f"kaons mum is {K.lpar.mother}") + #print(f"kaons mum is {K.lpar.mother}") if Kmotherpid == 0: B.update(newbgtype=9) -- GitLab From 17565053c80c0261e7d2d4c6cc6b66f0db4dd173 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 28 Jan 2025 13:57:38 +0000 Subject: [PATCH 48/70] configure dv sa and change sel reports loc --- bu2kdarkscalar_darkscalar2hh/job.py | 34 ++++++++++++++++++----------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 736e947087..423eac6814 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -6,7 +6,7 @@ from pathlib import Path import shutil from typing import Union -from Configurables import DaVinci, ToolSvc, TriggerTisTos +from Configurables import DaVinci, ToolSvc, TriggerTisTos, DaVinciSmartAssociator, P2MCPFromProtoP, BackgroundCategory import GaudiPython import ROOT #when running locally you need to uncomment the following lines @@ -101,9 +101,7 @@ DaVinci().Lumi = False # ================================================================================= #candidate locations -# IMPORTANT WARNING!!! -# is the data is saved in microDST format we must use '/Event/AllStreams/Phys/B2KX2KKDarkBosonLine/Particles' -# + if DaVinci().Simulation: stream = 'AllStreams' DaVinci().RootInTES = "/Event/AllStreams" @@ -111,21 +109,15 @@ if DaVinci().Simulation: else: stream = 'Leptonic' - #DaVinci().RootInTES = "/Event/AllStreams" print(f"stream set as {stream}") -""" if DaVinci().InputType == "MDST": - stream = 'Leptonic' -elif DaVinci().InputType == "DST": - stream = 'AllStreams' """ - if hh == "KK": line ='B2KX2KKDarkBosonLine' elif hh == "PiPi": line = 'B2KX2PiPiDarkBosonLine' candloc = '/Event/' + stream + '/Phys/' + line + '/Particles' -#candloc = '/Event/' + '/Phys/' + line + '/Particles' + print(f"Using candidate location: {candloc}") # ================================================================================= @@ -133,8 +125,24 @@ print(f"Using candidate location: {candloc}") for stage in ("Hlt1", "Hlt2", "Strip/Phys"): ToolSvc().addTool(TriggerTisTos, stage + "TriggerTisTos") tool = getattr(ToolSvc(), stage + "TriggerTisTos") - tool.HltDecReportsLocation = f"/Event/{stage}/DecReports" - tool.HltSelReportsLocation = f"/Event/{stage}/SelReports" + tool.HltDecReportsLocation = f"/Event/{stream}/{stage}/DecReports" + tool.HltSelReportsLocation = f"/Event/{stream}/{stage}/SelReports" + +# ================================================================================= +# Configure smart associator. + +ToolSvc().addTool(DaVinciSmartAssociator) +ToolSvc().DaVinciSmartAssociator.addTool(P2MCPFromProtoP) +ToolSvc().DaVinciSmartAssociator.addTool(BackgroundCategory) +ToolSvc().DaVinciSmartAssociator.BackgroundCategory.addTool(P2MCPFromProtoP) + +if "MDST" == DaVinci().InputType: + ToolSvc().DaVinciSmartAssociator.P2MCPFromProtoP.Locations = ["AllStreams/Relations/Rec/ProtoP/Charged"'AllStreams/Relations/Rec/ProtoP/Upstream', + 'AllStreams/Relations/Rec/ProtoP/Neutrals'] + ToolSvc().DaVinciSmartAssociator.BackgroundCategory.P2MCPFromProtoP.Locations = ['AllStreams/Relations/Rec/ProtoP/Charged', + 'AllStreams/Relations/Rec/ProtoP/Upstream', + 'AllStreams/Relations/Rec/ProtoP/Neutrals'] + # ================================================================================= # Run. -- GitLab From d8ae9b14002ec45da290e183e2492ab34f9fdb09 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 28 Jan 2025 14:54:20 +0000 Subject: [PATCH 49/70] add debug flag, comment lines when running via AP --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 61 +++++++++----------- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 34 +++++------ bu2kdarkscalar_darkscalar2hh/job.py | 24 ++++++-- 3 files changed, 63 insertions(+), 56 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 8e5dae3035..5ed8166220 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -8,6 +8,8 @@ from LoKiPhys.decorators import M, PX, PY, PZ, E, ID, ABSID, PROBNNk, PROBNNpi, from array import array from math import sqrt +from bu2kdarkscalar_darkscalar2hh.job import debug_print + class MCPart: """ Simple class for storing rec and gen mc particles. @@ -205,7 +207,7 @@ class MCEvent: for ddp in dp.endVertex().outgoingParticlesVector(): hadron = MCPart(ddp) hadron.update(newpnnk=PROBNNk(ddp),newpnnpi=PROBNNpi(ddp),newpnng=PROBNNghost(ddp),newtrgp=TRGHOSTPROB(ddp)) - #print(hadron.pid) + debug_print(hadron.pid) if hh == "KK": hhPID = 321 @@ -218,7 +220,7 @@ class MCEvent: dps[0] = MCPart(dp,dl=hadrons,sv=svcoor) B = MCPart(all_B,dl=dps) self.Bs += [B] - #print(f"self.Bs is {self.Bs}") + debug_print(f"self.Bs is {self.Bs}") def linkpars(self): """ @@ -232,9 +234,8 @@ class MCEvent: self.allreglinked = True for B in self.Bs: rels = self.mctool.relatedMCPs(B.dl[1].par) - #rels = self.mctool.relatedMCPs() - #print(f"rels is {rels}") - #print(f"type rels is {type(rels)}") + debug_print(f"rels is {rels}") + debug_print(f"type rels is {type(rels)}") w = 0 rel_K = None for rel in rels: @@ -373,40 +374,40 @@ class MCEvent: count = 1 for B in self.Bs: - #print(f"looping over number {count} B particles stored in stripping line") + debug_print(f"looping over number {count} B particles stored in stripping line") count += 1 if not B.alllinked: - #print("not all Bs are linked to MC particles already by linkpars so we need delta r matching") + debug_print("not all Bs are linked to MC particles already by linkpars so we need delta r matching") recpcount = 0 for recp in [B.dl[1],B.dl[0].dl[0],B.dl[0].dl[1]]: - #print(f"looping over {recpcount} recps") + debug_print(f"looping over {recpcount} recps") recpcount += 1 if not recp.lpar: - #print(f"if {abs(recp.pid)} has no linked particle") + debug_print(f"if {abs(recp.pid)} has no linked particle") mindr = 99999999999 parlink = None - #print(f"mcp store is {mcpstore}") - #print(f"mcp store contains {len(mcpstore)} mc particles") + debug_print(f"mcp store is {mcpstore}") + debug_print(f"mcp store contains {len(mcpstore)} mc particles") thereExistsMCPIDequalRECPID = False for mcp in mcpstore: - #print(f"mc particle is {mcp}") - #print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") + debug_print(f"mc particle is {mcp}") + debug_print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") if MCID(mcp) == recp.pid: thereExistsMCPIDequalRECPID = True - #print("found mc particle with same pid is same as rec particle pid") + debug_print("found mc particle with same pid is same as rec particle pid") dphi = MCPHI(mcp) - PHI(recp.par) deta = MCETA(mcp) - ETA(recp.par) deltar = sqrt(dphi**2 + deta**2) - #print(f"deltar is {deltar}") - #print(f"mindr is {mindr}") + debug_print(f"deltar is {deltar}") + debug_print(f"mindr is {mindr}") if deltar < mindr: mindr = deltar - #print(f"mindr is {mindr}") + debug_print(f"mindr is {mindr}") parlink = mcp - #print(f"assign the rec particle this mc particle which is {parlink}") + debug_print(f"assign the rec particle this mc particle which is {parlink}") break if thereExistsMCPIDequalRECPID == False: - #print("no mc particle with same pid as rec particle") + debug_print("no mc particle with same pid as rec particle") break recp.update(newlpar=MCPart(parlink,orvrt=parlink.originVertex().type())) if MCID(parlink) == recp.pid: recp.update(newclink=True) @@ -415,29 +416,25 @@ class MCEvent: try: parlink.mother().originVertex().type() recp.lpar.update(newmother=MCPart(parlink.mother(),orvrt=parlink.mother().originVertex().type())) - # print("WARNING mother has origin vertex from pp collision") if parlink.mother().originVertex().type() != 1: - #print("mother has origin vertex type != 1. i.e. not from pp collision") + debug_print("mother has origin vertex type != 1. i.e. not from pp collision") try: parlink.mother().mother().originVertex().type() recp.lpar.mother.update(newmother=MCPart(parlink.mother().mother(),orvrt=parlink.mother().mother().originVertex().type())) - #print("updating mother") - #print(f"mother of {recp.pid} is {recp.lpar.mother}") + debug_print("updating mother") + debug_print(f"mother of {recp.pid} is {recp.lpar.mother}") except: recp.lpar.mother.update(newmother=None) - #print("WARNING recp has no mother") + debug_print("WARNING recp has no mother") except: recp.lpar.update(newmother=None) - #print("except") - # i guess L418 shouldn't be being called - is this checking if all particles have been linked? - # in which case we should be checking if all particles have been linked to the same particle type if thereExistsMCPIDequalRECPID == False: - #print("no mc particle with same pid as rec particle") + debug_print("no mc particle with same pid as rec particle") break if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: - #print("all particles have been linked") + debug_print("all particles have been linked") B.update(newisdrmatched=True) if B.dl[1].lpar.pid == B.dl[1].pid and B.dl[0].dl[0].lpar.pid == B.dl[0].dl[0].pid and B.dl[0].dl[1].lpar.pid == B.dl[0].dl[1].pid: B.update(newcorlinked=True) @@ -526,12 +523,10 @@ class MCEvent: 9: there was an error in MC truth matching """ - #print("begin categorizing") + debug_print("begin categorisation") if not self.links: self.linkpars() for B in self.Bs: - #print("categorise") - if self.corlin(B): K = B.dl[1] hadron_minus = B.dl[0].dl[0] @@ -541,7 +536,7 @@ class MCEvent: # Note the reason I write this as a try except is because I don't know if the mother of K will always exist - #print(f"kaons mum is {K.lpar.mother}") + debug_print(f"kaons mum is {K.lpar.mother}") if Kmotherpid == 0: B.update(newbgtype=9) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 16152ce1c8..d2221f6458 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -15,6 +15,8 @@ import ROOT from dtf import dtf #dtf: decay tree fitter import MCEventTools +from bu2kdarkscalar_darkscalar2hh.job import debug_print + class Ntuple: """Class for storing an ntuple.""" @@ -344,20 +346,20 @@ class Ntuple: """ if mcp: # mcp: monte carlo particle, this is a boolean if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B - #print(f"mcp.pid is set to {mcp.pid}") + debug_print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): #only when we inspect pid do we know the charges associated to hadron0 and hadron1 if mcp.pid in (-211, -321): dptype = "hm" - #print(f"found mcp particle which is hm with pid {mcp.pid}") + debug_print(f"found mcp particle which is hm with pid {mcp.pid}") elif mcp.pid in (211, 321): dptype = "hp" - #print(f"found mcp particle which is hp with pid {mcp.pid}") + debug_print(f"found mcp particle which is hp with pid {mcp.pid}") elif dptype == "K": if mcp.pid in (321, -321): dptype = "K" - #print(f"found mcp particle which is K with pid {mcp.pid}") + debug_print(f"found mcp particle which is K with pid {mcp.pid}") pass else: raise ValueError(f"mcp.pid {mcp.pid} not recognized") @@ -842,20 +844,17 @@ def fill_ntuple( hh: "str", genTool, ): - #print(f"hh is {hh}") + debug_print(f"hh is {hh}") """Assign values to the ntuple.""" year = int(DaVinci().DataType) def get_particles(candloc): - #print(f"candidate_loc is {candloc}") + debug_print(f"candidate location is {candloc}") try: particles = tes[candloc] - #print(particles) - len(particles) - size = len(particles) - #print("found {0} particles".format(size)) + debug_print(f"found {len(particles)} particles in {candloc}") except: - #print("no particles found") + debug_print(f"no particles found in {candloc}") particles = [] return particles @@ -872,14 +871,13 @@ def fill_ntuple( # Fill the particles. all_Bparticles = get_particles(candidate_loc) - #print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") + debug_print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") if DaVinci().Simulation: mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool - #print(mct) mct.linkpars() mcpstore = get_particles('/Event/AllStreams/MC/Particles') - #print(f"got {len(mcpstore)} B mesons from mcp store") + debug_print(f"got {len(mcpstore)} B mesons from mcp store") mct.deltarlink(mcpstore) mct.categorize() else: @@ -889,20 +887,18 @@ def fill_ntuple( isfilled = False for all_Bparticle in all_Bparticles: - #print("its working") args, kwargs = [all_Bparticle], {"year": year, "run": rnum, "mctool": mct} boolVal = ntuple.fillPrt(*args, **kwargs, check=True) - #print("I find bool is {0}".format(boolVal)) + debug_print(f"I find bool is {boolVal}") - # if not all parricles are linked if ntuple.fillPrt(*args, **kwargs, check=True): ntuple.fillPrt(*args, **kwargs) isfilled = True - #print("isfilled has been set to {0}".format(isfilled)) + debug_print(f"isfilled has been set to {isfilled}") if len(all_Bparticles) > 0 and isfilled: - #print("conditions for filling tuple met") + debug_print("conditions for filling tuple met") ntuple.fill() ntuple.clear() diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 423eac6814..73592d4664 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -11,10 +11,16 @@ import GaudiPython import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -import sys -sys.path.append("../") +#import sys +#sys.path.append("../") +DEBUG = True # Set to False to disable debug output + +def debug_print(*args): + if DEBUG: + print(*args) + from bu2kdarkscalar_darkscalar2hh.Ntuple import fill_ntuple, Ntuple # ================================================================================= @@ -34,8 +40,11 @@ def parse_args() -> "tuple[Union[str, None], bool]": type=Path, help="Additional options files to load before starting Gaudi Python", ) + parser.add_argument("--debug", action="store_true", help="Enable debug output") args = parser.parse_args() + DEBUG = args.debug + allowed_hh = ("PiPi", "KK") hh = None @@ -71,6 +80,13 @@ def parse_args() -> "tuple[Union[str, None], bool]": # passed to the script need to be executed to set up input data and other state hh = parse_args() + +# ================================================================================= + +def debug_print(*args): + if DEBUG: + print(*args) + # ================================================================================= # Set up material tool print("About to compile velo material tool") @@ -164,10 +180,10 @@ while (NOfEvents == -1) or (event < NOfEvents): if not bool(tes["/Event"]): break event += 1 - #print(f"found event {event}") + debug_print(f"found event {event}") fill_ntuple(ntuple, tes, candloc, hh, genTool) - #print("filled") + debug_print("filled") # Close. -- GitLab From e06dc67697378807da67d7a2bd4e05b8e14c8720 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 29 Jan 2025 11:31:54 +0000 Subject: [PATCH 50/70] minor --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 56 ++++++++++---------- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 29 +++++----- bu2kdarkscalar_darkscalar2hh/job.py | 26 ++++----- 3 files changed, 57 insertions(+), 54 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 5ed8166220..9429a3b20f 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -8,7 +8,7 @@ from LoKiPhys.decorators import M, PX, PY, PZ, E, ID, ABSID, PROBNNk, PROBNNpi, from array import array from math import sqrt -from bu2kdarkscalar_darkscalar2hh.job import debug_print + class MCPart: """ @@ -207,7 +207,7 @@ class MCEvent: for ddp in dp.endVertex().outgoingParticlesVector(): hadron = MCPart(ddp) hadron.update(newpnnk=PROBNNk(ddp),newpnnpi=PROBNNpi(ddp),newpnng=PROBNNghost(ddp),newtrgp=TRGHOSTPROB(ddp)) - debug_print(hadron.pid) + #debug_print(hadron.pid) if hh == "KK": hhPID = 321 @@ -220,7 +220,7 @@ class MCEvent: dps[0] = MCPart(dp,dl=hadrons,sv=svcoor) B = MCPart(all_B,dl=dps) self.Bs += [B] - debug_print(f"self.Bs is {self.Bs}") + #debug_print(f"self.Bs is {self.Bs}") def linkpars(self): """ @@ -233,9 +233,9 @@ class MCEvent: """ self.allreglinked = True for B in self.Bs: - rels = self.mctool.relatedMCPs(B.dl[1].par) - debug_print(f"rels is {rels}") - debug_print(f"type rels is {type(rels)}") + rels = self.mctool.relatedMCPs(B.dl[1].par, '/Event/AllStreams/MC/Particles') + print(f"rels is {rels}") + print(f"type rels is {type(rels)}") w = 0 rel_K = None for rel in rels: @@ -374,40 +374,40 @@ class MCEvent: count = 1 for B in self.Bs: - debug_print(f"looping over number {count} B particles stored in stripping line") + #debug_print(f"looping over number {count} B particles stored in stripping line") count += 1 if not B.alllinked: - debug_print("not all Bs are linked to MC particles already by linkpars so we need delta r matching") + #debug_print("not all Bs are linked to MC particles already by linkpars so we need delta r matching") recpcount = 0 for recp in [B.dl[1],B.dl[0].dl[0],B.dl[0].dl[1]]: - debug_print(f"looping over {recpcount} recps") + #debug_print(f"looping over {recpcount} recps") recpcount += 1 if not recp.lpar: - debug_print(f"if {abs(recp.pid)} has no linked particle") + #debug_print(f"if {abs(recp.pid)} has no linked particle") mindr = 99999999999 parlink = None - debug_print(f"mcp store is {mcpstore}") - debug_print(f"mcp store contains {len(mcpstore)} mc particles") + #debug_print(f"mcp store is {mcpstore}") + #debug_print(f"mcp store contains {len(mcpstore)} mc particles") thereExistsMCPIDequalRECPID = False for mcp in mcpstore: - debug_print(f"mc particle is {mcp}") - debug_print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") + #debug_print(f"mc particle is {mcp}") + #debug_print(f" MCID is {MCID(mcp)} and recp.pid is {recp.pid}") if MCID(mcp) == recp.pid: thereExistsMCPIDequalRECPID = True - debug_print("found mc particle with same pid is same as rec particle pid") + #debug_print("found mc particle with same pid is same as rec particle pid") dphi = MCPHI(mcp) - PHI(recp.par) deta = MCETA(mcp) - ETA(recp.par) deltar = sqrt(dphi**2 + deta**2) - debug_print(f"deltar is {deltar}") - debug_print(f"mindr is {mindr}") + #debug_print(f"deltar is {deltar}") + #debug_print(f"mindr is {mindr}") if deltar < mindr: mindr = deltar - debug_print(f"mindr is {mindr}") + #debug_print(f"mindr is {mindr}") parlink = mcp - debug_print(f"assign the rec particle this mc particle which is {parlink}") + #debug_print(f"assign the rec particle this mc particle which is {parlink}") break if thereExistsMCPIDequalRECPID == False: - debug_print("no mc particle with same pid as rec particle") + #debug_print("no mc particle with same pid as rec particle") break recp.update(newlpar=MCPart(parlink,orvrt=parlink.originVertex().type())) if MCID(parlink) == recp.pid: recp.update(newclink=True) @@ -417,24 +417,24 @@ class MCEvent: parlink.mother().originVertex().type() recp.lpar.update(newmother=MCPart(parlink.mother(),orvrt=parlink.mother().originVertex().type())) if parlink.mother().originVertex().type() != 1: - debug_print("mother has origin vertex type != 1. i.e. not from pp collision") + #debug_print("mother has origin vertex type != 1. i.e. not from pp collision") try: parlink.mother().mother().originVertex().type() recp.lpar.mother.update(newmother=MCPart(parlink.mother().mother(),orvrt=parlink.mother().mother().originVertex().type())) - debug_print("updating mother") - debug_print(f"mother of {recp.pid} is {recp.lpar.mother}") + #debug_print("updating mother") + #debug_print(f"mother of {recp.pid} is {recp.lpar.mother}") except: recp.lpar.mother.update(newmother=None) - debug_print("WARNING recp has no mother") + #debug_print("WARNING recp has no mother") except: recp.lpar.update(newmother=None) if thereExistsMCPIDequalRECPID == False: - debug_print("no mc particle with same pid as rec particle") + #debug_print("no mc particle with same pid as rec particle") break if B.dl[1].lpar and B.dl[0].dl[0].lpar and B.dl[0].dl[1].lpar: - debug_print("all particles have been linked") + #debug_print("all particles have been linked") B.update(newisdrmatched=True) if B.dl[1].lpar.pid == B.dl[1].pid and B.dl[0].dl[0].lpar.pid == B.dl[0].dl[0].pid and B.dl[0].dl[1].lpar.pid == B.dl[0].dl[1].pid: B.update(newcorlinked=True) @@ -523,7 +523,7 @@ class MCEvent: 9: there was an error in MC truth matching """ - debug_print("begin categorisation") + #debug_print("begin categorisation") if not self.links: self.linkpars() for B in self.Bs: @@ -536,7 +536,7 @@ class MCEvent: # Note the reason I write this as a try except is because I don't know if the mother of K will always exist - debug_print(f"kaons mum is {K.lpar.mother}") + #debug_print(f"kaons mum is {K.lpar.mother}") if Kmotherpid == 0: B.update(newbgtype=9) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index d2221f6458..aaefe24473 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -15,7 +15,9 @@ import ROOT from dtf import dtf #dtf: decay tree fitter import MCEventTools -from bu2kdarkscalar_darkscalar2hh.job import debug_print + + + class Ntuple: """Class for storing an ntuple.""" @@ -346,20 +348,20 @@ class Ntuple: """ if mcp: # mcp: monte carlo particle, this is a boolean if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B - debug_print(f"mcp.pid is set to {mcp.pid}") + #debug_print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): #only when we inspect pid do we know the charges associated to hadron0 and hadron1 if mcp.pid in (-211, -321): dptype = "hm" - debug_print(f"found mcp particle which is hm with pid {mcp.pid}") + #debug_print(f"found mcp particle which is hm with pid {mcp.pid}") elif mcp.pid in (211, 321): dptype = "hp" - debug_print(f"found mcp particle which is hp with pid {mcp.pid}") + #debug_print(f"found mcp particle which is hp with pid {mcp.pid}") elif dptype == "K": if mcp.pid in (321, -321): dptype = "K" - debug_print(f"found mcp particle which is K with pid {mcp.pid}") + #debug_print(f"found mcp particle which is K with pid {mcp.pid}") pass else: raise ValueError(f"mcp.pid {mcp.pid} not recognized") @@ -844,17 +846,17 @@ def fill_ntuple( hh: "str", genTool, ): - debug_print(f"hh is {hh}") + #debug_print(f"hh is {hh}") """Assign values to the ntuple.""" year = int(DaVinci().DataType) def get_particles(candloc): - debug_print(f"candidate location is {candloc}") + #debug_print(f"candidate location is {candloc}") try: particles = tes[candloc] - debug_print(f"found {len(particles)} particles in {candloc}") + print(f"found {len(particles)} particles in {candloc}") except: - debug_print(f"no particles found in {candloc}") + #debug_print(f"no particles found in {candloc}") particles = [] return particles @@ -871,13 +873,13 @@ def fill_ntuple( # Fill the particles. all_Bparticles = get_particles(candidate_loc) - debug_print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") + #debug_print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") if DaVinci().Simulation: mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool mct.linkpars() mcpstore = get_particles('/Event/AllStreams/MC/Particles') - debug_print(f"got {len(mcpstore)} B mesons from mcp store") + #debug_print(f"got {len(mcpstore)} B mesons from mcp store") mct.deltarlink(mcpstore) mct.categorize() else: @@ -895,12 +897,13 @@ def fill_ntuple( if ntuple.fillPrt(*args, **kwargs, check=True): ntuple.fillPrt(*args, **kwargs) isfilled = True - debug_print(f"isfilled has been set to {isfilled}") + #debug_print(f"isfilled has been set to {isfilled}") if len(all_Bparticles) > 0 and isfilled: - debug_print("conditions for filling tuple met") + #debug_print("conditions for filling tuple met") ntuple.fill() ntuple.clear() # End of fill_ntuple function + diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 73592d4664..2b0bd86948 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -5,21 +5,18 @@ import importlib.util from pathlib import Path import shutil from typing import Union - -from Configurables import DaVinci, ToolSvc, TriggerTisTos, DaVinciSmartAssociator, P2MCPFromProtoP, BackgroundCategory +import sys import GaudiPython +import os +from GaudiConf import IOHelper +from Configurables import DaVinci, ToolSvc, TriggerTisTos, DaVinciSmartAssociator, P2MCPFromProtoP, BackgroundCategory, CondDB, MessageSvc import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -#import sys + #sys.path.append("../") -DEBUG = True # Set to False to disable debug output - -def debug_print(*args): - if DEBUG: - print(*args) from bu2kdarkscalar_darkscalar2hh.Ntuple import fill_ntuple, Ntuple @@ -43,8 +40,6 @@ def parse_args() -> "tuple[Union[str, None], bool]": parser.add_argument("--debug", action="store_true", help="Enable debug output") args = parser.parse_args() - DEBUG = args.debug - allowed_hh = ("PiPi", "KK") hh = None @@ -73,13 +68,16 @@ def parse_args() -> "tuple[Union[str, None], bool]": raise ValueError(f"hh must be one of {allowed_hh}") else: print(f"Running with hh == '{hh}'.") - return hh + return hh, args.debug # In order to use Run 1+2 GaudiPython with analysis productions the arguments # passed to the script need to be executed to set up input data and other state -hh = parse_args() +hh = parse_args()[0] +DEBUG = parse_args()[1] + +print(f"debug is set to {DEBUG}") # ================================================================================= @@ -87,6 +85,7 @@ def debug_print(*args): if DEBUG: print(*args) + # ================================================================================= # Set up material tool print("About to compile velo material tool") @@ -153,7 +152,8 @@ ToolSvc().DaVinciSmartAssociator.addTool(BackgroundCategory) ToolSvc().DaVinciSmartAssociator.BackgroundCategory.addTool(P2MCPFromProtoP) if "MDST" == DaVinci().InputType: - ToolSvc().DaVinciSmartAssociator.P2MCPFromProtoP.Locations = ["AllStreams/Relations/Rec/ProtoP/Charged"'AllStreams/Relations/Rec/ProtoP/Upstream', + ToolSvc().DaVinciSmartAssociator.P2MCPFromProtoP.Locations = ['AllStreams/Relations/Rec/ProtoP/Charged', + 'AllStreams/Relations/Rec/ProtoP/Upstream', 'AllStreams/Relations/Rec/ProtoP/Neutrals'] ToolSvc().DaVinciSmartAssociator.BackgroundCategory.P2MCPFromProtoP.Locations = ['AllStreams/Relations/Rec/ProtoP/Charged', 'AllStreams/Relations/Rec/ProtoP/Upstream', -- GitLab From d745b4d585847809157de3a6b7c22cb6946a023c Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 29 Jan 2025 12:53:58 +0000 Subject: [PATCH 51/70] comment --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 4 ++-- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index 9429a3b20f..fb231e8cd3 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -234,8 +234,8 @@ class MCEvent: self.allreglinked = True for B in self.Bs: rels = self.mctool.relatedMCPs(B.dl[1].par, '/Event/AllStreams/MC/Particles') - print(f"rels is {rels}") - print(f"type rels is {type(rels)}") + #print(f"rels is {rels}") + #print(f"type rels is {type(rels)}") w = 0 rel_K = None for rel in rels: diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index aaefe24473..2adcec28d8 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -854,7 +854,7 @@ def fill_ntuple( #debug_print(f"candidate location is {candloc}") try: particles = tes[candloc] - print(f"found {len(particles)} particles in {candloc}") + #print(f"found {len(particles)} particles in {candloc}") except: #debug_print(f"no particles found in {candloc}") particles = [] @@ -892,7 +892,7 @@ def fill_ntuple( args, kwargs = [all_Bparticle], {"year": year, "run": rnum, "mctool": mct} boolVal = ntuple.fillPrt(*args, **kwargs, check=True) - debug_print(f"I find bool is {boolVal}") + #debug_print(f"I find bool is {boolVal}") if ntuple.fillPrt(*args, **kwargs, check=True): ntuple.fillPrt(*args, **kwargs) -- GitLab From fb055c3b1026dd47373cf154976fa8f497e4cda9 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Wed, 29 Jan 2025 14:35:16 +0000 Subject: [PATCH 52/70] stop mc truth matching when no candidates present --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 13 +++++++------ bu2kdarkscalar_darkscalar2hh/job.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 2adcec28d8..67adcf578f 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -854,7 +854,7 @@ def fill_ntuple( #debug_print(f"candidate location is {candloc}") try: particles = tes[candloc] - #print(f"found {len(particles)} particles in {candloc}") + print(f"found {len(particles)} particles in {candloc}") except: #debug_print(f"no particles found in {candloc}") particles = [] @@ -873,9 +873,10 @@ def fill_ntuple( # Fill the particles. all_Bparticles = get_particles(candidate_loc) - #debug_print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") + print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") - if DaVinci().Simulation: + + if DaVinci().Simulation and len(all_Bparticles) > 0: mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool mct.linkpars() mcpstore = get_particles('/Event/AllStreams/MC/Particles') @@ -892,15 +893,15 @@ def fill_ntuple( args, kwargs = [all_Bparticle], {"year": year, "run": rnum, "mctool": mct} boolVal = ntuple.fillPrt(*args, **kwargs, check=True) - #debug_print(f"I find bool is {boolVal}") + print(f"I find bool is {boolVal}") if ntuple.fillPrt(*args, **kwargs, check=True): ntuple.fillPrt(*args, **kwargs) isfilled = True - #debug_print(f"isfilled has been set to {isfilled}") + print(f"isfilled has been set to {isfilled}") if len(all_Bparticles) > 0 and isfilled: - #debug_print("conditions for filling tuple met") + print("conditions for filling tuple met") ntuple.fill() ntuple.clear() diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 2b0bd86948..5e921fb0b5 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -14,7 +14,7 @@ import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -#sys.path.append("../") +sys.path.append("../") -- GitLab From efdbfd261b8f1c3a87a1bca608227be42c498378 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Thu, 30 Jan 2025 11:16:50 +0000 Subject: [PATCH 53/70] modify debug print outs --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 19 ++++++++++--------- bu2kdarkscalar_darkscalar2hh/job.py | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 67adcf578f..7e73c3024b 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -845,18 +845,19 @@ def fill_ntuple( candidate_loc: "str", hh: "str", genTool, + debug_print: "function" ): - #debug_print(f"hh is {hh}") + debug_print(f"hh is {hh}") """Assign values to the ntuple.""" year = int(DaVinci().DataType) def get_particles(candloc): - #debug_print(f"candidate location is {candloc}") + debug_print(f"candidate location is {candloc}") try: particles = tes[candloc] - print(f"found {len(particles)} particles in {candloc}") + debug_print(f"found {len(particles)} particles in {candloc}") except: - #debug_print(f"no particles found in {candloc}") + debug_print(f"no particles found in {candloc}") particles = [] return particles @@ -873,14 +874,14 @@ def fill_ntuple( # Fill the particles. all_Bparticles = get_particles(candidate_loc) - print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") + debug_print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") if DaVinci().Simulation and len(all_Bparticles) > 0: mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool mct.linkpars() mcpstore = get_particles('/Event/AllStreams/MC/Particles') - #debug_print(f"got {len(mcpstore)} B mesons from mcp store") + debug_print(f"got {len(mcpstore)} B mesons from mcp store") mct.deltarlink(mcpstore) mct.categorize() else: @@ -893,15 +894,15 @@ def fill_ntuple( args, kwargs = [all_Bparticle], {"year": year, "run": rnum, "mctool": mct} boolVal = ntuple.fillPrt(*args, **kwargs, check=True) - print(f"I find bool is {boolVal}") + debug_print(f"I find bool is {boolVal}") if ntuple.fillPrt(*args, **kwargs, check=True): ntuple.fillPrt(*args, **kwargs) isfilled = True - print(f"isfilled has been set to {isfilled}") + debug_print(f"isfilled has been set to {isfilled}") if len(all_Bparticles) > 0 and isfilled: - print("conditions for filling tuple met") + debug_print("conditions for filling tuple met") ntuple.fill() ntuple.clear() diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 5e921fb0b5..cd6e437bfc 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -182,7 +182,7 @@ while (NOfEvents == -1) or (event < NOfEvents): event += 1 debug_print(f"found event {event}") - fill_ntuple(ntuple, tes, candloc, hh, genTool) + fill_ntuple(ntuple, tes, candloc, hh, genTool, debug_print) debug_print("filled") -- GitLab From 6c369ec2fdc4b81fecf51bdd3863aa57b871a43d Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Thu, 30 Jan 2025 12:28:53 +0000 Subject: [PATCH 54/70] don't need this line when ap --- bu2kdarkscalar_darkscalar2hh/job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index cd6e437bfc..1537fec627 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -14,7 +14,7 @@ import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -sys.path.append("../") +#sys.path.append("../") -- GitLab From 4f45e4098edfea8753034e39bd4e9d24262d56c1 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 31 Jan 2025 15:17:49 +0000 Subject: [PATCH 55/70] add deacy time, PT, dira and vtx chi2 branches --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 33 +++++++++++++++++++------- bu2kdarkscalar_darkscalar2hh/job.py | 3 ++- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 7e73c3024b..ca7949649b 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -9,17 +9,14 @@ from typing import Any, Union from Configurables import DaVinci import GaudiPython from LoKiArrayFunctors.decorators import AMAXDOCA -from LoKiMC.decorators import MCINANCESTORS, MCID +from LoKiMC.decorators import MCINANCESTORS, MCID, DTF_FUN +from LoKiPhys.decorators import DIRA import ROOT from dtf import dtf #dtf: decay tree fitter import MCEventTools - - - - -class Ntuple: +class Ntuple(): """Class for storing an ntuple.""" ########################################################################### @@ -82,7 +79,7 @@ class Ntuple: else: self.pvrs = "/Event/Leptonic/" + self.pvrs vrsVrt = ["x", "y", "z", "dx", "dy", "dz"] - vrsMom = ["px", "py", "pz", "e"] + vrsMom = ["px", "py", "pz", "pt", "e"] vrsTrk = [ "pnn_e", "pnn_mu", @@ -99,6 +96,7 @@ class Ntuple: "dm", "idx_pvr", "veloCharge", + "dira", ] for h in range(0, self.nhit): vrsTrk += [f"x{h}", f"y{h}", f"z{h}", f"t{h}", f"p{h}"] @@ -117,6 +115,9 @@ class Ntuple: "vt_tip", "vt_d", "htrackOL", + "dira", + "decay_time", + "vtx_chi2", ] + [ f"{dtf}dtf_{x}" @@ -283,11 +284,13 @@ class Ntuple: self.fill(f"{pre}_px", -1) self.fill(f"{pre}_py", -1) self.fill(f"{pre}_pz", -1) + self.fill(f"{pre}_pt", -1) self.fill(f"{pre}_e", -1) else: self.fill(f"{pre}_px", mom.Px()) self.fill(f"{pre}_py", mom.Py()) self.fill(f"{pre}_pz", mom.Pz()) + self.fill(f"{pre}_pt", mom.perp()) self.fill(f"{pre}_e", mom.E()) ########################################################################### @@ -348,7 +351,7 @@ class Ntuple: """ if mcp: # mcp: monte carlo particle, this is a boolean if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B - #debug_print(f"mcp.pid is set to {mcp.pid}") + debug_print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): #only when we inspect pid do we know the charges associated to hadron0 and hadron1 @@ -719,6 +722,14 @@ class Ntuple: self.fill(f"{pre}_ip", ip.value) self.fill(f"{pre}_ip_chi2", ipChi2.value) + # DIRA. + self.fill(f"{pre}_dira", (DIRA(pvr)(prt))) + + # Decay time. + self.fill(f"{pre}_decay_time", DTF_FUN("CTAU")(prt)) + + # Vrtx chi 2. + self.fill(f"{pre}_vtx_chi2", vrt.chi2()) return pvr ########################################################################### @@ -784,6 +795,7 @@ class Ntuple: self._dstTool.distance(prt, pvr, ip, ipChi2) self.fill(f"{pre}_ip", ip.value) self.fill(f"{pre}_ip_chi2", ipChi2.value) + self.fill(f"{pre}_dira", (DIRA(pvr)(prt))) return pvr ########################################################################### @@ -847,8 +859,11 @@ def fill_ntuple( genTool, debug_print: "function" ): - debug_print(f"hh is {hh}") + """Assign values to the ntuple.""" + + debug_print(f"hh is {hh}") + year = int(DaVinci().DataType) def get_particles(candloc): diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 1537fec627..6a39c00803 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -14,7 +14,7 @@ import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -#sys.path.append("../") +sys.path.append("../") @@ -164,6 +164,7 @@ if "MDST" == DaVinci().InputType: # Run. gaudi = GaudiPython.AppMgr() tes = gaudi.evtsvc() + ntuple = Ntuple(gaudi, output_filename) # Setting up tools for MC BG categorizing genTool = ( -- GitLab From 061cb144113e103b65c622f3e85ba0dd2ab60653 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 31 Jan 2025 15:26:37 +0000 Subject: [PATCH 56/70] comment --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 8 ++++---- bu2kdarkscalar_darkscalar2hh/job.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index ca7949649b..b5976b5da5 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -9,8 +9,8 @@ from typing import Any, Union from Configurables import DaVinci import GaudiPython from LoKiArrayFunctors.decorators import AMAXDOCA -from LoKiMC.decorators import MCINANCESTORS, MCID, DTF_FUN -from LoKiPhys.decorators import DIRA +from LoKiMC.decorators import MCINANCESTORS, MCID +from LoKiPhys.decorators import DIRA, DTF_CTAU import ROOT from dtf import dtf #dtf: decay tree fitter @@ -351,7 +351,7 @@ class Ntuple(): """ if mcp: # mcp: monte carlo particle, this is a boolean if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B - debug_print(f"mcp.pid is set to {mcp.pid}") + #debug_print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): #only when we inspect pid do we know the charges associated to hadron0 and hadron1 @@ -726,7 +726,7 @@ class Ntuple(): self.fill(f"{pre}_dira", (DIRA(pvr)(prt))) # Decay time. - self.fill(f"{pre}_decay_time", DTF_FUN("CTAU")(prt)) + self.fill(f"{pre}_decay_time", DTF_CTAU(prt)) # Vrtx chi 2. self.fill(f"{pre}_vtx_chi2", vrt.chi2()) diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 6a39c00803..5a709966ed 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -14,7 +14,7 @@ import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -sys.path.append("../") +#sys.path.append("../") -- GitLab From 5dc35cb74a623b186d4fd851afcf2fabb9d40795 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 31 Jan 2025 15:40:37 +0000 Subject: [PATCH 57/70] calc pt correctly --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index b5976b5da5..313f70b6aa 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -10,7 +10,7 @@ from Configurables import DaVinci import GaudiPython from LoKiArrayFunctors.decorators import AMAXDOCA from LoKiMC.decorators import MCINANCESTORS, MCID -from LoKiPhys.decorators import DIRA, DTF_CTAU +from LoKiPhys.decorators import DIRA, DTF_CTAU, PT import ROOT from dtf import dtf #dtf: decay tree fitter @@ -284,13 +284,11 @@ class Ntuple(): self.fill(f"{pre}_px", -1) self.fill(f"{pre}_py", -1) self.fill(f"{pre}_pz", -1) - self.fill(f"{pre}_pt", -1) self.fill(f"{pre}_e", -1) else: self.fill(f"{pre}_px", mom.Px()) self.fill(f"{pre}_py", mom.Py()) self.fill(f"{pre}_pz", mom.Pz()) - self.fill(f"{pre}_pt", mom.perp()) self.fill(f"{pre}_e", mom.E()) ########################################################################### @@ -730,6 +728,9 @@ class Ntuple(): # Vrtx chi 2. self.fill(f"{pre}_vtx_chi2", vrt.chi2()) + + # PT. + self.fill(f"{pre}_pt", PT(prt)) return pvr ########################################################################### @@ -796,6 +797,7 @@ class Ntuple(): self.fill(f"{pre}_ip", ip.value) self.fill(f"{pre}_ip_chi2", ipChi2.value) self.fill(f"{pre}_dira", (DIRA(pvr)(prt))) + self.fill(f"{pre}_pt", PT(prt)) return pvr ########################################################################### -- GitLab From 28e0241ff17f8bf38704551075bbd4ccc55329fd Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 31 Jan 2025 16:05:06 +0000 Subject: [PATCH 58/70] CTAU needs 2 args --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 313f70b6aa..0205ee358e 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -724,7 +724,7 @@ class Ntuple(): self.fill(f"{pre}_dira", (DIRA(pvr)(prt))) # Decay time. - self.fill(f"{pre}_decay_time", DTF_CTAU(prt)) + self.fill(f"{pre}_decay_time", DTF_CTAU(prt, True)) # Vrtx chi 2. self.fill(f"{pre}_vtx_chi2", vrt.chi2()) -- GitLab From a4b5d3d747a0fbac0d2a48f7c93fb04be7cdd414 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 3 Feb 2025 13:46:10 +0000 Subject: [PATCH 59/70] add ctau to dtf and modify debug # Please enter the commit message for your changes. Lines starting --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 40 ++++++++++++++++++-------- bu2kdarkscalar_darkscalar2hh/debug.py | 5 ++++ bu2kdarkscalar_darkscalar2hh/dtf.py | 9 ++++++ bu2kdarkscalar_darkscalar2hh/job.py | 1 - 4 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 bu2kdarkscalar_darkscalar2hh/debug.py diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 0205ee358e..4ca61a7a14 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -10,13 +10,25 @@ from Configurables import DaVinci import GaudiPython from LoKiArrayFunctors.decorators import AMAXDOCA from LoKiMC.decorators import MCINANCESTORS, MCID -from LoKiPhys.decorators import DIRA, DTF_CTAU, PT +from LoKiPhys.decorators import DIRA, PT import ROOT from dtf import dtf #dtf: decay tree fitter import MCEventTools -class Ntuple(): +from bu2kdarkscalar_darkscalar2hh.debug import debug_print + +class Debuggable(): + def __getattribute__(self, name): + attr = object.__getattribute__(self, name) + if callable(attr) and not name.startswith("__"): + def wrapper(*args, **kwargs): + debug_print(f"Calling method: {name} with args: {args} kwargs: {kwargs}") + return attr(*args, **kwargs) + return wrapper + return attr + +class Ntuple(Debuggable): """Class for storing an ntuple.""" ########################################################################### @@ -116,7 +128,7 @@ class Ntuple(): "vt_d", "htrackOL", "dira", - "decay_time", + "dtf_decay_time", "vtx_chi2", ] + [ @@ -349,20 +361,20 @@ class Ntuple(): """ if mcp: # mcp: monte carlo particle, this is a boolean if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B - #debug_print(f"mcp.pid is set to {mcp.pid}") + debug_print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): #only when we inspect pid do we know the charges associated to hadron0 and hadron1 if mcp.pid in (-211, -321): dptype = "hm" - #debug_print(f"found mcp particle which is hm with pid {mcp.pid}") + debug_print(f"found mcp particle which is hm with pid {mcp.pid}") elif mcp.pid in (211, 321): dptype = "hp" - #debug_print(f"found mcp particle which is hp with pid {mcp.pid}") + debug_print(f"found mcp particle which is hp with pid {mcp.pid}") elif dptype == "K": if mcp.pid in (321, -321): dptype = "K" - #debug_print(f"found mcp particle which is K with pid {mcp.pid}") + debug_print(f"found mcp particle which is K with pid {mcp.pid}") pass else: raise ValueError(f"mcp.pid {mcp.pid} not recognized") @@ -532,8 +544,8 @@ class Ntuple(): return False return True - pdtf = dtf(prt, pvr, True) - ddtf = dtf(prt, pvr, False) + pdtf = dtf(prt, pvr, True) #decay tree fitter with mass constraint + ddtf = dtf(prt, pvr, False) #decay tree fitter without mass constraint if prt.particleID().pid() in (-521, 521): # B- or B+ pdtf.fit() @@ -541,6 +553,9 @@ class Ntuple(): assert pdtf.dihadron == ddtf.dihadron assert pdtf.dihadron is not None dtrp = pdtf.dihadron.momentum() + + #help(prt) + elif prt.particleID().pid() == 310: # dihadron candidate dtrp = prt.momentum() @@ -723,15 +738,16 @@ class Ntuple(): # DIRA. self.fill(f"{pre}_dira", (DIRA(pvr)(prt))) - # Decay time. - self.fill(f"{pre}_decay_time", DTF_CTAU(prt, True)) - # Vrtx chi 2. self.fill(f"{pre}_vtx_chi2", vrt.chi2()) # PT. self.fill(f"{pre}_pt", PT(prt)) + + self.fill(f"{pre}_ddtf_decay_time", ddtf.get_ctau()) + return pvr + ########################################################################### def _fillTrk(self, pre: str, prt, org, year: "int", run: "int"): diff --git a/bu2kdarkscalar_darkscalar2hh/debug.py b/bu2kdarkscalar_darkscalar2hh/debug.py new file mode 100644 index 0000000000..2ef3e72867 --- /dev/null +++ b/bu2kdarkscalar_darkscalar2hh/debug.py @@ -0,0 +1,5 @@ + +DEBUG = False +def debug_print(*args): + if DEBUG: + print(*args) \ No newline at end of file diff --git a/bu2kdarkscalar_darkscalar2hh/dtf.py b/bu2kdarkscalar_darkscalar2hh/dtf.py index 826477d4e8..f756c006e5 100644 --- a/bu2kdarkscalar_darkscalar2hh/dtf.py +++ b/bu2kdarkscalar_darkscalar2hh/dtf.py @@ -14,6 +14,7 @@ class dtf: self._dtf = None # the DecayTreeFitter (must be stored for persistency) self._dtf_mom = None # the fitted B momentum + self._dtf_ctau = None # the ctau self._dtf_cov = None # the covariance matrix of the fitted position self._dtf_pos = None # the fitted vertex position self._dtf_chi2 = None # the chi2 of the fit @@ -29,6 +30,7 @@ class dtf: self._reset_prompt() params = [ self._dtf_mom, + self._dtf_ctau, self._dtf_cov, self._dtf_pos, self._dtf_chi2, @@ -72,8 +74,10 @@ class dtf: if self._dtf.status() == self._dtf.Success: dtf_params = self._dtf.fitParams(self.particle) dtf_momentum = dtf_params.momentum() + dtf_ctau = dtf_params.ctau() if dtf_momentum.m().error() != 0: self._dtf_mom = dtf_momentum + self._dtf_ctau = dtf_ctau self._dtf_chi2 = self._dtf.chiSquare() self._dtf_cov = dtf_params.posCovMatrix() self._dtf_pos = dtf_params.position() @@ -106,3 +110,8 @@ class dtf: def get_dih_momentum(self): """Return the fitted dihadron momentum.""" return self._dtf_dtrp + + def get_ctau(self): + """Return the ctau.""" + return self._dtf_ctau + diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 5a709966ed..1537fec627 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -164,7 +164,6 @@ if "MDST" == DaVinci().InputType: # Run. gaudi = GaudiPython.AppMgr() tes = gaudi.evtsvc() - ntuple = Ntuple(gaudi, output_filename) # Setting up tools for MC BG categorizing genTool = ( -- GitLab From a13c00406a590438d9a45d3c54a21f8ef604e1c7 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 3 Feb 2025 14:03:48 +0000 Subject: [PATCH 60/70] decay time branches named wrong --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 4ca61a7a14..a62db2e17d 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -13,7 +13,7 @@ from LoKiMC.decorators import MCINANCESTORS, MCID from LoKiPhys.decorators import DIRA, PT import ROOT -from dtf import dtf #dtf: decay tree fitter +from dtf import dtf #dtf: decay tree fitter, this is defined in the dtf.py file, needed to add the branches which are dtf varibles import MCEventTools from bu2kdarkscalar_darkscalar2hh.debug import debug_print @@ -128,13 +128,12 @@ class Ntuple(Debuggable): "vt_d", "htrackOL", "dira", - "dtf_decay_time", "vtx_chi2", ] + [ f"{dtf}dtf_{x}" for dtf in ["p", "d"] # p: parent, d: daughter - for x in ["m", "dm", "chi2"] + for x in ["m", "dm", "chi2", "decay_time"] + vrsMom + vrsVrt + [f"dih_{y}" for y in ["m"] + vrsMom] @@ -744,7 +743,9 @@ class Ntuple(Debuggable): # PT. self.fill(f"{pre}_pt", PT(prt)) + # Decay time. self.fill(f"{pre}_ddtf_decay_time", ddtf.get_ctau()) + self.fill(f"{pre}_pdtf_decay_time", pdtf.get_ctau()) return pvr -- GitLab From 8b836db79dab2f135ec69905ccdba24434b3c40e Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 3 Feb 2025 17:10:54 +0000 Subject: [PATCH 61/70] ctau correction now working --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 6 ++++-- bu2kdarkscalar_darkscalar2hh/dtf.py | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index a62db2e17d..68270f24d5 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -744,8 +744,10 @@ class Ntuple(Debuggable): self.fill(f"{pre}_pt", PT(prt)) # Decay time. - self.fill(f"{pre}_ddtf_decay_time", ddtf.get_ctau()) - self.fill(f"{pre}_pdtf_decay_time", pdtf.get_ctau()) + self.fill(f"{pre}_ddtf_decay_time", -1 if ddtf.get_ctau() is None else ddtf.get_ctau().value()) + + + self.fill(f"{pre}_pdtf_decay_time", -1 if pdtf.get_ctau() is None else pdtf.get_ctau().value()) return pvr diff --git a/bu2kdarkscalar_darkscalar2hh/dtf.py b/bu2kdarkscalar_darkscalar2hh/dtf.py index f756c006e5..117814acbd 100644 --- a/bu2kdarkscalar_darkscalar2hh/dtf.py +++ b/bu2kdarkscalar_darkscalar2hh/dtf.py @@ -3,6 +3,9 @@ from typing import Any import GaudiPython +from GaudiKernel.PhysicalConstants import c_light + + class dtf: """Create a DecayTreeFitter and extract some information.""" @@ -14,7 +17,7 @@ class dtf: self._dtf = None # the DecayTreeFitter (must be stored for persistency) self._dtf_mom = None # the fitted B momentum - self._dtf_ctau = None # the ctau + self._dtf_ctau = None # the proper Decay Time self._dtf_cov = None # the covariance matrix of the fitted position self._dtf_pos = None # the fitted vertex position self._dtf_chi2 = None # the chi2 of the fit @@ -77,7 +80,7 @@ class dtf: dtf_ctau = dtf_params.ctau() if dtf_momentum.m().error() != 0: self._dtf_mom = dtf_momentum - self._dtf_ctau = dtf_ctau + self._dtf_ctau = dtf_ctau/c_light self._dtf_chi2 = self._dtf.chiSquare() self._dtf_cov = dtf_params.posCovMatrix() self._dtf_pos = dtf_params.position() @@ -112,6 +115,6 @@ class dtf: return self._dtf_dtrp def get_ctau(self): - """Return the ctau.""" + """Return the get the decay time in units of time.""" return self._dtf_ctau -- GitLab From 399b24859395b387c1195f96751ac2bdf2a2c4b6 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 4 Feb 2025 15:30:50 +0000 Subject: [PATCH 62/70] test --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 22 +++------------------- bu2kdarkscalar_darkscalar2hh/debug.py | 5 ----- 2 files changed, 3 insertions(+), 24 deletions(-) delete mode 100644 bu2kdarkscalar_darkscalar2hh/debug.py diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 68270f24d5..403d0af407 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -16,19 +16,7 @@ import ROOT from dtf import dtf #dtf: decay tree fitter, this is defined in the dtf.py file, needed to add the branches which are dtf varibles import MCEventTools -from bu2kdarkscalar_darkscalar2hh.debug import debug_print - -class Debuggable(): - def __getattribute__(self, name): - attr = object.__getattribute__(self, name) - if callable(attr) and not name.startswith("__"): - def wrapper(*args, **kwargs): - debug_print(f"Calling method: {name} with args: {args} kwargs: {kwargs}") - return attr(*args, **kwargs) - return wrapper - return attr - -class Ntuple(Debuggable): +class Ntuple(): """Class for storing an ntuple.""" ########################################################################### @@ -360,20 +348,16 @@ class Ntuple(Debuggable): """ if mcp: # mcp: monte carlo particle, this is a boolean if mcp.pid in (310, 211, -211, 321, -321, 521, -521): #MC particle must be Ks, +/- pi, +/- K, +/- B - debug_print(f"mcp.pid is set to {mcp.pid}") if dptype in ("hadron0", "hadron1"): #only when we inspect pid do we know the charges associated to hadron0 and hadron1 if mcp.pid in (-211, -321): dptype = "hm" - debug_print(f"found mcp particle which is hm with pid {mcp.pid}") elif mcp.pid in (211, 321): - dptype = "hp" - debug_print(f"found mcp particle which is hp with pid {mcp.pid}") + dptype = "hp" elif dptype == "K": if mcp.pid in (321, -321): dptype = "K" - debug_print(f"found mcp particle which is K with pid {mcp.pid}") pass else: raise ValueError(f"mcp.pid {mcp.pid} not recognized") @@ -913,7 +897,7 @@ def fill_ntuple( debug_print(f"got {len(all_Bparticles)} B mesons from {candidate_loc}") - if DaVinci().Simulation and len(all_Bparticles) > 0: + if DaVinci().Simulation: mct = MCEventTools.MCEvent(all_Bparticles, genTool, hh) # mct: monte carlo tool mct.linkpars() mcpstore = get_particles('/Event/AllStreams/MC/Particles') diff --git a/bu2kdarkscalar_darkscalar2hh/debug.py b/bu2kdarkscalar_darkscalar2hh/debug.py deleted file mode 100644 index 2ef3e72867..0000000000 --- a/bu2kdarkscalar_darkscalar2hh/debug.py +++ /dev/null @@ -1,5 +0,0 @@ - -DEBUG = False -def debug_print(*args): - if DEBUG: - print(*args) \ No newline at end of file -- GitLab From 455667b5a5d1c3b89dbedf71df8d6dfb4f9a0b75 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Fri, 7 Feb 2025 14:36:05 +0000 Subject: [PATCH 63/70] add new jobs --- bu2kdarkscalar_darkscalar2hh/info.yaml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index ae377d3eec..5df9c9b6b9 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,7 +1,9 @@ {%- set polarities = ["Down", "Up"]%} -{%- set MCnormalisationDatasets = [('KK', 18, 12103012, 6500, '18', '34NoPrescalingFlagged')]%} -{%- set MCsignalDatasets = [('KK', 18, 12103047, 6500, '18', '34NoPrescalingFlagged'), - ('PiPi', 18, 12103056, 6500, '18', '34NoPrescalingFlagged')]%} + +{%- set MCnormalisationDatasets = [('KK', 18, 12103012, 6500, '18', '34NoPrescalingFlagged'), + ('PiPi', 18, 12103024, 6500, '18', '34NoPrescalingFlagged')]%} +{%- set MCsignalDatasets = [('KK', 18, 12103047, '1100', '20', 6500, '18', '34NoPrescalingFlagged'), + ('PiPi', 18, 12103056, '1100', '20', 6500, '18', '34NoPrescalingFlagged')]%} {%- for polarity in polarities %} {%- for hh, year, eventnumber, energy, reco, strip in MCnormalisationDatasets %} @@ -25,9 +27,9 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: {%- endfor %} -{%- for hh, year, eventnumber, energy, reco, strip in MCsignalDatasets %} +{%- for hh, year, eventnumber, chiMass, chiLifetime, energy, reco, strip in MCsignalDatasets %} -my_B2KDarkScalar_DarkScalar2{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: +my_B2KDarkScalar_DarkScalar2{{hh}}_{{massChi}}MeV{{lieftimeChi}}ps_20{{year}}_Mag{{polarity}}_MC_job: application: DaVinci/v46r8 wg: QEE automatically_configure: yes @@ -46,4 +48,17 @@ my_B2KDarkScalar_DarkScalar2{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: {%- endfor %} + + +my_20{{year}}_Mag{{polarity}}_{{hh}}_Data_job: + input: + bk_query: "/LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34/90000000/LEPTONIC.MDST" + options: + command: + - python + files: + - job.py + - hh_{{hh}}.py + + {%- endfor %} \ No newline at end of file -- GitLab From 97f794ba04386e7a4bc367bb613b5e44ab29e224 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 10 Feb 2025 09:31:26 +0000 Subject: [PATCH 64/70] fix typo --- bu2kdarkscalar_darkscalar2hh/info.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 5df9c9b6b9..fd8ac69b67 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -29,7 +29,7 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: {%- for hh, year, eventnumber, chiMass, chiLifetime, energy, reco, strip in MCsignalDatasets %} -my_B2KDarkScalar_DarkScalar2{{hh}}_{{massChi}}MeV{{lieftimeChi}}ps_20{{year}}_Mag{{polarity}}_MC_job: +my_B2KDarkScalar_DarkScalar2{{hh}}_{{chiMass}}MeV{{chiLifetime}}ps_20{{year}}_Mag{{polarity}}_MC_job: application: DaVinci/v46r8 wg: QEE automatically_configure: yes -- GitLab From 68aade1209cfa6d9c44d17f815e7f8cac33a39ad Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 10 Feb 2025 09:53:40 +0000 Subject: [PATCH 65/70] restructure jobs --- bu2kdarkscalar_darkscalar2hh/info.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index fd8ac69b67..58a3b6cfac 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -5,7 +5,11 @@ {%- set MCsignalDatasets = [('KK', 18, 12103047, '1100', '20', 6500, '18', '34NoPrescalingFlagged'), ('PiPi', 18, 12103056, '1100', '20', 6500, '18', '34NoPrescalingFlagged')]%} +{%- set Datasets = [('KK', 18), + ('PiPi', 18)]%} + {%- for polarity in polarities %} + {%- for hh, year, eventnumber, energy, reco, strip in MCnormalisationDatasets %} my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: @@ -48,7 +52,7 @@ my_B2KDarkScalar_DarkScalar2{{hh}}_{{chiMass}}MeV{{chiLifetime}}ps_20{{year}}_Ma {%- endfor %} - +{%- for hh, year in Datasets %} my_20{{year}}_Mag{{polarity}}_{{hh}}_Data_job: input: @@ -59,6 +63,9 @@ my_20{{year}}_Mag{{polarity}}_{{hh}}_Data_job: files: - job.py - hh_{{hh}}.py + output: b2kdarkscalar_darkscalar2hh_data.root +{%- endfor %} + {%- endfor %} \ No newline at end of file -- GitLab From 3044d0853df2466b0dfe299ea96d9f981ecb0606 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 10 Feb 2025 10:25:00 +0000 Subject: [PATCH 66/70] correct --- bu2kdarkscalar_darkscalar2hh/info.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index 58a3b6cfac..eb441084b0 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -55,6 +55,12 @@ my_B2KDarkScalar_DarkScalar2{{hh}}_{{chiMass}}MeV{{chiLifetime}}ps_20{{year}}_Ma {%- for hh, year in Datasets %} my_20{{year}}_Mag{{polarity}}_{{hh}}_Data_job: + application: DaVinci/v46r8 + wg: QEE + automatically_configure: yes + turbo: no + inform: + - eleanor.whiter@cern.ch input: bk_query: "/LHCb/Collision18/Beam6500GeV-VeloClosed-Mag{{polarity}}/Real Data/Reco18/Stripping34/90000000/LEPTONIC.MDST" options: -- GitLab From 39e390757d95832b09274e933fe3dc664ee8d3fe Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 10 Feb 2025 11:26:30 +0000 Subject: [PATCH 67/70] bk query correction --- bu2kdarkscalar_darkscalar2hh/info.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index eb441084b0..f971249eed 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -1,7 +1,7 @@ {%- set polarities = ["Down", "Up"]%} -{%- set MCnormalisationDatasets = [('KK', 18, 12103012, 6500, '18', '34NoPrescalingFlagged'), - ('PiPi', 18, 12103024, 6500, '18', '34NoPrescalingFlagged')]%} +{%- set MCnormalisationDatasets = [('KK', 18, 12103012, 6500, '10d', '18', '34NoPrescalingFlagged'), + ('PiPi', 18, 12103024, 6500, '09h', '18', '34NoPrescalingFlagged')]%} {%- set MCsignalDatasets = [('KK', 18, 12103047, '1100', '20', 6500, '18', '34NoPrescalingFlagged'), ('PiPi', 18, 12103056, '1100', '20', 6500, '18', '34NoPrescalingFlagged')]%} @@ -20,7 +20,7 @@ my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: inform: - eleanor.whiter@cern.ch input: - bk_query: /MC/20{{year}}/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Nu1.6-25ns-Pythia8/Sim10d/Trig0x617d18a4/Reco{{year}}/Turbo05-WithTurcal/Stripping{{strip}}/{{eventnumber}}/ALLSTREAMS.MDST + bk_query: /MC/20{{year}}/Beam{{energy}}GeV-20{{year}}-Mag{{polarity}}-Nu1.6-25ns-Pythia8/Sim{{sim}}/Trig0x617d18a4/Reco{{year}}/Turbo05-WithTurcal/Stripping{{strip}}/{{eventnumber}}/ALLSTREAMS.MDST options: command: - python -- GitLab From 2f29e9d5534003872fd32a8719fa64d01a393483 Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Mon, 10 Feb 2025 13:03:47 +0000 Subject: [PATCH 68/70] correct info.yaml --- bu2kdarkscalar_darkscalar2hh/info.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bu2kdarkscalar_darkscalar2hh/info.yaml b/bu2kdarkscalar_darkscalar2hh/info.yaml index f971249eed..e0c0241bc7 100644 --- a/bu2kdarkscalar_darkscalar2hh/info.yaml +++ b/bu2kdarkscalar_darkscalar2hh/info.yaml @@ -10,7 +10,7 @@ {%- for polarity in polarities %} -{%- for hh, year, eventnumber, energy, reco, strip in MCnormalisationDatasets %} +{%- for hh, year, eventnumber, energy, sim, reco, strip in MCnormalisationDatasets %} my_B2K{{hh}}_20{{year}}_Mag{{polarity}}_MC_job: application: DaVinci/v46r8 -- GitLab From 98561d3ec6b513f61cb88c13c108f9cea7acae4d Mon Sep 17 00:00:00 2001 From: Eleanor Whiter <eleanor.whiter@cern.ch> Date: Tue, 11 Feb 2025 09:16:22 +0000 Subject: [PATCH 69/70] fix dira errors --- bu2kdarkscalar_darkscalar2hh/Ntuple.py | 2 -- bu2kdarkscalar_darkscalar2hh/job.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/Ntuple.py b/bu2kdarkscalar_darkscalar2hh/Ntuple.py index 403d0af407..79bcc8aaf7 100644 --- a/bu2kdarkscalar_darkscalar2hh/Ntuple.py +++ b/bu2kdarkscalar_darkscalar2hh/Ntuple.py @@ -96,7 +96,6 @@ class Ntuple(): "dm", "idx_pvr", "veloCharge", - "dira", ] for h in range(0, self.nhit): vrsTrk += [f"x{h}", f"y{h}", f"z{h}", f"t{h}", f"p{h}"] @@ -799,7 +798,6 @@ class Ntuple(): self._dstTool.distance(prt, pvr, ip, ipChi2) self.fill(f"{pre}_ip", ip.value) self.fill(f"{pre}_ip_chi2", ipChi2.value) - self.fill(f"{pre}_dira", (DIRA(pvr)(prt))) self.fill(f"{pre}_pt", PT(prt)) return pvr diff --git a/bu2kdarkscalar_darkscalar2hh/job.py b/bu2kdarkscalar_darkscalar2hh/job.py index 1537fec627..cd6e437bfc 100644 --- a/bu2kdarkscalar_darkscalar2hh/job.py +++ b/bu2kdarkscalar_darkscalar2hh/job.py @@ -14,7 +14,7 @@ import ROOT #when running locally you need to uncomment the following lines #but if you are running on the grid, you need to comment them -#sys.path.append("../") +sys.path.append("../") -- GitLab From 9b7239bcdb6664d16733e7bec37ac7e53a627345 Mon Sep 17 00:00:00 2001 From: Daniel Johnson <daniel.johnson@cern.ch> Date: Tue, 11 Feb 2025 17:52:38 +0000 Subject: [PATCH 70/70] Fix memory leak (hopefully!) --- bu2kdarkscalar_darkscalar2hh/MCEventTools.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py index fb231e8cd3..d2cd954027 100644 --- a/bu2kdarkscalar_darkscalar2hh/MCEventTools.py +++ b/bu2kdarkscalar_darkscalar2hh/MCEventTools.py @@ -198,13 +198,15 @@ class MCEvent: dps = [0,0] dpsv = None svcoor = None - for dp in all_B.daughters(): # loop over the daughters of the B+/B- particle + for i_dp in range(len(all_B.daughters())): # loop over the daughters of the B+/B- particle + dp = all_B.daughters()[i_dp] if abs(ID(dp)) == 321: dps[1] = MCPart(dp,pnnk=PROBNNk(dp),pnnpi=PROBNNpi(dp),pnng=PROBNNghost(dp),cl=CL(dp)) else: hadrons = [0,0] dpsv = dp.endVertex() - for ddp in dp.endVertex().outgoingParticlesVector(): + for i_ddp in range(len(dp.endVertex().outgoingParticlesVector())): + ddp = dp.endVertex().outgoingParticlesVector()[i_ddp] hadron = MCPart(ddp) hadron.update(newpnnk=PROBNNk(ddp),newpnnpi=PROBNNpi(ddp),newpnng=PROBNNghost(ddp),newtrgp=TRGHOSTPROB(ddp)) #debug_print(hadron.pid) -- GitLab