26 """Module to collect matching information about the found particles and to generate
27 validation plots and figures of merit on the performance of track finding."""
29 """ Expert level behavior:
30 expert_level <= default_expert_level: all figures and plots from this module except tree entries
31 expert_level > default_expert_level: everything including tree entries
34 default_expert_level = 10
40 output_file_name=None,
41 reco_tracks_name='RecoTracks',
42 mc_reco_tracks_name='MCRecoTracks',
46 output_file_name = output_file_name or name + 'TrackingValidation.root'
47 super().__init__(foreach=reco_tracks_name,
50 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 ## Cache for the hit content of the Monte Carlo tracks - updated each event
63 self.mc_reco_tracks_det_hit_ids = []
65 ## Cache for the MC hit lookup
66 self.mc_hit_lookup = Belle2.TrackFindingCDC.CDCMCHitLookUp.getInstance()
69 """Receive signal at the start of event processing"""
71 self.track_match_look_up = Belle2.TrackMatchLookUp(self.mc_reco_tracks_name,
72 self.reco_tracks_name)
75 """Called once at the start of each event"""
77 mc_reco_tracks = Belle2.PyStoreArray(self.mc_reco_tracks_name)
78 mc_reco_tracks_det_hit_ids = []
80 for mc_reco_track in mc_reco_tracks:
81 mc_reco_track_det_hit_ids = utilities.get_det_hit_ids(mc_reco_track)
82 mc_reco_tracks_det_hit_ids.append(mc_reco_track_det_hit_ids)
84 self.mc_reco_tracks_det_hit_ids = mc_reco_tracks_det_hit_ids
86 self.mc_hit_lookup.fill()
88 def pick(self, reco_track):
89 """Method to filter the track candidates to reject part of them"""
92 def peel(self, reco_track):
93 """Looks at the individual pattern recognition tracks and store information about them"""
94 track_match_look_up = self.track_match_look_up
96 # Matching information
97 mc_reco_track = track_match_look_up.getRelatedMCRecoTrack(reco_track)
98 mc_particle = track_match_look_up.getRelatedMCParticle(reco_track)
99 mc_particle_crops = peelers.peel_mc_particle(mc_particle)
101 hit_content_crops = peelers.peel_reco_track_hit_content(reco_track)
103 pr_to_mc_match_info_crops = self.peel_pr_to_mc_match_info(reco_track)
105 # Peel function to get hit purity of subdetectors
106 subdetector_hit_purity_crops = peelers.peel_subdetector_hit_purity(reco_track, mc_reco_track)
108 # Information on TrackFinders
109 trackfinder_crops = peelers.peel_trackfinder(reco_track)
111 # Basic peel function to get Quality Indicators
112 qualityindicator_crops = peelers.peel_quality_indicators(reco_track)
114 # Get the fit results
115 seed_fit_crops = peelers.peel_reco_track_seed(reco_track)
117 fit_result = track_match_look_up.getRelatedTrackFitResult(reco_track)
118 fit_crops = peelers.peel_track_fit_result(fit_result)
119 fit_status_crops = peelers.peel_fit_status(reco_track)
121 correct_rl_information = sum(peelers.is_correct_rl_information(cdc_hit, reco_track, self.mc_hit_lookup)
122 for cdc_hit in getObjectList(reco_track.getCDCHitList()))
125 correct_rl_information=correct_rl_information,
128 **pr_to_mc_match_info_crops,
129 **subdetector_hit_purity_crops, # Custom
131 **qualityindicator_crops,
137 if self.expert_level >= self.default_expert_level:
140 event_meta_data = Belle2.PyStoreObj("EventMetaData")
141 event_crops = peelers.peel_event_info(event_meta_data)
143 # Store Array for easier joining
144 store_array_crops = peelers.peel_store_array_info(reco_track, key="pr_{part_name}")
145 mc_store_array_crops = peelers.peel_store_array_info(mc_reco_track, key="mc_{part_name}")
147 # Information on PR reco track
148 mc_efficiency_information = {
149 "mc_hit_efficiency": track_match_look_up.getRelatedEfficiency(mc_reco_track) if mc_reco_track else float("nan"),
150 **peelers.peel_subdetector_hit_efficiency(reco_track=reco_track, mc_reco_track=mc_reco_track,
151 key="mc_{part_name}")
157 **mc_store_array_crops,
158 **mc_efficiency_information
163 def peel_pr_to_mc_match_info(self, reco_track):
164 """Extracts track-match information from the MCMatcherTracksModule results"""
165 track_match_look_up = self.track_match_look_up
166 is_matched = track_match_look_up.isAnyChargeMatchedPRRecoTrack(reco_track)
167 is_matched_correct_charge = track_match_look_up.isCorrectChargeMatchedPRRecoTrack(reco_track)
168 is_matched_wrong_charge = track_match_look_up.isWrongChargeMatchedPRRecoTrack(reco_track)
169 is_clone = track_match_look_up.isAnyChargeClonePRRecoTrack(reco_track)
170 is_clone_correct_charge = track_match_look_up.isCorrectChargeClonePRRecoTrack(reco_track)
171 is_clone_wrong_charge = track_match_look_up.isWrongChargeClonePRRecoTrack(reco_track)
172 is_background = track_match_look_up.isBackgroundPRRecoTrack(reco_track)
173 is_ghost = track_match_look_up.isGhostPRRecoTrack(reco_track)
175 reco_track_det_hit_ids = utilities.get_det_hit_ids(reco_track)
176 n_intersecting_mc_tracks = 0
177 for mc_reco_track_det_hit_ids in self.mc_reco_tracks_det_hit_ids:
178 intersects = len(mc_reco_track_det_hit_ids & reco_track_det_hit_ids) > 0
180 n_intersecting_mc_tracks += 1
182 mc_particle = track_match_look_up.getRelatedMCParticle(reco_track)
183 mc_is_primary = False
185 mc_is_primary = bool(mc_particle.hasStatus(Belle2.MCParticle.c_PrimaryParticle))
188 is_matched=is_matched,
189 is_matchedPrimary=is_matched and mc_is_primary,
190 is_matched_correct_charge=is_matched_correct_charge,
191 is_matchedPrimary_correct_charge=is_matched_correct_charge and mc_is_primary,
192 is_matched_wrong_charge=is_matched_wrong_charge,
193 is_matchedPrimary_wrong_charge=is_matched_wrong_charge and mc_is_primary,
196 is_clone_correct_charge=is_clone_correct_charge,
197 is_clone_wrong_charge=is_clone_wrong_charge,
199 is_background=is_background,
201 is_clone_or_match=(is_matched or is_clone),
202 is_fake=not (is_matched or is_clone),
203 hit_purity=track_match_look_up.getRelatedPurity(reco_track),
204 n_intersecting_mc_tracks=n_intersecting_mc_tracks,
207 # Refiners to be executed on terminate #
208 # #################################### #
210 ## Save a tree of all collected variables in a sub folder
211 save_tree = refiners.save_tree(
213 folder_name="pr_tree", name="pr_tree", above_expert_level=default_expert_level
217 ## Save RecoTrack clone-rate information
218 save_clone_rate = refiners.save_fom(
220 name="{module.id}_overview_figures_of_merit",
221 # Same as in the mc side module to combine the overview figures of merit into the same TNTuple
222 title="Overview figures in {module.title}",
223 description="clone_rate - ratio of clones divided the number of tracks that are related to a particle (clones and matches)",
227 filter_on="is_clone_or_match",
231 ## Make profile of the clone rate versus seed tan(lambda)
232 ## Rename the quantities to names that display nicely by root latex translation
233 save_clone_rate_by_seed_tan_lambda_profile = refiners.save_profiles(
235 filter_on="is_clone_or_match",
237 'is_clone': 'clone rate',
238 'seed_tan_lambda_estimate': 'seed tan #lambda',
249 ## Make profile of the clone rate versus seed phi0
250 ## Rename the quantities to names that display nicely by root latex translation
251 save_clone_rate_by_seed_phi0_profile = refiners.save_profiles(
254 'is_clone': 'clone rate',
255 'seed_phi0_estimate': 'seed #phi',
264 ## Make profile of the clone rate versus seed transverse momentum
265 ## Rename the quantities to names that display nicely by root latex translation
266 save_clone_rate_by_seed_pt_profile = refiners.save_profiles(
268 filter_on="is_clone_or_match",
270 'is_clone': 'clone rate',
271 'seed_pt_estimate': 'seed p_{t}',
282 ## Charge dependent histograms
283 ## Make profile of the clone rate versus seed transverse momentum
284 save_clone_rate_by_seed_pt_profile_groupbyCharge = refiners.save_profiles(
286 filter_on="is_clone_or_match",
288 'is_clone': 'clone rate',
289 'seed_pt_estimate': 'seed p_{t}',
293 groupby=[("charge_truth", [0.])],
301 ## Charge dependent histograms
302 ## Make profile of the clone rate versus seed tan(lambda)
303 save_clone_rate_by_seed_tan_lambda_profile_groupbyCharge = refiners.save_profiles(
305 filter_on="is_clone_or_match",
307 'is_clone': 'clone rate',
308 'seed_tan_lambda_estimate': 'seed tan #lambda',
312 groupby=[("charge_truth", [0.])],
320 ## Save RecoTrack fake-rate information
321 save_fake_rate = refiners.save_fom(
323 name="{module.id}_overview_figures_of_merit",
324 # Same as in the mc side module to combine the overview figures of merit into the same TNTuple
325 title="Overview figures in {module.title}",
326 description="fake_rate - ratio of pattern recognition tracks that are not related to a particle" +
327 "(background, ghost) to all pattern recognition tracks",
334 ## Make profile of the fake rate versus seed phi0
335 ## Rename the quantities to names that display nicely by root latex translation
336 save_fake_rate_by_seed_phi0_profile = refiners.save_profiles(
339 'is_fake': 'fake rate',
340 'seed_phi0_estimate': 'seed #phi',
348 ## Make profile of the fake rate versus seed tan(lambda)
349 ## Rename the quantities to names that display nicely by root latex translation
350 save_fake_rate_by_seed_tan_lambda_profile = refiners.save_profiles(
353 'is_fake': 'fake rate',
354 'seed_tan_lambda_estimate': 'seed tan #lambda',
364 ## Make profile of the fake rate versus seed transverse momentum
365 ## Rename the quantities to names that display nicely by root latex translation
366 save_fake_rate_by_seed_pt_profile = refiners.save_profiles(
369 'is_fake': 'fake rate',
370 'seed_pt_estimate': 'seed p_{t}',
380 ## Charge dependent histograms
381 ## Make profile of the fake rate versus seed tan(lambda)
382 save_fake_rate_by_seed_tan_lambda_profile_groupbyCharge = refiners.save_profiles(
384 filter_on="has_trackFitResult",
386 'is_fake': 'fake rate',
387 'seed_tan_lambda_estimate': 'seed tan #lambda',
394 groupby=[("track_charge", [0.])],
398 ## Charge dependent histograms
399 ## Make profile of the fake rate versus seed transverse momentum
400 save_fake_rate_by_seed_pt_profile_groupbyCharge = refiners.save_profiles(
402 filter_on="has_trackFitResult",
404 'is_fake': 'fake rate',
405 'seed_pt_estimate': 'seed p_{t}',
412 groupby=[("track_charge", [0.])],
416 ## Hit counts in each sub detector by the true pt value
417 save_hit_counts_by_pt_profile = refiners.save_profiles(
419 filter_on="is_matched",
421 "pt_truth": "true p_{t}",
422 "n_pxd_hits": "pxd hits",
423 "n_svd_hits": "svd hits",
424 "n_cdc_hits": "cdc hits",
437 ## Hit efficiency in each sub detector by the true pt value
438 save_hit_efficiency_by_pt_profile = refiners.save_profiles(
440 filter_on="is_matchedPrimary",
442 "pt_truth": "true p_{t}",
443 "mc_pxd_hit_efficiency": "pxd hit efficiency",
444 "mc_svd_hit_efficiency": "svd hit efficiency",
445 "mc_cdc_hit_efficiency": "cdc hit efficiency",
448 "pxd hit efficiency",
449 "svd hit efficiency",
450 "cdc hit efficiency",
458 ## Hit purity in each sub detector by the true pt value
459 save_hit_purity_by_pt_profile = refiners.save_profiles(
461 filter_on="is_matchedPrimary",
463 "pt_truth": "true p_{t}",
464 "pxd_hit_purity": "pxd hit purity",
465 "svd_hit_purity": "svd hit purity",
466 "cdc_hit_purity": "cdc hit purity",
479 ## Hit counts in each sub detector by the true tanlambda value
480 save_hit_counts_by_tanlambda_profile = refiners.save_profiles(
482 filter_on="is_matched",
484 "tan_lambda_truth": "true tan #lambda",
485 "n_pxd_hits": "pxd hits",
486 "n_svd_hits": "svd hits",
487 "n_cdc_hits": "cdc hits",
500 ## Hit efficiency in each sub detector by the true tanlambda value
501 save_hit_efficiency_by_tanlambda_profile = refiners.save_profiles(
503 filter_on="is_matchedPrimary",
505 "tan_lambda_truth": "true tan #lambda",
506 "mc_pxd_hit_efficiency": "pxd hit efficiency",
507 "mc_svd_hit_efficiency": "svd hit efficiency",
508 "mc_cdc_hit_efficiency": "cdc hit efficiency",
511 "pxd hit efficiency",
512 "svd hit efficiency",
513 "cdc hit efficiency",
521 ## Hit purity in each sub detector by the true tanlambda value
522 save_hit_purity_by_tanlambda_profile = refiners.save_profiles(
524 filter_on="is_matchedPrimary",
526 "tan_lambda_truth": "true tan #lambda",
527 "pxd_hit_purity": "pxd hit purity",
528 "svd_hit_purity": "svd hit purity",
529 "cdc_hit_purity": "cdc hit purity",
542 ## Charge dependent histograms
543 ## Hit counts in each sub detector by the true pt value
544 save_hit_counts_by_pt_profile_groupbyCharge = refiners.save_profiles(
546 filter_on="is_matched",
548 "pt_truth": "true p_{t}",
549 "n_pxd_hits": "pxd hits",
550 "n_svd_hits": "svd hits",
551 "n_cdc_hits": "cdc hits",
558 groupby=[("charge_truth", [0.])],
565 ## Charge dependent histograms
566 ## Hit counts in each sub detector by the true tan lambda value
567 save_hit_counts_by_tanlambda_profile_groupbyCharge = refiners.save_profiles(
569 filter_on="is_matched",
571 "tan_lambda_truth": "true tan #lambda",
572 "n_pxd_hits": "pxd hits",
573 "n_svd_hits": "svd hits",
574 "n_cdc_hits": "cdc hits",
581 groupby=[("charge_truth", [0.])],
588 ## Charge dependent histograms
589 ## Hit efficiency in each sub detector by the true pt value
590 save_hit_efficiency_by_pt_profile_groupbyCharge = refiners.save_profiles(
592 filter_on="is_matchedPrimary",
594 "pt_truth": "true p_{t}",
595 "mc_pxd_hit_efficiency": "pxd hit efficiency",
596 "mc_svd_hit_efficiency": "svd hit efficiency",
597 "mc_cdc_hit_efficiency": "cdc hit efficiency",
600 "pxd hit efficiency",
601 "svd hit efficiency",
602 "cdc hit efficiency",
604 groupby=[("charge_truth", [0.])],
611 ## Charge dependent histograms
612 ## Hit efficiency in each sub detector by the true tan lambda value
613 save_hit_efficiency_by_tanlambda_profile_groupbyCharge = refiners.save_profiles(
615 filter_on="is_matchedPrimary",
617 "tan_lambda_truth": "true tan #lambda",
618 "mc_pxd_hit_efficiency": "pxd hit efficiency",
619 "mc_svd_hit_efficiency": "svd hit efficiency",
620 "mc_cdc_hit_efficiency": "cdc hit efficiency",
623 "pxd hit efficiency",
624 "svd hit efficiency",
625 "cdc hit efficiency",
627 groupby=[("charge_truth", [0.])],
635 save_hit_efficiency = refiners.save_fom(
637 name="{module.id}_subdetector_figures_of_merit",
638 title="Overview figures in {module.title}",
639 description="Hit efficiency in the subdetectors",
640 key="hit efficiency",
641 select="mc_hit_efficiency",
642 aggregation=np.nanmean,
643 filter_on="is_matchedPrimary"
648 save_pxd_hit_efficiency = refiners.save_fom(
650 name="{module.id}_subdetector_figures_of_merit",
651 title="Overview figures in {module.title}",
652 description="Hit efficiency in the subdetectors",
653 key="pxd hit efficiency",
654 select="mc_pxd_hit_efficiency",
655 aggregation=np.nanmean,
656 filter_on="is_matchedPrimary"
661 save_svd_hit_efficiency = refiners.save_fom(
663 name="{module.id}_subdetector_figures_of_merit",
664 title="Overview figures in {module.title}",
665 description="Hit efficiency in the subdetectors",
666 key="svd hit efficiency",
667 select="mc_svd_hit_efficiency",
668 aggregation=np.nanmean,
669 filter_on="is_matchedPrimary"
674 save_cdc_hit_efficiency = refiners.save_fom(
676 name="{module.id}_subdetector_figures_of_merit",
677 title="Overview figures in {module.title}",
678 description="Hit efficiency in the subdetectors",
679 key="cdc hit efficiency",
680 select="mc_cdc_hit_efficiency",
681 aggregation=np.nanmean,
682 filter_on="is_matchedPrimary"
687 save_hit_purity = refiners.save_fom(
689 name="{module.id}_subdetector_figures_of_merit",
690 title="Overview figures in {module.title}",
691 description="Hit purity in the subdetectors",
694 aggregation=np.nanmean,
695 filter_on="is_matchedPrimary"
700 save_pxd_hit_purity = refiners.save_fom(
702 name="{module.id}_subdetector_figures_of_merit",
703 title="Overview figures in {module.title}",
704 description="Hit purity in the subdetectors",
705 key="pxd hit purity",
706 select="pxd_hit_purity",
707 aggregation=np.nanmean,
708 filter_on="is_matchedPrimary"
713 save_svd_hit_purity = refiners.save_fom(
715 name="{module.id}_subdetector_figures_of_merit",
716 title="Overview figures in {module.title}",
717 description="Hit purity in the subdetectors",
718 key="svd hit purity",
719 select="svd_hit_purity",
720 aggregation=np.nanmean,
721 filter_on="is_matchedPrimary"
726 save_cdc_hit_purity = refiners.save_fom(
728 name="{module.id}_subdetector_figures_of_merit",
729 title="Overview figures in {module.title}",
730 description="Hit purity in the subdetectors",
731 key="cdc hit purity",
732 select="cdc_hit_purity",
733 aggregation=np.nanmean,
734 filter_on="is_matchedPrimary"
738 ## Creates a distribution of p values from the Genfit track fit for match pr tracks.
739 save_p_value_histogram = refiners.save_histograms(
741 filter_on="is_matched",
742 select={"p_value": "Genfit p value"},
744 The distribution of p values from the Genfit track fit.
745 If all errors are propagated correctly the distribution should be flat.
746 Generally some peaking behavior towards zero is too be expected if the errors are underestimated.
748 check="The distribution should be flat."
752 ## Pull of seed omega
753 save_seed_omega_pull_analysis = refiners.save_pull_analysis(
755 filter_on="is_matched",
756 part_name="seed_omega",
757 quantity_name="seed #omega",
758 folder_name="pull_seed_omega",
759 truth_name="omega_truth",
764 ## Pull of seed tan(lambda)
765 save_seed_tan_lambda_pull_analysis = refiners.save_pull_analysis(
767 filter_on="is_matched",
768 part_name="seed_tan_lambda",
769 quantity_name="seed tan #lambda",
770 folder_name="pull_seed_tan_lambda",
771 truth_name="tan_lambda_truth",
775 ## Pull of fitted omega
776 save_fitted_omega_pull_analysis = refiners.save_pull_analysis(
778 filter_on="is_matched",
780 quantity_name="#omega",
781 folder_name="pull_fitted_omega",
786 ## Pull of fitted tan(lambda)
787 save_fitted_tan_lambda_pull_analysis = refiners.save_pull_analysis(
789 filter_on="is_matched",
790 part_name="tan_lambda",
791 quantity_name="tan #lambda",
792 folder_name="pull_fitted_tan_lambda",
796 ## Pull of fitted transverse momentum
797 save_fitted_pt_pull_analysis = refiners.save_pull_analysis(
799 filter_on="is_matched",
801 quantity_name="p_{t}",
802 folder_name="pull_fitted_p_t",
806 ## Pull of fitted x coordinate grouped by true transverse momentum
807 save_fitted_x_pull_analysis = refiners.save_pull_analysis(
809 filter_on="is_matched",
812 folder_name="pull_fitted_x{groupby_addition}",
813 groupby=[None, ("pt_truth", [0.070, 0.250, 0.600])],
817 ## Pull of fitted y coordinate grouped by true transverse momentum
818 save_fitted_y_pull_analysis = refiners.save_pull_analysis(
820 filter_on="is_matched",
823 folder_name="pull_fitted_y{groupby_addition}",
824 groupby=[None, ("pt_truth", [0.070, 0.250, 0.600])],
828 ## Pull of fitted z coordinate grouped by true transverse momentum
829 save_fitted_z_pull_analysis = refiners.save_pull_analysis(
831 filter_on="is_matched",
834 folder_name="pull_fitted_z{groupby_addition}",
835 groupby=[None, ("pt_truth", [0.070, 0.250, 0.600])],
839 ## Resolutions as a function of true p_t
840 save_resolutions_by_pt_profile = refiners.save_profiles(
842 filter_on="is_matched",
844 "pt_truth": "true p_{t}",
845 "d0_variance": "#sigma(d_{0})",
846 "z0_variance": "#sigma(z_{0})",
847 "pt_resolution": "#sigma(p_{t}) / p_{t}",
852 "#sigma(p_{t}) / p_{t}",