Belle II Software  release-05-01-25
flight_time_mc_compare.py
1 import basf2
2 from ROOT import Belle2
3 from ROOT import gSystem
4 from generators import add_cosmics_generator
5 
6 gSystem.Load('libcdc')
7 gSystem.Load('libtracking')
8 gSystem.Load('libtracking_trackFindingCDC')
9 
10 import tracking
11 from tracking.harvest.harvesting import harvest
12 from tracking.harvest.refiners import (
13  save_tree,
14  save_pull_analysis,
15  save_profiles,
16  save_scatters,
17 )
18 
20 
21 
22 def main():
23  path = basf2.create_path()
24 
25  # Create Event information
26  path.add_module('EventInfoSetter',
27  evtNumList=[50000],
28  runList=[1],
29  )
30 
31  # Show progress of processing
32  path.add_module('Progress')
33 
34  # Load gearbox parameters
35  gearbox_module = path.add_module('Gearbox')
36 
37  # Create geometry
38  geometry_module = path.add_module('Geometry')
39 
40  # Adjust gemetry for the test beam #
41  # ################################ #
42  cdc_top_geometry = False
43  if cdc_top_geometry:
44  gearbox_module.param(dict(fileName="geometry/CDCcosmicTests.xml")) # <- does something mysterious to the reconstruction...
45  # The peculiar behaviour seems to originate from the low magnetic field setup close to the earth magentic field.
46  # In the current state of the reconstruction it seems to be better to set the magnetic field to zero
47  # Override of the magnetic field is made below as a temporary work around until we discussed the topic with
48  # the TOP group how composed the geometry/CDCcosmicTests.xml.
49  gearbox_module.param(dict(overrideMultiple=[
50  # Explicitly override magnetic field to zero instead of earth magnetic field as defined in CDC TOP geometry
51  ("/DetectorComponent[@name='MagneticField']//Component/Z", "0", ""),
52  ]
53  ))
54 
55  else:
56  # Use the regular geometry but only the cdc, no magnetic field.
57  geometry_module.param(dict(components=["CDC"],))
58 
59  gearbox_module.param(dict(
60  override=[
61  # Reset the top volume: must be larger than the generated surface and higher than the detector
62  # It is the users responsibility to ensure a full angular coverage
63  ("/Global/length", "8.", "m"),
64  ("/Global/width", "8.", "m"),
65  ("/Global/height", "1.5", "m"),
66 
67  # Adjustments of the CDC setup
68  ("/DetectorComponent[@name='CDC']//t0FileName", "t0.dat", ""),
69  ("/DetectorComponent[@name='CDC']//xtFileName", "xt_noB_v1.dat", ""),
70  # ("/DetectorComponent[@name='CDC']//bwFileName", "badwire_CDCTOP.dat", ""),
71  ("/DetectorComponent[@name='CDC']//GlobalPhiRotation", "1.875", "deg"),
72  ]
73  ))
74 
75  # Generator setup #
76  # ############### #
77  # generator_module = "cry"
78  generator_module = "cosmics"
79  # generator_module = "particle_gun"
80 
81  if generator_module == "cry":
82  add_cosmics_generator(path, accept_box=[0.5, 0.5, 0.5], keep_box=[0.5, 0.5, 0.5])
83 
84  elif generator_module == "cosmics":
85  path.add_module("Cosmics",
86  # Require particles close to the counter
87  ipRequirement=1,
88  ipdr=5,
89  ipdz=15,
90  )
91 
92  elif generator_module == "particle_gun":
93  # Generate muons starting in the IP
94  # Yields outgoing arm only
95 
96  # PDG code of a muon
97  muon_pdg_code = 13
98  sector = 0
99  phiBounds = [240 + 60.0 * sector % 360.0, 300 + 60.0 * sector % 360.0]
100 
101  path.add_module("ParticleGun",
102  pdgCodes=[muon_pdg_code, -muon_pdg_code],
103  nTracks=1,
104  varyNTracks=False,
105  momentumGeneration='uniform',
106  momentumParams=[2.0, 4.0],
107  phiGeneration='uniform',
108  phiParams=phiBounds,
109  thetaGeneration='uniform',
110  thetaParams=[70., 110.])
111 
112  else:
113  raise ValueError("Unknown generator module key " + generator_module)
114 
115  # Flight time versus trigger setup #
116  # ################################ #
117 
118  # Two alternative tof setups:
119  # * Relative to plane sets the Monte Carlo time of flight
120  # such that zero is on first intersection with the trigger counter plane
121  # * Relative to point sets the Monte Carlo time of flight
122  # such that zero is at the perigee relative to central point of the trigger counter plane
123  use_tof_relative_to_trigger_plane = True
124  use_tof_relative_to_trigger_point = not use_tof_relative_to_trigger_plane
125  if use_tof_relative_to_trigger_plane:
126  tof_mode = 1
127  elif use_tof_relative_to_trigger_point:
128  tof_mode = 2
129 
130  # Also counter the adjustments the CRY generator makes to the production time in case it is used.
131  use_cry_generator = generator_module == "cry"
132 
133  path.add_module('CDCCosmicSelector',
134  lOfCounter=30.,
135  wOfCounter=10.,
136  xOfCounter=0.3744,
137  yOfCounter=0.,
138  zOfCounter=-1.284,
139 
140  # xOfCounter = -0.6,
141  # yOfCounter = 5,
142  # zOfCounter = 16,
143 
144  TOF=tof_mode,
145  cryGenerator=use_cry_generator,
146  )
147 
148  # Run simulation
149  path.add_module('FullSim',
150  ProductionCut=1000000.
151  )
152 
153  path.add_module('CDCDigitizer',
154  # **{'2015AprRun' : True} # <- only hits in superlayer4 are digitized
155  # TrigTimeJitter=8., # <- simulate trigger timing jitter
156  AddTimeOfFlight=True,
157  AddInWirePropagationDelay=True,
158  CorrectForWireSag=True,
159  )
160 
161  tracking.add_cdc_cr_track_finding(path,
162  trigger_point=(0.3744, 0, -1.284),
163  )
164 
165  # path.add_module("TFCDC_TrackFinderCosmics")
166  # tracking.add_cdc_track_finding(path)
167  # path.add_module("TrackFinderMCTruthRecoTracks")
168 
169  if use_tof_relative_to_trigger_plane:
170  path.add_module("PlaneTriggerTrackTimeEstimator",
171  triggerPlanePosition=[0.3744, 0, -1.284],
172  triggerPlaneDirection=[0, 1, 0],
173  useFittedInformation=False)
174 
175  path.add_module("SetupGenfitExtrapolation")
176  path.add_module("DAFRecoFitter")
177 
178  # Create a harvesting module for the time of flight
179  @save_pull_analysis(part_name="time", aux_names=["wire_idx", ], groupby=["layer_sidx"], outlier_z_score=3,)
180  @save_pull_analysis(part_name="time", aux_names=["layer_sidx", "time_truth", ], outlier_z_score=3,)
181  @save_pull_analysis(part_name="drift_length", aux_names=["layer_sidx", ], outlier_z_score=3,)
182  @save_tree(name="tree")
183  @harvest(foreach="RecoTracks", show_results=True)
184  def harvest_cdc_cr(reco_track):
185  fit_status = reco_track.getTrackFitStatus()
186  if not fit_status or not fit_status.isFitConverged():
187  return
188 
189  track_points = reco_track.getHitPointsWithMeasurement()
190  for track_point in track_points:
191  kalman_fitter_info = track_point.getKalmanFitterInfo()
192  try:
193  measurement_on_plane = kalman_fitter_info.getFittedState()
194  except:
195  # Sometimes a throw happens from Genfit, skip track point
196  continue
197 
198  abs_measurement = track_point.getRawMeasurement(0)
199  daf_weight = kalman_fitter_info.getWeights().at(0)
200  cdc_hit = abs_measurement.getCDCHit()
201  wire_id = Belle2.WireID(cdc_hit.getID())
202  layer_cidx = wire_id.getICLayer()
203  wire_idx = wire_id.getIWire()
204  cdc_sim_hit = cdc_hit.getRelated("CDCSimHits")
205 
206  fit_data = get_fit_data(measurement_on_plane)
207 
208  flight_time = fit_data["flight_time_estimate"]
209  drift_time = DriftTimeUtil.getDriftTime(fit_data["drift_length_estimate"],
210  layer_cidx,
211  fit_data["rl"],
212  fit_data["alpha_estimate"],
213  fit_data["theta_estimate"],
214  )
215 
216  prop_time = DriftTimeUtil.getPropTime(wire_id, fit_data["z_estimate"])
217  time_walk = DriftTimeUtil.getTimeWalk(wire_id, cdc_hit.getADCCount())
218 
219  reco_time = flight_time + drift_time + prop_time + time_walk
220  smear = True
221  measured_time = DriftTimeUtil.getMeasuredTime(wire_id, cdc_hit.getTDCCount(), smear)
222 
223  # MC Information
224  flight_time_truth = cdc_sim_hit.getFlightTime()
225  drift_length_truth = cdc_sim_hit.getDriftLength()
226  incoming_arm = -1 if flight_time_truth < 0 else 1
227 
228  if drift_time > 1000:
229  # There seems to be a bug in the CDCGeometryPar::getDriftTime function
230  continue
231  if drift_length_truth < 0.1 or drift_length_truth > 0.5:
232  continue
233  if daf_weight < 0.5:
234  continue
235 
236  yield dict(drift_length_estimate=fit_data["drift_length_estimate"],
237  drift_length_variance=fit_data["drift_length_variance"],
238  drift_length_truth=drift_length_truth,
239  time_truth=reco_time,
240  time_estimate=measured_time,
241  time_delta=reco_time - measured_time,
242 
243  flight_time_estimate=flight_time,
244  drift_time_estimate=drift_time,
245  prop_time_estimate=prop_time,
246  time_walk_estimate=time_walk,
247 
248  rl_estimate=fit_data["rl"],
249  alpha_estimate=fit_data["alpha_estimate"],
250  theta_estimate=fit_data["theta_estimate"],
251  daf_weight=daf_weight,
252  flight_time_truth=flight_time_truth,
253  incoming_arm=incoming_arm,
254  layer_sidx=incoming_arm * layer_cidx,
255  wire_idx=wire_idx,
256  tdc=cdc_hit.getTDCCount(),
257  )
258 
259  path.add_module(harvest_cdc_cr)
260 
261  # Create a harvesting module for the time of flight mc comparision
262  @save_pull_analysis(part_name="flight_time", groupby=[None, "incoming_arm"], outlier_z_score=5,)
263  @save_tree(name="tree")
264  @harvest(foreach="RecoTracks", show_results=True)
265  def harvest_flight_times(reco_track):
266  track_points = reco_track.getHitPointsWithMeasurement()
267  for track_point in track_points:
268  kalman_fitter_info = track_point.getKalmanFitterInfo()
269  try:
270  measurement_on_plane = kalman_fitter_info.getFittedState()
271  except:
272  # Sometimes a throw happens from Genfit, skip track point
273  continue
274  abs_measurement = track_point.getRawMeasurement(0)
275  cdc_hit = abs_measurement.getCDCHit()
276  cdc_sim_hit = cdc_hit.getRelated("CDCSimHits")
277 
278  flight_time_estimate = measurement_on_plane.getTime()
279  flight_time_truth = cdc_sim_hit.getFlightTime()
280  incoming_arm = flight_time_truth < 0
281 
282  yield dict(flight_time_estimate=flight_time_estimate,
283  flight_time_truth=flight_time_truth,
284  incoming_arm=incoming_arm)
285 
286  # path.add_module(harvest_flight_times)
287 
288  # generate events
289  basf2.print_path(path)
290  basf2.process(path)
291 
292  # show call statistics
293  print(basf2.statistics)
294 
295 
296 # Helper functions #
297 # ################ #
298 def get_fit_data(measurement_on_plane):
299  flight_time = measurement_on_plane.getTime()
300  pos = measurement_on_plane.getPos()
301  mom = measurement_on_plane.getMom()
302  wirepos = measurement_on_plane.getPlane().getO()
303  alpha = wirepos.DeltaPhi(mom)
304  theta = mom.Theta()
305 
306  state_on_plane = measurement_on_plane.getState() # TVectorD
307  cov_on_plane = measurement_on_plane.getCov() # TMatrixDSym
308 
309  # The state at index 3 is equivalent to the drift length
310  drift_length = abs(state_on_plane(3))
311  drift_length_var = abs(cov_on_plane(3, 3))
312  rl_sign = 1 if state_on_plane(3) > 0 else -1
313  rl = 1 if state_on_plane(3) > 0 else 0
314 
315  return dict(flight_time_estimate=flight_time,
316  x_estimate=pos.X(),
317  y_estimate=pos.Y(),
318  z_estimate=pos.Z(),
319  px_estimate=mom.X(),
320  py_estimate=mom.Y(),
321  pz_estimate=mom.Z(),
322  wire_x_estimate=wirepos.X(),
323  wire_y_estimate=wirepos.Y(),
324  wire_z_estimate=wirepos.Z(),
325  alpha_estimate=alpha,
326  theta_estimate=theta,
327  drift_length_estimate=drift_length,
328  drift_length_variance=drift_length_var,
329  rl=rl,
330  rl_sign=rl_sign,
331  )
332 
333 
334 if __name__ == "__main__":
335  main()
Belle2::WireID
Class to identify a wire inside the CDC.
Definition: WireID.h:44
tracking.harvest.refiners
Definition: refiners.py:1
tracking.harvest.harvesting
Definition: harvesting.py:1
basf2.process
def process(path, max_event=0)
Definition: __init__.py:25
main
int main(int argc, char **argv)
Run all tests.
Definition: test_main.cc:77
Belle2::TrackFindingCDC::DriftTimeUtil
Structure to expose some drift time and length functions from the CDCGeometryPar to Python.
Definition: DriftTimeUtil.h:32