Belle II Software  release-08-01-10
event_generation.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 
11 
12 import os
13 
14 import basf2
15 import simulation
16 import generators
17 import beamparameters
18 
19 from tracking.run import utilities
20 from tracking.run.minimal import MinimalRun
21 import tracking.adjustments as adjustments
22 
23 import argparse
24 import logging
25 
26 
27 def get_logger():
28  return logging.getLogger(__name__)
29 
30 
31 # Standard run for generating or reading event #
32 
33 
34 
36  """Read generated events or generate new events"""
37 
38 
39  description = "Simulate events using various generator and detector setups from command line."
40 
41  # Declarative section which can be redefined in a subclass
42 
43 
44  generator_module = None
45 
46  detector_setup = "Default"
47 
48  bkg_files = []
49 
50  components = None
51 
52  disable_deltas = False
53 
54  simulation_output = None
55 
56  def create_argument_parser(self, **kwds):
57  """Convert command-line arguments to basf2 argument list"""
58  argument_parser = super().create_argument_parser(**kwds)
59 
60  setup_argument_group = argument_parser.add_argument_group("Detector setup arguments")
61  setup_argument_group.add_argument(
62  '-d',
63  '--detector',
64  dest='detector_setup',
65  default=argparse.SUPPRESS,
66  metavar='DETECTOR_SETUP_NAME',
67  choices=utilities.NonstrictChoices(detector_setups_by_short_name.keys()),
68  help=('Name of the detector setup to be used')
69  )
70 
71  setup_argument_group.add_argument(
72  '-c',
73  '--component',
74  dest='components',
75  nargs='+',
76  default=argparse.SUPPRESS,
77  metavar='COMPONENTS',
78  action='store',
79  help=('Overrides the components of the detector setup')
80  )
81 
82  generator_argument_group = argument_parser.add_argument_group("Generator arguments")
83  generator_argument_group.add_argument(
84  '-g',
85  '--generator',
86  dest='generator_module',
87  default=argparse.SUPPRESS,
88  metavar='GENERATOR_NAME',
89  choices=utilities.NonstrictChoices(valid_generator_short_names),
90  help='Name module or short name of the generator to be used.',
91  )
92 
93  simulation_argument_group = argument_parser.add_argument_group("Simulation arguments")
94  simulation_argument_group.add_argument(
95  '-b',
96  '--bkg-file',
97  dest='bkg_files',
98  nargs='+',
99  default=self.bkg_filesbkg_files,
100  metavar='BACKGROUND_DIRECTORY',
101  help='Path to folder of files or to a file containing the background to be used. ' +
102  'Can be given multiple times.',
103  )
104 
105  simulation_argument_group.add_argument(
106  '--disable-deltas',
107  action='store_true',
108  help='Disable the generation of delta rays in the simulation'
109  )
110 
111  simulation_argument_group.add_argument(
112  '-so',
113  '--simulation-output',
114  nargs='?',
115  default=self.simulation_outputsimulation_output,
116  const=self.root_input_fileroot_input_fileroot_input_file,
117  dest='simulation_output',
118  help='Only generate and simulate the events and write them to the given output file. Skip rest of the path.'
119  )
120 
121  return argument_parser
122 
123  def configure(self, arguments):
124  """Configure for basf2 job; disable ROOT input if simulating events"""
125  super().configure(arguments)
126  if self.simulation_outputsimulation_output:
127  get_logger().info("Requested to simulation run. Deactivate input file")
128 
129  self.root_input_fileroot_input_fileroot_input_file = None
130 
131  def execute(self):
132  """Run the basf2 job"""
133  if not self.simulation_outputsimulation_output:
134  super().execute()
135  return
136 
137  # Run only simulation
138  path = ReadOrGenerateEventsRun.create_path(self)
139  self.runrun(path)
140 
141  def create_path(self):
142  """Create and configure the basf2 path"""
143  path = super().create_path()
144 
145  # Gearbox & Geometry must always be registered
146  path.add_module("Gearbox")
147  path.add_module("Geometry", useDB=True)
148  if self.detector_setupdetector_setup:
149  detector_setup = self.detector_setupdetector_setup
150  detector_setup_function = detector_setups_by_short_name[detector_setup]
151  components = detector_setup_function(path)
152 
153  if self.componentscomponents:
154  components = self.componentscomponents
155 
156  # Only generate events if no input file has been provided
157  if self.root_input_fileroot_input_fileroot_input_file is None:
158  # Check if generator means a decay file
159  if isinstance(self.generator_modulegenerator_module, str) and utilities.find_file(self.generator_modulegenerator_module):
160  dec_file_path = utilities.find_file(self.generator_modulegenerator_module)
161  add_evtgen_generator(path, dec_file_path)
162  else:
163  # All other possibilities
164  utilities.extend_path(path,
165  self.generator_modulegenerator_module,
166  generators_by_short_name,
167  allow_function_import=True)
168 
169  # Only simulate if generator is setup
170  if self.root_input_fileroot_input_fileroot_input_file is None:
171  bkg_file_paths = get_bkg_file_paths(self.bkg_filesbkg_files)
172 
174  components=components,
175  bkgfiles=bkg_file_paths)
176 
177  if self.disable_deltasdisable_deltas:
178  adjustments.disable_deltas(path)
179 
180  # Catch if no generator is added, no background should be simulated and events
181  # are not read from a file.
182  if not bkg_file_paths and self.generator_modulegenerator_module is None:
183  raise RuntimeError('Need at least one of root_input_file,'
184  ' generator_module or bkg_files specified.')
185  else:
186  if not os.path.exists(self.root_input_fileroot_input_fileroot_input_file):
187  raise RuntimeError("Could not find file " + str(self.root_input_fileroot_input_fileroot_input_file) + ". Generate it with -- -so?")
188 
189  # early write out if simulation output was requested
190  if self.simulation_outputsimulation_output:
191  path.add_module('RootOutput',
192  outputFileName=self.simulation_outputsimulation_output)
193 
194  return path
195 
196 
198  """Generate events using the EvtGen generator"""
199 
200  generator_module = "EvtGenInput"
201 
202 
203 # Default settings and shorthand names for generator with specific settings #
204 
205 
206 # PDG code of an electorn
207 electron_pdg_code = 11
208 
209 # PDG code of a muon
210 muon_pdg_code = 13
211 
212 # PDG code of a tau
213 tau_pdg_code = 15
214 
215 # PDG code of a pion
216 pion_pdg_code = 211
217 
218 # PDG code of a kaon
219 kaon_pdg_code = 321
220 
221 # PDG code of a protons
222 proton_pdg_code = 2212
223 
224 
225 def add_single_gun_generator(path):
226  """Add ParticleGun with a single muon"""
227  path.add_module("ParticleGun",
228  pdgCodes=[muon_pdg_code, -muon_pdg_code],
229  nTracks=1,
230  varyNTracks=False,
231  momentumGeneration='inversePt',
232  momentumParams=[0.6, 1.4],
233  thetaGeneration='uniform',
234  thetaParams=[17., 150.])
235 
236 
237 def add_transverse_gun_generator(path):
238  """Add ParticleGun to illuminate a region of the phase space with low efficiency"""
239  path.add_module("ParticleGun",
240  pdgCodes=[muon_pdg_code, -muon_pdg_code],
241  nTracks=1,
242  varyNTracks=False,
243  momentumGeneration='inversePt',
244  momentumParams=[0.275, 0.276],
245  thetaGeneration='uniform',
246  thetaParams=[89., 91.])
247 
248 
249 def add_simple_gun_generator(path):
250  """Add ParticleGun firing 10 muons at medium energy"""
251  path.add_module("ParticleGun",
252  pdgCodes=[muon_pdg_code, -muon_pdg_code],
253  nTracks=10,
254  varyNTracks=False,
255  momentumGeneration='inversePt',
256  momentumParams=[0.6, 1.4],
257  thetaGeneration='uniform')
258 
259 
260 def add_low_gun_generator(path):
261  """Add ParticleGun firing 10 muons at low energy"""
262  path.add_module("ParticleGun",
263  pdgCodes=[muon_pdg_code, -muon_pdg_code],
264  nTracks=10,
265  varyNTracks=False,
266  momentumGeneration='inversePt',
267  momentumParams=[0.4, 0.8],
268  thetaGeneration='uniform')
269 
270 
271 def add_gun_generator(path):
272  """Add ParticleGun firing 10 muons with wide energy range"""
273  path.add_module("ParticleGun",
274  pdgCodes=[muon_pdg_code, -muon_pdg_code],
275  nTracks=10,
276  varyNTracks=False,
277  momentumGeneration='inversePt',
278  thetaGeneration='uniform',
279  thetaParams=[17., 150.])
280 
281 
282 def add_eloss_gun_generator(path):
283  """Add ParticleGun particle gun for energy loss estimations"""
284  path.add_module("ParticleGun",
285  pdgCodes=[
286  # muon_pdg_code,
287  # -muon_pdg_code,
288  # electron_pdg_code,
289  # -electron_pdg_code,
290  pion_pdg_code,
291  -pion_pdg_code,
292  # kaon_pdg_code,
293  # -kaon_pdg_code,
294  # proton_pdg_code,
295  # -proton_pdg_code,
296  ],
297  momentumParams=[0.3, 2],
298  nTracks=10,
299  varyNTracks=False,
300  thetaGeneration='uniform',
301  # thetaParams=[17., 150.],
302  thetaParams=[89., 91],
303  )
304 
305 
306 def add_forward_gun_generator(path):
307  """Add ParticleGun with one muon in rather forward direction"""
308  path.add_module("ParticleGun",
309  pdgCodes=[muon_pdg_code, -muon_pdg_code],
310  nTracks=1,
311  varyNTracks=False,
312  momentumGeneration='inversePt',
313  thetaGeneration='uniform',
314  thetaParams=[30., 31.])
315 
316 
317 def add_evtgen_generator(path, dec_file_path=None):
318  """Add Y4S generator"""
320  if dec_file_path:
321  path.add_module("EvtGenInput", userDECFile=dec_file_path)
322  else:
323  path.add_module("EvtGenInput")
324 
325 
326 def add_cosmics_generator(path):
327  """Add simple cosmics generator"""
329 
330 
331 def add_sector_tb_generator(path, sector=1):
332  phiBounds = (240 + 60.0 * sector % 360.0, 300 + 60.0 * sector % 360.0)
333  path.add_module("ParticleGun",
334  pdgCodes=[muon_pdg_code, -muon_pdg_code],
335  nTracks=1,
336  varyNTracks=False,
337  momentumGeneration='uniform',
338  momentumParams=[2.0, 4.0],
339  phiGeneration='uniform',
340  phiParams=phiBounds,
341  thetaGeneration='uniform',
342  thetaParams=[70., 110.])
343 
344 
345 def add_cosmics_tb_generator(path):
346  """Add simple cosmics generator resembling the test beam setup"""
347  path.add_module("Cosmics",
348  ipRequirement=1,
349  ipdr=5,
350  ipdz=15,
351  )
352 
353  # Use point trigger
354  tof_mode = 1
355  # Do not add time of propagation in the scintilator
356  top_mode = False
357 
358  cosmics_selector = path.add_module('CDCCosmicSelector',
359  xOfCounter=0.0,
360  yOfCounter=0.0,
361  zOfCounter=0.0,
362  TOF=tof_mode,
363  TOP=top_mode,
364  cryGenerator=False,
365  )
366 
367  cosmics_selector.if_false(basf2.create_path())
368 
369 
370 def add_cry_tb_generator(path):
371  """Add cry generator resembling the test beam setup"""
372  generators.add_cosmics_generator(path, accept_box=[0.7, 0.3, 0.3],
373  keep_box=[0.7, 0.3, 0.3],
374  pre_general_run_setup="normal")
375 
376 
377 def add_no_generator(path):
378  """Add no generator for e.g. background only studies"""
379  # Nothing to do here since the background files are included in the add_simulation
380 
381 
382 # Generator module names hashed by shorthand menomics. Includes
383 # None as a special value for background only simulation
384 generators_by_short_name = {
385  'single_gun': add_single_gun_generator,
386  'transverse_gun': add_transverse_gun_generator,
387  'simple_gun': add_simple_gun_generator,
388  'low_gun': add_low_gun_generator,
389  'forward_gun': add_forward_gun_generator,
390  'gun': add_gun_generator,
391  'eloss_gun': add_eloss_gun_generator,
392  'generic': add_evtgen_generator,
393  "EvtGenInput": add_evtgen_generator, # <- requires beam parameters
394  'cosmics': add_cosmics_generator,
395  'cosmics_tb': add_cosmics_tb_generator,
396  'cry_tb': add_cry_tb_generator,
397  'sector_tb': add_sector_tb_generator,
398  'bkg': add_no_generator,
399  'none': add_no_generator,
400 }
401 
402 # Names of module names and short names of the generators usable in this script.
403 valid_generator_short_names = list(generators_by_short_name.keys())
404 
405 
406 # Memorandum of geometry setups #
407 # ############################# #
408 def setup_default_detector(path):
409  pass
410 
411 
412 def setup_tracking_detector(path):
413  components = ["BeamPipe", "PXD", "SVD", "CDC", "MagneticField"]
414  override = [
415  ("/DetectorComponent[@name='MagneticField']//Component[@type='3d'][2]/ExcludeRadiusMax",
416  "4.20", "m", ) # Remove the second compontent which is the magnetic field outside the tracking volumn.
417  ]
418 
419  adjustments.adjust_module(path, "Gearbox", override=override)
420  adjustments.adjust_module(path, "Geometry", components=components)
421  return components
422 
423 
424 def setup_tracking_detector_constant_b(path):
425  components = ["BeamPipe", "PXD", "SVD", "CDC", "MagneticFieldConstant4LimitedRCDC"]
426  adjustments.adjust_module(path, "Geometry", components=components)
427  return components
428 
429 
430 def setup_cdc_cr_test(path):
431  components = ["CDC"]
432  override = [
433  # Reset the top volume to accomodate cosmics that can hit all parts of the detector
434  ("/Global/length", "8.", "m"),
435  ("/Global/width", "8.", "m"),
436  ("/Global/height", "1.5", "m"),
437 
438  # Adjustments of the CDC setup
439  ("/DetectorComponent[@name='CDC']//t0FileName", "t0.dat", ""),
440  ("/DetectorComponent[@name='CDC']//xtFileName", "xt_noB_v1.dat", ""),
441  ("/DetectorComponent[@name='CDC']//GlobalPhiRotation", "1.875", "deg"),
442  # ("/DetectorComponent[@name='CDC']//bwFileName", "badwire_CDCTOP.dat", ""),
443  ]
444 
445  adjustments.adjust_module(path, "Gearbox", override=override)
446  adjustments.adjust_module(path, "Geometry", components=components)
447  return components
448 
449 
450 def setup_cdc_top_test(path):
451  components = ["CDC"]
452  override = [
453  # Reset the top volume: must be larger than the generated surface and higher than the detector
454  # It is the users responsibility to ensure a full angular coverage
455  ("/Global/length", "8.", "m"),
456  ("/Global/width", "8.", "m"),
457  ("/Global/height", "1.5", "m"),
458 
459  # Adjustments of the CDC setup
460  ("/DetectorComponent[@name='CDC']//t0FileName", "t0.dat", ""),
461  ("/DetectorComponent[@name='CDC']//xtFileName", "xt_noB_v1.dat", ""),
462  # ("/DetectorComponent[@name='CDC']//bwFileName", "badwire_CDCTOP.dat", ""),
463  ("/DetectorComponent[@name='CDC']//GlobalPhiRotation", "1.875", "deg"),
464  ("/DetectorComponent[@name='MagneticField']//Component/Z", "0", ""),
465  ]
466 
467  adjustments.adjust_module(path, "Gearbox",
468  override=override,
469  fileName="geometry/CDCcosmicTests.xml" # <- does something mysterious to the reconstruction...
470  )
471 
472  adjustments.adjust_module(path, "Geometry", components=components)
473  return components
474 
475 
476 detector_setups_by_short_name = {
477  "Default": setup_default_detector,
478  'TrackingDetector': setup_tracking_detector,
479  'TrackingDetectorConstB': setup_tracking_detector_constant_b,
480  'CDCCRTest': setup_cdc_cr_test,
481  'CDCTOPTest': setup_cdc_top_test,
482 }
483 
484 
485 # Heuristic to find background files #
486 # ################################## #
487 
488 def is_bkg_file(bkg_file_path):
489  """Test if a file path points to a file containing background mixins.
490 
491  Returns
492  -------
493  bool
494 
495  Note
496  ----
497  Simple test only checks if file exists and ends with ".root"
498  """
499  return os.path.isfile(bkg_file_path) and bkg_file_path.endswith('.root')
500 
501 
502 def get_bkg_file_paths(bkg_dir_or_file_paths):
503  """Unpacks the content of a single or a list of directories and/or files filtering for
504  files containing background mixins.
505 
506  Parameters
507  ----------
508  bkg_dir_or_file_paths : string or iterable of strings
509  Single file or single directory in which background files are located or
510  a list of files and/or directories.
511 
512  Returns
513  -------
514  list(string)
515  A list of paths to individual background files.
516  """
517 
518  if isinstance(bkg_dir_or_file_paths, str):
519  bkg_dir_or_file_path = bkg_dir_or_file_paths
520  bkg_dir_or_file_paths = [bkg_dir_or_file_path]
521 
522  result = []
523  for bkg_dir_or_file_path in bkg_dir_or_file_paths:
524  if is_bkg_file(bkg_dir_or_file_path):
525  bkg_file_path = bkg_dir_or_file_path
526  result.append(bkg_file_path)
527  elif os.path.isdir(bkg_dir_or_file_path):
528 
529  bkg_dir_path = bkg_dir_or_file_path
530  bkg_dir_contents = os.listdir(bkg_dir_path)
531  for dir_or_file_name in bkg_dir_contents:
532  dir_or_file_path = os.path.join(bkg_dir_path, dir_or_file_name)
533  if is_bkg_file(dir_or_file_path):
534  bkg_file_path = dir_or_file_path
535  result.append(bkg_file_path)
536 
537  return result
def add_beamparameters(path, name, E_cms=None, **argk)
bool disable_deltas
By default, do not disable delta-ray generation.
simulation_output
By default, do no store the simulation output.
string detector_setup
By default, use the default detector setup.
root_input_file
generating events, so there is no ROOT input file
generator_module
By default, do not generate events.
list bkg_files
By default, no background overlay.
def run(self, path)
Definition: minimal.py:59
root_input_file
By default, there is no input ROOT TFile.
Definition: minimal.py:114
def add_cosmics_generator(path, components=None, global_box_size=None, accept_box=None, keep_box=None, geometry_xml_file='geometry/Beast2_phase2.xml', cosmics_data_dir='data/generators/modules/cryinput/', setup_file='generators/scripts/cry.setup', data_taking_period='gcr2017', top_in_counter=False)
Definition: generators.py:828
def add_simulation(path, components=None, bkgfiles=None, bkgOverlay=True, forceSetPXDDataReduction=False, usePXDDataReduction=True, cleanupPXDDataReduction=True, generate_2nd_cdc_hits=False, simulateT0jitter=True, isCosmics=False, FilterEvents=False, usePXDGatedMode=False, skipExperimentCheckForBG=False, save_slow_pions_in_mc=False)
Definition: simulation.py:121