Belle II Software  release-06-02-00
best_candidate_selection.py
1 
8 import basf2
9 import math
10 import random
11 from collections import defaultdict
12 import modularAnalysis as ma
13 from ROOT import Belle2
14 
15 
16 class Generator(basf2.Module):
17  """Generate a list of 10 electrons which have stupid momenta just to sort
18  them later. And then add one electron where all momentum components are
19  nan"""
20 
21  def initialize(self):
22  """We need to register the mc particles"""
23 
24  self.mcpmcp = Belle2.PyStoreArray("MCParticles")
25  self.mcpmcp.registerInDataStore()
26 
27  def event(self):
28  """And then we generate particles"""
29  print("New event:")
30  for i in range(10):
31  p = self.mcpmcp.appendNew()
32  p.setPDG(11)
33  p.setMassFromPDG()
34  p.setMomentum(random.randrange(1, 5), random.randrange(1, 5), random.randrange(1, 5))
35 
36  p = self.mcpmcp.appendNew()
37  p.setPDG(11)
38  p.setMassFromPDG()
39  p.setMomentum(math.nan, math.nan, math.nan)
40 
41 
42 class RankChecker(basf2.Module):
43  """Check if the ranks are actually what we want"""
44 
45  def initialize(self):
46  """Create particle list object"""
47 
48  self.plistplist = Belle2.PyStoreObj("e-")
49 
50  def event(self):
51  """And check all the ranks"""
52  # make a list of all the values and a dict of all the exta infos
53  px = []
54  py = []
55  einfo = defaultdict(list)
56  for p in self.plistplist:
57  px.append(p.getPx())
58  py.append(p.getPy())
59  # get all names of existing extra infos but convert to a native list of python strings to avoid
60  # possible crashes if the std::vector returned by the particle goes out of scope
61  names = [str(n) for n in p.getExtraInfoNames()]
62  for n in names:
63  einfo[n].append(p.getExtraInfo(n))
64 
65  # check the default name is set correctly if we don't specify an output variable
66  print(list(einfo.keys()))
67  assert 'M_rank' in einfo.keys(), "Default name is not as expected"
68 
69  # Now determine the correct ranks if multiple values are allowed:
70  # create a dictionary which will be value -> rank for all unique values
71  # in theory we just need loop over the sorted(set(values)) but we have
72  # special treatment for nans which should go always to the end of the
73  # list so sort with a special key that replaces nan by inf or -inf
74  # depending on sort order
75  px_value_ranks = {v: i for i, v in enumerate(sorted(set(px), reverse=True,
76  key=lambda v: -math.inf if math.isnan(v) else v), 1)}
77  py_value_ranks = {v: i for i, v in enumerate(sorted(set(py),
78  key=lambda v: math.inf if math.isnan(v) else v), 1)}
79 
80  # Ok, test if the rank from extra info actually corresponds to what we
81  # want
82  for v, r in zip(px, einfo["px_high_multi"]):
83  print(f"Value: {v}, rank: {r}, should be: {px_value_ranks[v]}")
84  assert r == px_value_ranks[v], "Rank is not correct"
85 
86  for v, r in zip(py, einfo["py_low_multi"]):
87  print(f"Value: {v}, rank: {r}, should be: {py_value_ranks[v]}")
88  assert r == py_value_ranks[v], "Rank is not correct"
89 
90  # so we checked multiRank=True. But for multiRank=False this is more
91  # complicated because ranking a second time will destroy the order
92  # of the previous sorts. But we can at least check if all the ranks
93  # form a range from 1..n if we sort them
94  simple_range = list(range(len(px)))
95  px_single_ranks = list(sorted(int(r) - 1 for r in einfo["px_high_single"]))
96  assert simple_range == px_single_ranks, "sorted ranks don't form a range from 1..n"
97  # but the second two rankings are on the same variable in the same
98  # order so they need to keep the order stable. so for py_low_single the
99  # ranks need to be the range without sorting
100  py_single_ranks = list(int(r) - 1 for r in einfo["py_low_single"])
101  assert simple_range == py_single_ranks, "ranks don't form a range from 1..n"
102 
103 
104 # fixed random numbers
105 random.seed(5)
106 # so lets create 10 events
107 path = basf2.Path()
108 path.add_module("EventInfoSetter", evtNumList=10)
109 # and put some electrons in there
110 path.add_module(Generator())
111 # load these electrons
112 ma.fillParticleListFromMC("e-", "", path=path)
113 # and sort them ...
114 ma.rankByHighest("e-", "M", path=path)
115 ma.rankByHighest("e-", "px", allowMultiRank=False, outputVariable="px_high_single", path=path)
116 ma.rankByHighest("e-", "px", allowMultiRank=True, outputVariable="px_high_multi", path=path)
117 ma.rankByLowest("e-", "py", allowMultiRank=False, outputVariable="py_low_single", path=path)
118 ma.rankByLowest("e-", "py", allowMultiRank=True, outputVariable="py_low_multi", path=path)
119 # and also check sorting
120 path.add_module(RankChecker())
121 
122 # we set numBest = 2: this is used also for the assert
123 numBest_value = 2
124 
125 
126 class NumBestChecker(basf2.Module):
127  """Check if 'numBest' works correctly"""
128 
129  def initialize(self):
130  """Create particle list 'e-:numbest' object"""
131 
132  self.plistplist = Belle2.PyStoreObj('e-:numBest')
133 
134  def event(self):
135  """Check if 'e-:numBest' has the expected size"""
136  size = self.plistplist.getListSize()
137  # The test fails if size > numBest_value
138  # since we will set numBest = numBest_value
139  assert size <= numBest_value, 'numBest test failed: there are too many Particles in the list!'
140 
141 
142 # create a new list
143 ma.fillParticleListFromMC('e-:numBest', '', path=path)
144 # sort the list, using numBest
145 ma.rankByHighest('e-:numBest', 'p', numBest=numBest_value, path=path)
146 # and check that numBest worked as expected
147 path.add_module(NumBestChecker())
148 
149 basf2.process(path)
a (simplified) python wrapper for StoreArray.
Definition: PyStoreArray.h:56
a (simplified) python wrapper for StoreObjPtr.
Definition: PyStoreObj.h:67