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