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