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