23 """Module to collect matching information about the found particles and to generate
24 validation plots and figures of merit on the performance of track finding."""
26 """ Expert level behavior:
27 expert_level = default_expert_level: all figures and plots from this module except tree entries
28 expert_level > default_expert_level: everything including tree entries
29 expert_level <= default_expert_level//2: only basic figures
30 default_expert_level//2 < expert_level < default_expert_level: basic figures and basic tree entries
33 default_expert_level = 10
39 output_file_name=None,
40 reco_tracks_name='RecoTracks',
41 mc_reco_tracks_name='MCRecoTracks',
45 output_file_name = output_file_name or name + 'TrackingValidation.root'
47 super().__init__(foreach=mc_reco_tracks_name,
49 output_file_name=output_file_name,
51 expert_level=expert_level)
53 ## Name of the StoreArray of the tracks from pattern recognition
54 self.reco_tracks_name = reco_tracks_name
56 ## Name of the StoreArray of the ideal mc tracks
57 self.mc_reco_tracks_name = mc_reco_tracks_name
59 ## Reference to the track match lookup object reading the relation information constructed by the MCMatcherTracksModule
60 self.track_match_look_up = None
62 if self.expert_level >= self.default_expert_level:
63 ## Set of all detector and hits ids contained in any pr track. Updated each event.
64 self.found_det_hit_ids = set()
66 ## Set of all detector and hits ids contained in matched pr tracks. Updated each event.
67 self.matched_det_hit_ids = set()
69 ## Set of all detector and hits ids contained in clone pr tracks. Updated each event.
70 self.clone_det_hit_ids = set()
72 ## Set of all detector and hits ids contained in background and ghost pr tracks. Updated each event.
73 self.fake_det_hit_ids = set()
76 """Initialization signal at the start of the event processing"""
78 self.track_match_look_up = Belle2.TrackMatchLookUp(self.mc_reco_tracks_name, self.reco_tracks_name)
81 """Collect some statistics about the pattern recognition tracks used for comparison to the MC tracks
83 Executed once at the start of each event.
86 if self.expert_level >= self.default_expert_level:
87 reco_tracks = Belle2.PyStoreArray(self.reco_tracks_name)
88 track_match_look_up = self.track_match_look_up
90 found_det_hit_ids = set()
91 matched_det_hit_ids = set()
92 clone_det_hit_ids = set()
93 fake_det_hit_ids = set()
95 for reco_track in reco_tracks:
96 det_hit_ids = utilities.get_det_hit_ids(reco_track)
98 found_det_hit_ids |= det_hit_ids
100 if track_match_look_up.isAnyChargeMatchedPRRecoTrack(reco_track):
101 matched_det_hit_ids |= det_hit_ids
103 if track_match_look_up.isAnyChargeClonePRRecoTrack(reco_track):
104 clone_det_hit_ids |= det_hit_ids
106 if (track_match_look_up.isGhostPRRecoTrack(reco_track) or
107 track_match_look_up.isBackgroundPRRecoTrack(reco_track)):
108 fake_det_hit_ids |= det_hit_ids
110 self.found_det_hit_ids = found_det_hit_ids
111 self.matched_det_hit_ids = matched_det_hit_ids
112 self.clone_det_hit_ids = clone_det_hit_ids
113 self.fake_det_hit_ids = fake_det_hit_ids
115 def pick(self, mc_reco_track):
116 """Pick every MCRecoTrack"""
119 def peel(self, mc_reco_track):
120 """Looks at the individual Monte Carlo tracks and store information about them"""
121 track_match_look_up = self.track_match_look_up
123 # Analyse from the Monte Carlo reference side
124 mc_reco_tracks = Belle2.PyStoreArray(self.foreach)
125 multiplicity = mc_reco_tracks.getEntries()
127 mc_particle = track_match_look_up.getRelatedMCParticle(mc_reco_track)
128 is_primary = bool(mc_particle.hasStatus(Belle2.MCParticle.c_PrimaryParticle))
129 mc_to_pr_match_info_crops = self.peel_mc_to_pr_match_info(mc_reco_track)
130 mc_store_array_crops = peelers.peel_store_array_info(mc_reco_track, key="mc_{part_name}")
132 crops = dict(is_primary=is_primary,
133 multiplicity=multiplicity,
134 **mc_to_pr_match_info_crops,
135 **mc_store_array_crops
138 if self.expert_level >= self.default_expert_level:
139 reco_track = track_match_look_up.getRelatedPRRecoTrack(mc_reco_track)
140 mc_particle_crops = peelers.peel_mc_particle(mc_particle)
141 hit_content_crops = peelers.peel_reco_track_hit_content(mc_reco_track)
143 # Custom peel function to get single detector hit purities
144 subdetector_hit_efficiency_crops = peelers.peel_subdetector_hit_efficiency(mc_reco_track, reco_track)
146 mc_hit_efficiencies_in_all_pr_tracks_crops = self.peel_hit_efficiencies_in_all_pr_tracks(mc_reco_track)
149 event_meta_data = Belle2.PyStoreObj("EventMetaData")
150 event_crops = peelers.peel_event_info(event_meta_data)
152 # Store Array for easier joining
153 store_array_crops = peelers.peel_store_array_info(reco_track, key="pr_{part_name}")
155 # Information on PR reco track
156 pr_purity_information = {
157 "pr_hit_purity": track_match_look_up.getRelatedPurity(reco_track) if reco_track else float("nan"),
158 **peelers.peel_subdetector_hit_purity(reco_track=reco_track, mc_reco_track=mc_reco_track,
159 key="pr_{part_name}")
162 # Information on TrackFinders
163 trackfinder_crops = peelers.peel_trackfinder(reco_track)
165 # Basic peel function to get Quality Indicators
166 qualityindicator_crops = peelers.peel_quality_indicators(reco_track)
168 crops.update(dict(**hit_content_crops,
170 **subdetector_hit_efficiency_crops,
171 **mc_hit_efficiencies_in_all_pr_tracks_crops,
174 **pr_purity_information,
176 **qualityindicator_crops
181 def peel_mc_to_pr_match_info(self, mc_reco_track):
182 """Extracts track-match information from the MCMatcherTracksModule results"""
183 track_match_look_up = self.track_match_look_up
185 is_matched=track_match_look_up.isAnyChargeMatchedMCRecoTrack(mc_reco_track),
186 is_matched_correct_charge=track_match_look_up.isCorrectChargeMatchedMCRecoTrack(mc_reco_track),
187 is_matched_wrong_charge=track_match_look_up.isWrongChargeMatchedMCRecoTrack(mc_reco_track),
188 is_merged=track_match_look_up.isAnyChargeMergedMCRecoTrack(mc_reco_track),
189 is_merged_correct_charge=track_match_look_up.isCorrectChargeMergedMCRecoTrack(mc_reco_track),
190 is_merged_wrong_charge=track_match_look_up.isWrongChargeMergedMCRecoTrack(mc_reco_track),
191 is_missing=track_match_look_up.isMissingMCRecoTrack(mc_reco_track),
192 hit_efficiency=track_match_look_up.getRelatedEfficiency(mc_reco_track),
195 def peel_hit_efficiencies_in_all_pr_tracks(self, mc_reco_track):
196 """Extracts hit efficiencies"""
197 mc_det_hit_ids = utilities.get_det_hit_ids(mc_reco_track)
199 hit_efficiency_in_all_found = utilities.calc_hit_efficiency(self.found_det_hit_ids,
202 unfound_hit_efficiency = 1.0 - hit_efficiency_in_all_found
204 hit_efficiency_in_all_matched = utilities.calc_hit_efficiency(self.matched_det_hit_ids,
207 hit_efficiency_in_all_fake = utilities.calc_hit_efficiency(self.fake_det_hit_ids,
210 hit_efficiency_crops = dict(
211 hit_efficiency_in_all_found=hit_efficiency_in_all_found,
212 unfound_hit_efficiency=unfound_hit_efficiency,
213 hit_efficiency_in_all_matched=hit_efficiency_in_all_matched,
214 hit_efficiency_in_all_fake=hit_efficiency_in_all_fake,
216 return hit_efficiency_crops
218 # Refiners to be executed on terminate #
219 # #################################### #
221 ## Save a tree of all collected variables in a sub folder
222 save_tree = refiners.save_tree(
223 # using cond to suppress false doxygen warnings
226 folder_name="mc_tree",
227 above_expert_level=default_expert_level
232 save_tree_basic = refiners.save_tree(
233 # using cond to suppress false doxygen warnings
236 folder_name="mc_tree",
237 above_expert_level=default_expert_level // 2,
238 below_expert_level=default_expert_level
242 ## Generate the average finding efficiencies and hit efficiencies
243 save_overview_figures_of_merit = refiners.save_fom(
245 name="{module.id}_overview_figures_of_merit",
246 title="Overview figures in {module.title}",
247 aggregation=np.nanmean,
249 select={"is_matched": "finding efficiency", "hit_efficiency": "hit efficiency", },
250 filter_on="is_primary",
252finding efficiency - the ratio of matched primary Monte Carlo tracks to all Monte Carlo tracks
253hit efficiency - the ratio of hits picked up by the matched pattern recognition track of primary Monte Carlo tracks
258 # Default refiners that can be disabled with a lower expert_level
259 # #################################### #
261 ## Save a histogram of the hit efficiency
262 save_hit_efficiency_histogram = refiners.save_histograms(
264 above_expert_level=default_expert_level - 1,
265 select={"hit_efficiency": "hit efficiency"},
266 filter_on="is_primary",
267 description="Not a serious plot yet.",
271 ## Rename the efficiency-profile quantities so that they display nicely in ROOT TLatex
272 renaming_select_for_finding_efficiency_profiles = {
273 'is_matched': 'finding efficiency',
276 'multiplicity': 'multiplicity',
277 'phi0_truth': '#phi',
280 ## Make profile of finding efficiency
281 save_finding_efficiency_profiles = refiners.save_profiles(
283 above_expert_level=default_expert_level - 1,
284 select=renaming_select_for_finding_efficiency_profiles,
285 y='finding efficiency',
287 filter_on="is_primary",
293 ## Make profile of finding efficiency versus tan(lambda)
294 save_finding_efficiency_by_tan_lamba_profiles = refiners.save_profiles(
296 above_expert_level=default_expert_level - 1,
298 'is_matched': 'finding efficiency',
299 'tan_lambda_truth': 'tan #lambda'
301 y='finding efficiency',
303 filter_on="is_primary",
310 ## Make profiles of finding efficiency versus tan(lambda) grouped by transverse momentum
311 save_finding_efficiency_by_tan_lamba_in_pt_groups_profiles = refiners.save_profiles(
313 above_expert_level=default_expert_level - 1,
315 'is_matched': 'finding efficiency',
316 'tan_lambda_truth': 'tan #lambda'
318 y='finding efficiency',
320 filter_on="is_primary",
321 groupby=[("pt_truth", [0.070, 0.250, 0.600])],
328 # Make profiles of the hit efficiencies versus various fit parameters
329 ## Rename the hit-profile quantities so that they display nicely in ROOT TLatex
330 renaming_select_for_hit_efficiency_profiles = {
331 'hit_efficiency': 'hit efficiency',
334 'multiplicity': 'multiplicity',
335 'phi0_truth': '#phi',
338 ## Make profile of hit efficiency
339 save_hit_efficiency_profiles = refiners.save_profiles(
341 above_expert_level=default_expert_level - 1,
342 select=renaming_select_for_hit_efficiency_profiles,
345 filter_on="is_primary",
351 ## Make profile of finding efficiency versus tan(lambda)
352 save_hit_efficiency_by_tan_lambda_profiles = refiners.save_profiles(
354 above_expert_level=default_expert_level - 1,
356 'hit_efficiency': 'hit efficiency',
357 'tan_lambda_truth': 'tan #lambda',
361 filter_on="is_primary",
368 ## Charge dependent histograms
369 ## Make profile of finding efficiency versus pt, tan(lambda)
370 save_finding_efficiency_by_pt_profiles_groupbyCharge = refiners.save_profiles(
372 above_expert_level=default_expert_level - 1,
374 'is_matched': 'finding efficiency',
377 y='finding efficiency',
379 filter_on="is_primary",
380 groupby=[("charge_truth", [0.])],
386 ## Charge dependent histograms
387 ## Make profile of finding efficiency versus pt, tan(lambda)
388 save_finding_efficiency_by_tan_lambda_profiles_groupbyCharge = refiners.save_profiles(
390 above_expert_level=default_expert_level - 1,
392 'is_matched': 'finding efficiency',
393 'tan_lambda_truth': 'tan #lambda'
395 y='finding efficiency',
397 filter_on="is_primary",
398 groupby=[("charge_truth", [0.])],
405 ## Charge dependent histograms
406 ## Make profile of finding efficiency versus pt, tan(lambda)
407 save_hit_efficiency_by_pt_profiles_groupbyCharge = refiners.save_profiles(
409 above_expert_level=default_expert_level - 1,
411 'hit_efficiency': 'hit efficiency',
416 filter_on="is_primary",
417 groupby=[("charge_truth", [0.])],
423 ## Charge dependent histograms
424 ## Make profile of finding efficiency versus pt, tan(lambda)
425 save_hit_efficiency_by_tan_lambda_profiles_groupbyCharge = refiners.save_profiles(
427 above_expert_level=default_expert_level - 1,
429 'hit_efficiency': 'hit efficiency',
430 'tan_lambda_truth': 'tan #lambda',
434 filter_on="is_primary",
435 groupby=[("charge_truth", [0.])],
442 ## This creates a histogram for all MC track displaying the ratio of hits contained in any PR track
443 ## Hence this is distinctly larger than the hit efficiency to the matched PR track
444 ## Usefulness under discussion
445 save_hit_efficiency_in_all_found_hist = refiners.save_histograms(
447 above_expert_level=default_expert_level - 1,
448 # renaming quantity to name that is more suitable for display
449 select=dict(hit_efficiency_in_all_found="total hit efficiency vs. all reconstructed tracks")
453 ## This creates a histogram for *each missing* MC track displaying the ratio of hits contained in any PR tracks
454 ## High values in this hit efficiencies means that the MC track is consumed by other PR tracks but no proper
455 ## match could be established.
456 save_missing_mc_tracks_hit_efficiency_in_all_found_hist = refiners.save_histograms(
458 above_expert_level=default_expert_level - 1,
459 filter_on="is_missing", # show only the efficiencies of missing mc tracks
460 # renaming quantity to name that is more suitable for display
461 select=dict(hit_efficiency_in_all_found="total hit efficiency in all reconstructed tracks for missing mc tracks")