Belle II Software  release-08-01-10
record.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 
11 
12 # Fix TLS bug
13 # ctypes.cdll.LoadLibrary('/space/ofrost/basf2/release/lib/Linux_x86_64/opt/libtracking_trackFindingCDC.so')
14 
15 
16 from ROOT import Belle2 # make Belle2 namespace available
17 from ROOT.Belle2 import TrackFindingCDC as TFCDC
18 
19 import sys
20 import math
21 import numpy as np
22 
23 from tracking.validation.utilities import is_primary
24 
25 import tracking.harvest.harvesting as harvesting
26 import tracking.harvest.refiners as refiners
27 from tracking.harvest.run import HarvestingRun
28 
29 
30 import logging
31 
32 
33 def get_logger():
34  return logging.getLogger(__name__)
35 
36 
37 CONTACT = "oliver.frost@desy.de"
38 
39 
40 def getNaiveBetheEnergyLoss(p, pdg_code, s):
41  eloss = s * 0.00008 # Miriam's / Sasha's value
42  eloss = s * 1 * 10**-4 # Miriam's / Sasha's value
43  return eloss
44 
45 
46 def getBetheStoppingPower(p, pdg_code):
47  mass = 0.139 # Hard coded pions
48  energy = np.sqrt(mass * mass + p * p)
49 
50  gamma = energy / mass
51  beta = p / energy
52 
53  gamma2 = gamma * gamma
54  beta2 = beta * beta
55 
56  eMass = 0.511 * Belle2.Unit.MeV
57  m_I = 7.66932e-08
58  m_eDensity = 0.000515726
59 
60  Wmax = 2 * eMass * beta2 * gamma2 / (1 + 2 * gamma * eMass / mass)
61  I2 = m_I * m_I
62 
63  K = 0.307075 * Belle2.Unit.MeV * Belle2.Unit.cm2
64  dEdx = (
65  K * m_eDensity / beta2 *
66  (1.0 / 2.0 * math.log(2 * eMass * beta2 * gamma2 * Wmax / I2) - beta2)
67  )
68 
69  m_I = 4.54e-8 # Miriam's / Sasha's value
70  # I2 = 2.06116e-15
71  I2 = m_I * m_I
72 
73  factor = 178.6 * 0.00015e-04 / 2 # Miriam's / Sasha's value - to big...
74  # eDensity = factor / K
75  dEdx = factor / beta2 * (math.log(2 * eMass * beta2 * gamma2 * Wmax / I2) - beta2)
76 
77  # Miriam's / Sasha's original
78  # SR = (0.00015e-04*(2*(beta)**2)**(-1))*(math.log((0.0000010404*((beta)**4)*((gamma)**4))/
79  # (2.06116e-15*(1+0.007331*gamma)))-beta**2)
80  # dEdx = 178.6 * SR
81  return dEdx
82 
83 
84 def getBetheEnergyLoss(p, pdg_code, path):
85  dEdx = getBetheStoppingPower(p, pdg_code)
86  eloss = dEdx * path
87  return eloss
88 
89 
90 def getMomentumLossFactor(p, pdg_code, eloss):
91  p_factor = (p - eloss) / p
92  return p_factor
93 
94 
95 def DeltaR(path, particleID, P):
96  eloss = getBetheEnergyLoss(P, particleID, path)
97  return getMomentumLossFactor(P, particleID, eloss)
98 
99 
101  """Harvester to generate, postprocess and inspect MC events for energy-loss evaluation"""
102 
103  n_events = 10000
104 
105  generator_module = "eloss_gun"
106 
107  detector_setup = "TrackingDetectorConstB"
108 
109  @property
110  def output_file_name(self):
111  """Get the output ROOT filename"""
112  return 'eloss.root'
113 
114  def harvesting_module(self, path=None):
115  """Harvest and post-process the generated events"""
116  harvesting_module = ElossHarvestingModule(self.output_file_nameoutput_file_nameoutput_file_nameoutput_file_name)
117  if path:
118  path.add_module(harvesting_module)
119  return harvesting_module
120 
121  def create_argument_parser(self, **kwds):
122  """Convert command-line arguments to basf2 argument list"""
123  argument_parser = super().create_argument_parser(**kwds)
124  return argument_parser
125 
126  def create_path(self):
127  """
128  Sets up a path that plays back pregenerated events or generates events
129  based on the properties in the base class.
130  """
131  path = super().create_path()
132 
133  path.add_module("TFCDC_WireHitPreparer",
134  logLevel=8,
135  flightTimeEstimation="outwards",
136  UseNLoops=1)
137 
138  path.add_module('TFCDC_AxialTrackCreatorMCTruth',
139  logLevel=8,
140  useOnlyBeforeTOP=True,
141  fit=True,
142  reconstructedDriftLength=True,
143  reconstructedPositions=True)
144 
145  return path
146 
147 
148 class ElossHarvestingModule(harvesting.HarvestingModule):
149 
150  """Module to collect information about the generated segments and
151  compose validation plots on terminate."""
152 
153  def __init__(self, output_file_name):
154  """Constructor"""
155  super().__init__(foreach='CDCTrackVector',
156  output_file_name=output_file_name)
157 
158 
159  self.mc_track_lookupmc_track_lookup = None
160 
161  origin_track_fitter = TFCDC.CDCRiemannFitter()
162  origin_track_fitter.setOriginConstrained()
163 
164  self.track_fittertrack_fitter = origin_track_fitter
165 
166  def initialize(self):
167  """Receive signal at the start of event processing"""
168  super().initialize()
169 
170  self.mc_track_lookupmc_track_lookup = TFCDC.CDCMCTrackLookUp.getInstance()
171 
172  self.mc_hit_lookupmc_hit_lookup = TFCDC.CDCMCHitLookUp.getInstance()
173 
174  self.eloss_estimatoreloss_estimator = TFCDC.EnergyLossEstimator.forCDC()
175 
176  self.bfieldbfield = TFCDC.CDCBFieldUtil
177 
178  def prepare(self):
179  """Initialize the MC-hit lookup method"""
180  TFCDC.CDCMCHitLookUp.getInstance().fill()
181 
182  def pick(self, track):
183  """Select tracks with at least 4 segments and associated primary MC particle with pt >= 0.25 GeV/c"""
184  if track.size() < 4:
185  return False
186 
187  mc_track_lookup = self.mc_track_lookupmc_track_lookup
188  mc_particle = mc_track_lookup.getMCParticle(track)
189 
190  # Check that mc_particle is not a nullptr
191  if mc_particle is None:
192  return False
193 
194  if mc_particle.getMomentum().Rho() < 0.250:
195  return False
196 
197  return is_primary(mc_particle)
198 
199  def peel(self, track):
200  """Aggregate the track and MC information for dE/dx analysis"""
201  mc_track_lookup = self.mc_track_lookupmc_track_lookup
202 
203  # rl_drift_circle = 1
204  # unit_variance = 0
205  # observations2D = TFCDC.CDCObservations2D(rl_drift_circle, unit_variance)
206 
207  # for recoHit3D in track:
208  # observations2D.append(recoHit3D)
209 
210  # trajectory2D = track_fitter.fit(observations2D)
211  # trajectory2D.setLocalOrigin(TFCDC.Vector2D(0, 0))
212 
213  mc_particle = mc_track_lookup.getMCParticle(track)
214  pdg_code = mc_particle.getPDG()
215  t_vertex = mc_particle.getVertex()
216  t_mom = mc_particle.getMomentum()
217  charge = mc_particle.getCharge()
218  mass = mc_particle.getMass()
219  mc_energy = mc_particle.getEnergy()
220 
221  mc_vertex2D = TFCDC.Vector2D(t_vertex.XYvector())
222  mc_mom2D = TFCDC.Vector2D(t_mom.XYvector())
223  mc_trajectory2D = TFCDC.CDCTrajectory2D(mc_vertex2D, 0, mc_mom2D, charge)
224  mc_pt = mc_mom2D.norm()
225 
226  first_hit = self.mc_track_lookupmc_track_lookup.getFirstHit(track)
227  first_sim_hit = first_hit.getRelated("CDCSimHits")
228  if first_sim_hit is None:
229  return
230  # Make sure we start the track in the first layer to avoid some confusion
231  if first_sim_hit.getWireID().getICLayer() != 0:
232  return
233 
234  last_hit = self.mc_track_lookupmc_track_lookup.getLastHit(track)
235  last_sim_hit = last_hit.getRelated("CDCSimHits")
236  if last_sim_hit is None:
237  return
238  # Make sure we start the track in the last layer to avoid some confusion
239  # if last_sim_hit.getWireID().getICLayer() != 55: return
240  # last_sim_mom3D = TFCDC.Vector3D(last_sim_hit.getMomentum())
241 
242  first_sim_pos3D = TFCDC.Vector3D(first_sim_hit.getPosTrack())
243  first_sim_mom3D = TFCDC.Vector3D(first_sim_hit.getMomentum())
244  first_sim_tof = first_sim_hit.getFlightTime()
245  first_sim_energy = np.sqrt(first_sim_mom3D.norm() ** 2 + mass ** 2)
246  first_sim_pt = first_sim_mom3D.cylindricalR()
247  first_sim_pz = first_sim_mom3D.z()
248 
249  first_sim_mom2D = first_sim_mom3D.xy()
250 
251  # first_sim_mom2D.normalizeTo(last_sim_mom3D.cylindricalR())
252  first_sim_trajectory2D = TFCDC.CDCTrajectory2D(first_sim_pos3D.xy(), first_sim_tof, first_sim_mom2D, charge)
253 
254  # mc_trajectory3D = mc_track_lookup.getTrajectory3D(track)
255  # mc_trajectory2D = mc_trajectory3D.getTrajectory2D()
256 
257  for reco_hit3D in track:
258  sim_hit = self.mc_hit_lookupmc_hit_lookup.getSimHit(reco_hit3D.getWireHit().getHit())
259  if sim_hit is None:
260  continue
261 
262  sim_mom3D = TFCDC.Vector3D(sim_hit.getMomentum())
263  sim_energy = np.sqrt(sim_mom3D.norm() ** 2 + mass ** 2)
264  sim_pt = sim_mom3D.cylindricalR()
265  sim_pz = sim_mom3D.z()
266 
267  mc_eloss_truth = mc_energy - sim_energy
268  first_eloss_truth = first_sim_energy - sim_energy
269 
270  sim_pos3D = TFCDC.Vector3D(sim_hit.getPosTrack())
271  sim_pos2D = sim_pos3D.xy()
272 
273  layer_cid = reco_hit3D.getWire().getICLayer()
274  bz = self.bfieldbfield.getBFieldZ(sim_pos3D)
275  r = sim_pos3D.cylindricalR()
276 
277  # recoPos3D = reco_hit3D.getRecoPos3D()
278  # recoPos3D = TFCDC.Vector3D(sim_hit.getPosTrack())
279  # recoPos2D = recoPos3D.xy()
280  # refPos2D = reco_hit3D.getRefPos2D()
281  # l = reco_hit3D.getSignedRecoDriftLength()
282 
283  mc_s2D = mc_trajectory2D.calcArcLength2D(sim_pos2D)
284  first_s2D = first_sim_trajectory2D.calcArcLength2D(sim_pos2D)
285 
286  if mc_s2D < 0:
287  # mc_s2D += mc_trajectory2D.getArcLength2DPeriod()
288  continue
289 
290  mc_eloss_estimate = self.eloss_estimatoreloss_estimator.getEnergyLoss(mc_pt, pdg_code, mc_s2D)
291  first_eloss_estimate = self.eloss_estimatoreloss_estimator.getEnergyLoss(first_sim_pt, pdg_code, first_s2D)
292  first_ploss_factor = self.eloss_estimatoreloss_estimator.getMomentumLossFactor(first_sim_pt, pdg_code, first_s2D)
293 
294  sasha_eloss_estimate = getBetheEnergyLoss(first_sim_pt, pdg_code, first_s2D)
295  sasha_ploss_factor = DeltaR(first_s2D, pdg_code, first_sim_pt)
296 
297  # first_residual2D = first_trajectory2D.getDist2D(refPos2D) - l
298  first_residual2D = first_sim_trajectory2D.getDist2D(sim_pos2D)
299  first_disp2D = charge * first_residual2D
300 
301  first_loss_disp2D_estimate = abs(self.eloss_estimatoreloss_estimator.getLossDist2D(first_sim_pt, pdg_code, first_s2D))
302  first_delossed_disp2D = first_disp2D - first_loss_disp2D_estimate
303 
304  # mc_residual2D = mc_trajectory2D.getDist2D(refPos2D) - l
305  mc_residual2D = mc_trajectory2D.getDist2D(sim_pos2D)
306  mc_disp2D = charge * mc_residual2D
307 
308  mc_loss_disp2D_estimate = abs(self.eloss_estimatoreloss_estimator.getLossDist2D(mc_pt, pdg_code, mc_s2D))
309  mc_delossed_disp2D = mc_disp2D - mc_loss_disp2D_estimate
310 
311  # s2D = trajectory2D.calcArcLength2D(recoPos2D)
312  # if s2D < 0: s2D += trajectory2D.getArcLength2DPeriod()
313 
314  # residual2D = trajectory2D.getDist2D(recoPos2D)
315  # disp2D = charge * residual2D
316 
317  # # Original approach
318  # center2D = trajectory2D.getGlobalCircle().center()
319  # radius2D = recoPos2D - center2D;
320 
321  # eLoss = self.eloss_estimator.getEnergyLoss(mc_pt, pdg_code, mc_s2D)
322  # dx = self.eloss_estimator.getMomentumLossFactor(mc_pt, pdg_code, mc_s2D)
323  # deloss_radius2D = recoPos2D - center2D;
324  # deloss_radius2D.scale (1.0 / dx)
325  # loss_disp2D_estimate2 = (deloss_radius2D - radius2D).norm()
326 
327  # loss_disp2D_estimate3 = radius2D.norm() * (1.0 /dx - 1.0)
328  # loss_disp2D_estimate3 = radius2D.norm() * ((1.0 - dx) /dx)
329  # loss_disp2D_estimate3 = radius2D.norm() * (eLoss / (mc_pt - eLoss))
330 
331  if abs(mc_residual2D) > 6:
332  continue
333 
334  if abs(first_residual2D) > 6:
335  continue
336 
337  yield dict(
338  layer_cid=layer_cid,
339  r=r,
340  bz=bz,
341  # pt=trajectory2D.getAbsMom2D(),
342  pdg_code=abs(pdg_code),
343  mass=mass,
344  charge=charge,
345  mc_pt=mc_pt,
346  first_sim_pt=first_sim_pt,
347  sim_pt=sim_pt,
348 
349  diff_pt=first_sim_pt - sim_pt,
350  diff_pz=first_sim_pz - sim_pz,
351 
352  mc_eloss_truth=mc_eloss_truth,
353  mc_eloss_estimate=mc_eloss_estimate,
354 
355  first_eloss_truth=first_eloss_truth,
356 
357  first_eloss_estimate=first_eloss_estimate,
358  sasha_eloss_estimate=sasha_eloss_estimate,
359 
360  first_ploss_factor=first_ploss_factor,
361  sasha_ploss_factor=sasha_ploss_factor,
362 
363  # l=l,
364 
365  first_s2D=first_s2D,
366  first_residual2D=first_residual2D,
367  first_disp2D=first_disp2D,
368  first_loss_disp2D_estimate=first_loss_disp2D_estimate,
369  first_delossed_disp2D=first_delossed_disp2D,
370 
371  mc_s2D=mc_s2D,
372  mc_residual2D=mc_residual2D,
373  mc_disp2D=mc_disp2D,
374  mc_loss_disp2D_estimate=mc_loss_disp2D_estimate,
375  mc_delossed_disp2D=mc_delossed_disp2D,
376 
377  # s2D=s2D,
378  # disp2D=disp2D,
379  # residual2D=residual2D,
380 
381  # loss_disp2D_estimate2=loss_disp2D_estimate2,
382  # loss_disp2D_estimate3=loss_disp2D_estimate3,
383  )
384 
385 
387  save_tree = refiners.save_tree()
388 
389  save_histograms = refiners.save_histograms(
390  outlier_z_score=5.0,
391  allow_discrete=True,
392  )
393 
394 
395  save_histograms_stackby_charge = refiners.save_histograms(
396  select=[
397  # "mc_disp2D",
398  "first_disp2D",
399  "charge",
400  ],
401  outlier_z_score=5.0,
402  allow_discrete=True,
403  fit="gaus",
404  fit_z_score=1,
405  groupby="charge",
406  )
407 
408 
409  save_scatter = refiners.save_scatters(
410  x=['mc_s2D'],
411  y=[
412  # 'mc_disp2D',
413  'disp2D',
414  ],
415  groupby=[None, "charge"],
416  filter=lambda x: x == 211,
417  filter_on="pdg_code",
418  )
419 
420 
421  save_profiles = refiners.save_profiles(
422  x=['mc_s2D'],
423  y=[
424  # 'mc_disp2D',
425  'first_disp2D',
426  'disp2D',
427  ],
428  groupby=[None, "charge"],
429  )
430 
431 
432  save_bz_profiles = refiners.save_profiles(
433  x='r',
434  y='bz',
435  )
436 
437  # Loss displacement #
438  # ################# #
439 
440 
441  save_cid_histogram = refiners.save_histograms(
442  select=[
443  # 'mc_disp2D',
444  # 'mc_delossed_disp2D',
445  'first_disp2D',
446  'first_delossed_disp2D',
447  'bz',
448  # 'disp2D',
449  ],
450  outlier_z_score=5.0,
451  groupby=["layer_cid"],
452  # stackby="pdg_code",
453  )
454 
455 
456  save_cid_profiles = refiners.save_profiles(
457  x=["mc_pt"],
458  y=[
459  # 'mc_disp2D',
460  # 'mc_delossed_disp2D',
461  'first_disp2D',
462  'first_delossed_disp2D',
463  ],
464  outlier_z_score=5.0,
465  groupby=["layer_cid"],
466  stackby="pdg_code",
467  )
468 
469 
470  save_cid_scatters = refiners.save_scatters(
471  x=["mc_pt"],
472  y=[
473  # 'mc_disp2D',
474  # 'mc_delossed_disp2D',
475  'first_disp2D',
476  'first_delossed_disp2D',
477  ],
478  outlier_z_score=5.0,
479  groupby=["layer_cid"],
480  stackby="pdg_code",
481  )
482 
483  # Energy loss #
484  # ########### #
485 
486  save_energy_cid_histogram = refiners.save_histograms(
487  select=[
488  'pdg_code',
489  'first_eloss_estimate',
490  'first_eloss_truth',
491  ],
492  outlier_z_score=5.0,
493  groupby=["layer_cid"],
494  stackby="pdg_code",
495  folder_name='energy/{groupby_addition}',
496  )
497 
498 
499  save_energy_cid_profiles = refiners.save_profiles(
500  x=["mc_pt"],
501  y=[
502  'first_eloss_truth',
503  'first_eloss_estimate',
504  ],
505  outlier_z_score=5.0,
506  groupby=["layer_cid"],
507  stackby="pdg_code",
508  folder_name='energy/{groupby_addition}',
509  )
510 
511 
512  save_energy_cid_scatters = refiners.save_scatters(
513  x=["mc_pt"],
514  y=[
515  'first_eloss_truth',
516  'first_eloss_estimate',
517  ],
518  outlier_z_score=5.0,
519  groupby=["layer_cid"],
520  stackby="pdg_code",
521  folder_name='energy/{groupby_addition}',
522  )
523 
524 
525 def main():
526  run = ElossHarvestingRun()
527  run.configure_and_execute_from_commandline()
528 
529 
530 if __name__ == "__main__":
531  logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(levelname)s:%(message)s')
532  main()
mc_track_lookup
by default, there is no method to find matching MC tracks
Definition: record.py:159
mc_hit_lookup
Method to find matching MC hits.
Definition: record.py:172
track_fitter
Use the CDCReimannFitter with a constrained origin for track fitting.
Definition: record.py:164
def peel(self, track)
Definition: record.py:199
bfield
Method to interrogate the magnetic field values.
Definition: record.py:176
def pick(self, track)
Definition: record.py:182
eloss_estimator
Method to estimate dE/dx in the CDC.
Definition: record.py:174
def __init__(self, output_file_name)
Definition: record.py:153
def create_argument_parser(self, **kwds)
Definition: record.py:121
def harvesting_module(self, path=None)
Definition: record.py:114
def output_file_name(self)
Definition: record.py:110
output_file_name
Disable the writing of an output ROOT file.
Definition: run.py:20
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