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