Belle II Software development
record.py
1#!/usr/bin/env python3
2
3
10
11import basf2
12
13from ROOT import Belle2 # make Belle2 namespace available
14
15import os
16import sys
17
18from tracking.validation.utilities import is_primary
19
20import tracking.harvest.harvesting as harvesting
21import tracking.harvest.refiners as refiners
22from tracking.harvest.run import HarvestingRun
23
24import trackfindingcdc.harvest.cdc_peelers as cdc_peelers
25
26import logging
27
28
29def get_logger():
30 return logging.getLogger(__name__)
31
32
34 """Harvester to generate, postprocess and inspect MC events for track-segment-pair fit validation"""
35
36 n_events = 10000
37
38 generator_module = "simple_gun" # Rather high momentum tracks should make the tracks rather straight.
39
40
41 monte_carlo = "no"
42
43 segment_orientation = "outwards"
44
45
46 fit_method_name = "fuse-sz"
47
48 @property
50 """Get the output ROOT filename"""
51 file_name = self.fit_method_name
52 file_name += "-mc-" + self.monte_carlomonte_carlo
53
55 file_name += "-" + os.path.split(self.root_input_fileroot_input_file)[1]
56 else:
57 file_name += ".root"
58
59 return file_name
60
61 def harvesting_module(self, path=None):
62 """Harvest and post-process the MC events"""
64 if path:
65 path.add_module(harvesting_module)
66 return harvesting_module
67
68 def create_argument_parser(self, **kwds):
69 """Convert command-line arguments to basf2 argument list"""
70 argument_parser = super().create_argument_parser(**kwds)
71
72 argument_parser.add_argument(
73 '-m',
74 '--monte-carlo',
75 choices=["no", "medium", "full"],
76 default=self.monte_carlomonte_carlo,
77 dest='monte_carlo',
78 help='Amount of monte carlo information to be used in the segment generation.',
79 )
80
81 argument_parser.add_argument(
82 "--fit",
83 choices=["zreco", "fuse-pre", "fuse-sz", "fuse-sz-re"],
84 default=self.fit_method_name,
85 dest="fit_method_name",
86 help=("Choose which fit positional information of the segment should be used. \n"
87 "* 'zreco' means the z coordinate is reconstructed and a linear sz fit is made. "
88 "No covariance between the circle and the linear sz part can be made.\n"
89 "* 'fuse-sz' means the Kalmanesk fuse of the two trajectory fits.\n"
90 "* 'fuse-sz-re' means the Kalmanesk fuse of the two trajectory fits but reestimate the drift length."
91 )
92 )
93
94 return argument_parser
95
96 def get_fit_method(self):
97 """Determine which track-segment-pair fitter to use"""
98 fit_method_name = self.fit_method_name
99
100 if fit_method_name == 'zreco':
102
103 def z_reconstruction_fit(pair):
104 return sz_fitter.update(pair)
105 return z_reconstruction_fit
106
107 elif fit_method_name.startswith('fuse-pre'):
110 fusionFit = CDCAxialStereoFusion()
111
112 def sz_segment_pair_preliminary_fit(pair):
113 fusionFit.fusePreliminary(pair)
114 return sz_segment_pair_preliminary_fit
115
116 elif fit_method_name.startswith('fuse-sz'):
119 reestimateDriftLength = fit_method_name.endswith("re")
120 fusionFit = CDCAxialStereoFusion(reestimateDriftLength)
121
122 def sz_segment_pair_fusion_fit(pair):
123 fusionFit.reconstructFuseTrajectories(pair)
124 return
125
126 trajectory3D = pair.getTrajectory3D()
127 revFromSegment = pair.getFromSegment().reversed()
128 revToSegment = pair.getToSegment().reversed()
129 revPair = CDCSegmentPair(revToSegment, revFromSegment)
130
131 CDCAxialStereoFusion.reconstructFuseTrajectories(revPair)
132 revTrajectory3D = revPair.getTrajectory3D().reversed()
133
134 # print("One origin x", trajectory3D.getLocalOrigin().x())
135 # print("One origin y", trajectory3D.getLocalOrigin().y())
136 # print("One origin z", trajectory3D.getLocalOrigin().z())
137
138 # print("Rev origin x", revTrajectory3D.getLocalOrigin().x())
139 # print("Rev origin y", revTrajectory3D.getLocalOrigin().y())
140 # print("Rev origin z", revTrajectory3D.getLocalOrigin().z())
141
142 print("One parameters", [trajectory3D.getLocalHelix().parameters()[i] for i in range(5)])
143 print("Rev parameters", [revTrajectory3D.getLocalHelix().parameters()[i] for i in range(5)])
144
145 print("One covariance")
146 for j in range(5):
147 print([trajectory3D.getLocalHelix().helixCovariance()(i, j) for i in range(5)])
148
149 print("Rev covariance")
150 for j in range(5):
151 print([revTrajectory3D.getLocalHelix().helixCovariance()(i, j) for i in range(5)])
152
153 # return revTrajectory3D
154 # return trajectory3D
155
156 return sz_segment_pair_fusion_fit
157
158 else:
159 raise ValueError(f"Unexpected fit_positions {fit_method_name}")
160
161 def create_path(self):
162 """
163 Sets up a path that plays back pregenerated events or generates events
164 based on the properties in the base class.
165 """
166 path = super().create_path()
167
168 path.add_module("TFCDC_WireHitPreparer",
169 flightTimeEstimation="outwards",
170 UseNLoops=0.5
171 )
172
173 path.add_module("TFCDC_ClusterPreparer")
174
175
176 if self.monte_carlomonte_carlo == "no":
177 # MC free - default
178 path.add_module("TFCDC_SegmentFinderFacetAutomaton",
179 SegmentOrientation="outwards"
180 )
181
182 path.add_module("TFCDC_SegmentFitter",
183 inputSegments="CDCSegment2DVector",
184 updateDriftLength=True,
185 useAlphaInDriftLength=True,
186 )
187
188 elif self.monte_carlomonte_carlo == "medium":
189 # Medium MC - proper generation logic, but true facets and facet relations
190 path.add_module("TFCDC_SegmentFinderFacetAutomaton",
191 FacetFilter="truth",
192 FacetRelationFilter="truth",
193 SegmentOrientation="outwards"
194 )
195
196 path.add_module("TFCDC_SegmentFitter",
197 inputSegments="CDCSegment2DVector",
198 updateDriftLength=True,
199 useAlphaInDriftLength=True,
200 )
201
202 elif self.monte_carlomonte_carlo == "full":
203 # Only true monte carlo segments
204 # make the positions realistic but keep the true drift length
205 path.add_module("TFCDC_SegmentCreatorMCTruth",
206 reconstructedDriftLength=False,
207 reconstructedPositions=True,
208 # segments="MCSegments"
209 )
210
211 path.add_module("TFCDC_SegmentFitter",
212 inputSegments="CDCSegment2DVector",
213 updateDriftLength=False,
214 # useAlphaInDriftLength=True,
215 )
216
217 else:
218 raise ValueError("Invalid degree of Monte Carlo information")
219
220 path.add_module("TFCDC_SegmentOrienter",
221 SegmentOrientation="outwards",
222 # SegmentOrientation="none",
223 inputSegments="CDCSegment2DVector",
224 segments="CDCSegment2DVectorOriented"
225 )
226
227 path.add_module("TFCDC_TrackFinderSegmentPairAutomaton",
228 inputSegments="CDCSegment2DVectorOriented",
229 WriteSegmentPairs=True,
230 SegmentPairFilter="truth",
231 SegmentPairFilterParameters={"allowReverse": True},
232 SegmentPairRelationFilter="none"
233 )
234
235 path.add_module(AxialStereoPairFitterModule(fit_method=self.get_fit_method()))
236 return path
237
238
239class SegmentPairFitValidationModule(harvesting.HarvestingModule):
240
241 """Module to collect information about the generated segment pairs and
242 compose validation plots on terminate."""
243
244 def __init__(self, output_file_name):
245 """Constructor"""
246 super().__init__(
247 output_file_name=output_file_name,
248 foreach="CDCSegmentPairVector"
249 )
250
251
253
254 def initialize(self):
255 """Receive signal at the start of event processing"""
257 super().initialize()
258
259 def prepare(self):
260 """Initialize the MC-hit lookup method"""
262
263 def pick(self, segment_pair):
264 """Select segment pairs with 4 or more hits per segment and a matching primary MC particle"""
265 mc_segment_lookup = self.mc_segment_lookup
266 from_segment = segment_pair.getFromSegment()
267 to_segment = segment_pair.getToSegment()
268 mc_particle = mc_segment_lookup.getMCParticle(from_segment)
269 return (mc_particle and
270 is_primary(mc_particle) and
271 from_segment.size() > 3 and
272 to_segment.size() > 3)
273
274 def peel(self, segment_pair):
275 """Aggregate the track and MC information for track-segment analysis"""
276 mc_segment_lookup = self.mc_segment_lookup
277
278 # from_segment = segment_pair.getFromSegment()
279 to_segment = segment_pair.getToSegment()
280
281 # mc_particle = mc_segment_lookup.getMCParticle(from_segment)
282
283 # Take the fit best at the middle of the segment pair
284 fit3d_truth = mc_segment_lookup.getTrajectory3D(to_segment)
285
286 fb_info = 1 if segment_pair.getAutomatonCell().getCellWeight() > 0 else -1
287 truth_crops = dict(
288 curvature_truth=fb_info * fit3d_truth.getCurvatureXY(),
289 tan_lambda_truth=fb_info * fit3d_truth.getTanLambda(),
290 )
291
292 segment_pair_crops = cdc_peelers.peel_segment_pair(segment_pair)
293 segment_pair_crops.update(truth_crops)
294 return segment_pair_crops
295
296 # Refiners to be executed at the end of the harvesting / termination of the module
297
298 save_histograms = refiners.save_histograms(outlier_z_score=5.0, allow_discrete=True)
299
300 save_tree = refiners.save_tree()
301
302
303 save_curvature_pull_aux = refiners.save_pull_analysis(
304 part_name="curvature",
305 folder_name="aux",
306 unit="1/cm",
307 absolute=False,
308 aux_names=["superlayer_id_pair", "tan_lambda_truth"],
309 which_plots=["aux"],
310 outlier_z_score=3.0)
311
312
313 save_curvature_pull = refiners.save_pull_analysis(
314 part_name="curvature",
315 unit="1/cm",
316 absolute=False,
317 groupby=[None, "superlayer_id_pair"],
318 outlier_z_score=3.0)
319
320
321 save_absolute_curvature_pull = refiners.save_pull_analysis(
322 part_name="curvature",
323 unit="1/cm",
324 absolute=True,
325 groupby=[None, "superlayer_id_pair"],
326 outlier_z_score=3.0)
327
328
329 save_tan_lambda_pull = refiners.save_pull_analysis(
330 part_name="tan_lambda",
331 quantity_name="tan #lambda",
332 absolute=False,
333 groupby=[None, "superlayer_id_pair"],
334 outlier_z_score=3.0)
335
336
337 save_fit_quality_histograms = refiners.save_histograms(
338 outlier_z_score=5.0,
339 select={"ndf": "ndf",
340 "chi2": "#chi2",
341 "p_value": "p-value",
342 },
343 groupby=[None, "superlayer_id_pair"],
344 description="Distribution of {part_name} in the segment fits",
345 )
346
347
348 save_fit_quality_by_tan_lambda_profiles = refiners.save_profiles(
349 select={
350 "p_value": "fit p-value",
351 "tan_lambda_truth": "true tan #lambda",
352 },
353 groupby=[None, "superlayer_id_pair"],
354 x="true tan #lambda",
355 y="fit p-value",
356 check=("The {y_part_name} should be essentially the same over"
357 "the whole range of the tan lambda spectrum"),
358 description=("Investigating the reconstruction quality for different "
359 "tan lambda regions of the CDC. Most notably is the superlayer dependency."
360 "For stereo superlayers the curve is not flat but has distinct slope."),
361 fit='line',
362 )
363
364
365class AxialStereoPairFitterModule(basf2.Module):
366 """Fit axial and stereo track segment pairs"""
367
368 @staticmethod
369 def default_fit_method(segmentPair):
370 """Default method to fit the generated segment pairs."""
371
373 CDCAxialStereoFusion.reconstructFuseTrajectories(segmentPair,
374 True)
375
376 def __init__(self, fit_method=None):
377 """Constructor"""
378
379
383 self.fit_method = fit_method
384 if not fit_method:
386
387 super().__init__()
388
389 def event(self):
390 """Called by basf2 for each event"""
391 self.fitStoredPairs()
392
393 def fitStoredPairs(self):
394 """Fits all pairs in the StoreArray with designated fit method."""
395
396 fit_method = self.fit_method
397
398 stored_segment_pair_relations = Belle2.PyStoreObj("CDCSegmentPairVector")
399 wrapped_segment_pair_relations = stored_segment_pair_relations.obj()
400 segment_pair_relations = wrapped_segment_pair_relations.get()
401
402 for segment_pair_relation in segment_pair_relations:
403 fit_method(segment_pair_relation)
404
405
406def main():
408 run.configure_and_execute_from_commandline()
409
410
411if __name__ == "__main__":
412 logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(levelname)s:%(message)s')
413 main()
def peel_segment_pair(segment_pair, key="{part_name}")
Definition: cdc_peelers.py:79
a (simplified) python wrapper for StoreObjPtr.
Definition: PyStoreObj.h:67
Utility class implementing the Kalmanesk combination of to two dimensional trajectories to one three ...
static const CDCMCHitLookUp & getInstance()
Getter for the singletone instance.
static const CDCMCSegment2DLookUp & getInstance()
Getter for the singletone instance.
static const CDCSZFitter & getFitter()
Getter for a standard sz line fitter instance.
Definition: CDCSZFitter.cc:36
Class representing a pair of one reconstructed axial segment and one stereo segment in adjacent super...
def default_fit_method(segmentPair)
Definition: record.py:369
def __init__(self, fit_method=None)
Definition: record.py:376
fit_method
fit_method : function A function called on each stored segment pair as its only argument to update it...
Definition: record.py:383
mc_segment_lookup
by default, there is no method to find matching MC track segments
Definition: record.py:252
def peel(self, segment_pair)
Definition: record.py:274
def pick(self, segment_pair)
Definition: record.py:263
def __init__(self, output_file_name)
Definition: record.py:244
def create_argument_parser(self, **kwds)
Definition: record.py:68
monte_carlo
Degree of refinement of the segment generation.
Definition: record.py:176
str fit_method_name
use the Kalmanesk fuse of the two trajectory fits
Definition: record.py:46
def harvesting_module(self, path=None)
Definition: record.py:61
str monte_carlo
do not generate new events
Definition: record.py:41
None output_file_name
Disable the writing of an output ROOT file.
Definition: run.py:20
root_input_file
generating events, so there is no ROOT input file
None root_input_file
By default, there is no input ROOT TFile.
Definition: minimal.py:113
None output_file_name
There is no default for the name of the output TFile.
Definition: mixins.py:60
Definition: main.py:1