Belle II Software  release-05-01-25
ChargedPidMVAModule.py
1 #!/usr/bin/env python3
2 
3 # Doxygen should skip this script
4 # @cond
5 
6 """
7 This steering file fills an NTuple with the ChargedPidMVA score
8 for charged particle identification. By default, global PID info is stored,
9 meaning one signal hypothesis is tested against all others.
10 Optionally, binary PID can be stored, by testing one (or more) pair of (S,B) mass hypotheses.
11 
12 Usage:
13 
14 basf2 -i /PATH/TO/MDST/FILE.root analysis/examples/PostMdstIdentification/ChargedPidMVAModule.py -- [OPTIONS]
15 
16 Input: *_mdst_*.root
17 Output: *_ntup_*.root
18 
19 Example steering file - 2019 Belle II Collaboration.
20 """
21 
22 __author__ = "Marco Milesi"
23 __email__ = "marco.milesi@unimelb.edu.au"
24 
25 
26 import argparse
27 from modularAnalysis import getAnalysisGlobaltag
28 
29 
30 def argparser():
31 
32  parser = argparse.ArgumentParser(description=__doc__,
33  formatter_class=argparse.RawDescriptionHelpFormatter)
34 
35  def sb_pair(arg):
36  try:
37  s, b = map(int, arg.split(','))
38  return s, b
39  except BaseException:
40  raise argparse.ArgumentTypeError("Option string must be of the form 'S,B'")
41 
42  parser.add_argument("--matchTruth",
43  action="store_true",
44  default=False,
45  help="Apply truth-matching on particles.")
46  parser.add_argument("--testHyposPDGCodePair",
47  type=sb_pair,
48  nargs='+',
49  default=(0, 0),
50  help="Option required in binary mode."
51  "A list of pdgId pairs of the (S, B) charged stable particle mass hypotheses to test."
52  "Pass a space-separated list of (>= 1) S,B pdgIds, e.g.:"
53  "'--testHyposPDGCodePair 11,211 13,211'")
54  parser.add_argument("--addECLOnly",
55  dest="add_ecl_only",
56  action="store_true",
57  default=False,
58  help="Apply the BDT also for the ECL-only training."
59  "This will result in a separate score branch in the ntuple.")
60  parser.add_argument("--global_tag_append",
61  type=str,
62  nargs="+",
63  default=[getAnalysisGlobaltag()],
64  help="List of names of conditions DB global tag(s) to append on top of GT replay."
65  "NB: these GTs will have lowest priority."
66  "Pass a space-separated list of names.")
67  parser.add_argument("-d", "--debug",
68  dest="debug",
69  action="store",
70  default=0,
71  type=int,
72  choices=list(range(11, 20)),
73  help="Run the ChargedPidMVA module in debug mode. Pass the desired DEBUG level integer.")
74 
75  return parser
76 
77 
78 if __name__ == '__main__':
79 
80  args = argparser().parse_args()
81 
82  import basf2
83  from modularAnalysis import inputMdst, fillParticleLists, variablesToNtuple, matchMCTruth, applyCuts, applyChargedPidMVA
84  from variables import variables
85  from ROOT import Belle2
86 
87  for tag in args.global_tag_append:
88  basf2.conditions.append_globaltag(tag)
89  print(f"Appending GTs:\n{args.global_tag_append}")
90 
91  # ------------
92  # Create path.
93  # ------------
94 
95  path = basf2.create_path()
96 
97  # ----------
98  # Add input.
99  # ----------
100 
101  inputMdst(environmentType="default",
102  filename=basf2.find_file("mdst13.root", "validation"),
103  path=path)
104 
105  # ---------------------------------------
106  # Load standard charged stable particles,
107  # and fill corresponding particle lists.
108  # ---------------------------------------
109 
110  std_charged = {
111  Belle2.Const.electron.getPDGCode(): {"EVTGEN_ID": "e+", "NAME": "e", "FULLNAME": "electron", "CUT": ""},
112  Belle2.Const.muon.getPDGCode(): {"EVTGEN_ID": "mu+", "NAME": "mu", "FULLNAME": "muon", "CUT": ""},
113  Belle2.Const.pion.getPDGCode(): {"EVTGEN_ID": "pi+", "NAME": "pi", "FULLNAME": "pion", "CUT": ""},
114  Belle2.Const.kaon.getPDGCode(): {"EVTGEN_ID": "K+", "NAME": "K", "FULLNAME": "kaon", "CUT": ""},
115  Belle2.Const.proton.getPDGCode(): {"EVTGEN_ID": "p+", "NAME": "p", "FULLNAME": "proton", "CUT": ""},
116  Belle2.Const.deuteron.getPDGCode(): {"EVTGEN_ID": "deuteron", "NAME": "d", "FULLNAME": "deuteron", "CUT": ""},
117  }
118 
119  plists = [(f"{d.get('EVTGEN_ID')}:{d.get('FULLNAME')}s", d.get("CUT")) for d in std_charged.values()]
120 
121  fillParticleLists(plists, path=path)
122 
123  # --------------------------
124  # (Optional) truth matching.
125  # --------------------------
126 
127  if args.matchTruth:
128  for plistname, _ in plists:
129  matchMCTruth(plistname, path=path)
130  applyCuts(plistname, "isSignal == 1", path=path)
131 
132  # -------------------
133  # Global/Binary PID ?
134  # -------------------
135 
136  global_pid = (args.testHyposPDGCodePair == (0, 0))
137  binary_pid = not global_pid
138 
139  # ----------------------------------------------------------------------------
140  # Set variable aliases to be consistent with the names in the MVA weightfiles.
141  # ----------------------------------------------------------------------------
142 
143  variables.addAlias("__event__", "evtNum")
144  for det in ["SVD", "CDC", "TOP", "ARICH", "ECL", "KLM"]:
145  if global_pid:
146  for pdgId, d in std_charged.items():
147  variables.addAlias(f"{d.get('FULLNAME')}LogL_{det}", f"pidLogLikelihoodValueExpert({pdgId}, {det})")
148  elif binary_pid:
149  for s, b in args.testHyposPDGCodePair:
150  s_name = std_charged.get(s).get("NAME")
151  b_name = std_charged.get(b).get("NAME")
152  variables.addAlias(f"deltaLogL_{s_name}_{b_name}_{det}", f"pidDeltaLogLikelihoodValueExpert({s}, {b}, {det})")
153 
154  # ----------------------
155  # Apply charged Pid MVA.
156  # ----------------------
157 
158  if global_pid:
159  applyChargedPidMVA(particleLists=[plistname for plistname, _ in plists],
160  path=path,
161  trainingMode=Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_Multiclass)
162  if args.add_ecl_only:
163  applyChargedPidMVA(particleLists=[plistname for plistname, _ in plists],
164  path=path,
165  trainingMode=Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_ECL_Multiclass)
166  elif binary_pid:
167  for s, b in args.testHyposPDGCodePair:
168  applyChargedPidMVA(particleLists=[plistname for plistname, _ in plists],
169  path=path,
170  trainingMode=Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_Classification,
171  binaryHypoPDGCodes=(s, b))
172  if args.add_ecl_only:
173  applyChargedPidMVA(particleLists=[plistname for plistname, _ in plists],
174  path=path,
175  trainingMode=Belle2.ChargedPidMVAWeights.ChargedPidMVATrainingMode.c_ECL_Classification,
176  binaryHypoPDGCodes=(s, b))
177 
178  if args.debug:
179  for m in path.modules():
180  if "ChargedPidMVA" in m.name():
181  m.logging.log_level = basf2.LogLevel.DEBUG
182  m.logging.debug_level = args.debug
183 
184  # ---------------
185  # Make an NTuple.
186  # ---------------
187 
188  if global_pid:
189 
190  append = "_vs_".join(map(str, std_charged.keys()))
191 
192  variables = [f"pidChargedBDTScore({pdgId}, ALL)" for pdgId in std_charged]
193  if args.add_ecl_only:
194  variables += [f"pidChargedBDTScore({pdgId}, ECL)" for pdgId in std_charged]
195 
196  elif binary_pid:
197 
198  append = "__".join([f"{s}_vs_{b}" for s, b in args.testHyposPDGCodePair])
199 
200  variables = [f"pidPairChargedBDTScore({s}, {b}, ALL)" for s, b in args.testHyposPDGCodePair]
201  if args.add_ecl_only:
202  variables += [f"pidPairChargedBDTScore({s}, {b}, ECL)" for s, b in args.testHyposPDGCodePair]
203 
204  filename = f"chargedpid_ntuples__{append}.root"
205 
206  for plistname, _ in plists:
207  if global_pid:
208  variablesToNtuple(decayString=plistname,
209  variables=variables,
210  treename=plistname.split(':')[1],
211  filename=filename,
212  path=path)
213  elif binary_pid:
214  variablesToNtuple(decayString=plistname,
215  variables=variables,
216  treename=plistname.split(':')[1],
217  filename=filename,
218  path=path)
219 
220  # -----------------
221  # Monitor progress.
222  # -----------------
223 
224  progress = basf2.register_module("Progress")
225  path.add_module(progress)
226 
227  # ---------------
228  # Process events.
229  # ---------------
230 
231  # Start processing of modules.
232  basf2.process(path)
233 
234  # Print basf2 call statistics.
235  print(basf2.statistics)
236 
237 # @endcond
variablesToNtuple
Definition: variablesToNtuple.py:1
basf2.process
def process(path, max_event=0)
Definition: __init__.py:25