Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
simulation.py
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file simulation.py
1 from typing import Optional, Union, Any, List
2 from pathlib import Path
3 from collections import namedtuple
4 from collections.abc import Iterable
5 
6 import acts
7 from acts.examples import (
8  RandomNumbers,
9  EventGenerator,
10  FixedMultiplicityGenerator,
11  CsvParticleWriter,
12  ParticlesPrinter,
13  RootParticleWriter,
14 )
15 
16 # Defaults (given as `None` here) use class defaults defined in
17 # Examples/Algorithms/Generators/ActsExamples/Generators/ParametricParticleGenerator.hpp
18 MomentumConfig = namedtuple(
19  "MomentumConfig",
20  ["min", "max", "transverse"],
21  defaults=[None, None, None],
22 )
23 EtaConfig = namedtuple(
24  "EtaConfig", ["min", "max", "uniform"], defaults=[None, None, None]
25 )
26 PhiConfig = namedtuple("PhiConfig", ["min", "max"], defaults=[None, None])
27 ParticleConfig = namedtuple(
28  "ParticleConfig",
29  ["num", "pdg", "randomizeCharge", "charge", "mass"],
30  defaults=[None, None, None, None, None],
31 )
32 ParticleSelectorConfig = namedtuple(
33  "ParticleSelectorConfig",
34  [
35  "rho", # (min,max)
36  "absZ", # (min,max)
37  "time", # (min,max)
38  "phi", # (min,max)
39  "eta", # (min,max)
40  "absEta", # (min,max)
41  "pt", # (min,max)
42  "m", # (min,max)
43  "measurements", # (min,max)
44  "removeCharged", # bool
45  "removeNeutral", # bool
46  "removeSecondaries", # bool
47  ],
48  defaults=[(None, None)] * 9 + [None] * 3,
49 )
50 
51 
53  momentumConfig=MomentumConfig,
54  etaConfig=EtaConfig,
55  phiConfig=PhiConfig,
56  particleConfig=ParticleConfig,
57 )
58 def addParticleGun(
60  outputDirCsv: Optional[Union[Path, str]] = None,
61  outputDirRoot: Optional[Union[Path, str]] = None,
62  momentumConfig: MomentumConfig = MomentumConfig(),
63  etaConfig: EtaConfig = EtaConfig(),
64  phiConfig: PhiConfig = PhiConfig(),
65  particleConfig: ParticleConfig = ParticleConfig(),
66  multiplicity: int = 1,
67  vtxGen: Optional[EventGenerator.VertexGenerator] = None,
68  printParticles: bool = False,
69  rnd: Optional[RandomNumbers] = None,
70  logLevel: Optional[acts.logging.Level] = None,
71 ) -> None:
72  """This function steers the particle generation using the particle gun
73 
74  Parameters
75  ----------
76  s: Sequencer
77  the sequencer module to which we add the particle gun steps (returned from addParticleGun)
78  outputDirCsv : Path|str, path, None
79  the output folder for the Csv output, None triggers no output
80  outputDirRoot : Path|str, path, None
81  the output folder for the Root output, None triggers no output
82  momentumConfig : MomentumConfig(min, max, transverse)
83  momentum configuration: minimum momentum, maximum momentum, transverse
84  etaConfig : EtaConfig(min, max, uniform)
85  pseudorapidity configuration: eta min, eta max, uniform
86  phiConfig : PhiConfig(min, max)
87  azimuthal angle configuration: phi min, phi max
88  particleConfig : ParticleConfig(num, pdg, randomizeCharge, charge, mass)
89  particle configuration: number of particles, particle type, charge flip
90  multiplicity : int, 1
91  number of generated vertices
92  vtxGen : VertexGenerator, None
93  vertex generator module
94  printParticles : bool, False
95  print generated particles
96  rnd : RandomNumbers, None
97  random number generator
98  """
99 
100  customLogLevel = acts.examples.defaultLogging(s, logLevel)
101 
102  # Preliminaries
103  rnd = rnd or RandomNumbers(seed=228)
104 
105  # Input
106  evGen = EventGenerator(
107  level=customLogLevel(),
108  generators=[
109  EventGenerator.Generator(
110  multiplicity=FixedMultiplicityGenerator(n=multiplicity),
111  vertex=vtxGen
112  or acts.examples.GaussianVertexGenerator(
113  mean=acts.Vector4(0, 0, 0, 0),
114  stddev=acts.Vector4(0, 0, 0, 0),
115  ),
116  particles=acts.examples.ParametricParticleGenerator(
118  p=(momentumConfig.min, momentumConfig.max),
119  pTransverse=momentumConfig.transverse,
120  eta=(etaConfig.min, etaConfig.max),
121  phi=(phiConfig.min, phiConfig.max),
122  etaUniform=etaConfig.uniform,
123  numParticles=particleConfig.num,
124  pdg=particleConfig.pdg,
125  randomizeCharge=particleConfig.randomizeCharge,
126  charge=particleConfig.charge,
127  mass=particleConfig.mass,
128  )
129  ),
130  )
131  ],
132  outputParticles="particles_input",
133  randomNumbers=rnd,
134  )
135 
136  s.addReader(evGen)
137 
138  s.addWhiteboardAlias("particles", evGen.config.outputParticles)
139 
140  if printParticles:
141  s.addAlgorithm(
142  ParticlesPrinter(
143  level=customLogLevel(),
144  inputParticles=evGen.config.outputParticles,
145  )
146  )
147 
148  if outputDirCsv is not None:
149  outputDirCsv = Path(outputDirCsv)
150  if not outputDirCsv.exists():
151  outputDirCsv.mkdir()
152 
153  s.addWriter(
154  CsvParticleWriter(
155  level=customLogLevel(),
156  inputParticles=evGen.config.outputParticles,
157  outputDir=str(outputDirCsv),
158  outputStem="particles",
159  )
160  )
161 
162  if outputDirRoot is not None:
163  outputDirRoot = Path(outputDirRoot)
164  if not outputDirRoot.exists():
165  outputDirRoot.mkdir()
166 
167  s.addWriter(
168  RootParticleWriter(
169  level=customLogLevel(),
170  inputParticles=evGen.config.outputParticles,
171  filePath=str(outputDirRoot / "particles.root"),
172  )
173  )
174 
175  return s
176 
177 
178 def addPythia8(
180  rnd: Optional[acts.examples.RandomNumbers] = None,
181  nhard: int = 1,
182  npileup: int = 200,
183  beam: Optional[
184  Union[acts.PdgParticle, Iterable]
185  ] = None, # default: acts.PdgParticle.eProton
186  cmsEnergy: Optional[float] = None, # default: 14 * acts.UnitConstants.TeV
187  hardProcess: Optional[Iterable] = None, # default: ["HardQCD:all = on"]
188  pileupProcess: Iterable = ["SoftQCD:all = on"],
189  vtxGen: Optional[acts.examples.EventGenerator.VertexGenerator] = None,
190  outputDirCsv: Optional[Union[Path, str]] = None,
191  outputDirRoot: Optional[Union[Path, str]] = None,
192  printParticles: bool = False,
193  printPythiaEventListing: Optional[Union[None, str]] = None,
194  logLevel: Optional[acts.logging.Level] = None,
195 ) -> None:
196  """This function steers the particle generation using Pythia8
197 
198  Parameters
199  ----------
200  s: Sequencer
201  the sequencer module to which we add the particle gun steps (returned from addParticleGun)
202  rnd : RandomNumbers, None
203  random number generator
204  nhard, npileup : int, 1, 200
205  Number of hard-scatter and pileup vertices
206  beam : PdgParticle|[PdgParticle,PdgParticle], eProton
207  beam particle(s)
208  cmsEnergy : float, 14 TeV
209  CMS energy
210  hardProcess, pileupProcess : [str], ["HardQCD:all = on"], ["SoftQCD:all = on"]
211  hard and pileup processes
212  vtxGen : VertexGenerator, None
213  vertex generator module
214  outputDirCsv : Path|str, path, None
215  the output folder for the Csv output, None triggers no output
216  outputDirRoot : Path|str, path, None
217  the output folder for the Root output, None triggers no output
218  printParticles : bool, False
219  print generated particles
220  printPythiaEventListing
221  None or "short" or "long"
222  """
223 
224  customLogLevel = acts.examples.defaultLogging(s, logLevel)
225 
226  # Preliminaries
227  rnd = rnd or acts.examples.RandomNumbers()
228  vtxGen = vtxGen or acts.examples.GaussianVertexGenerator(
229  stddev=acts.Vector4(0, 0, 0, 0), mean=acts.Vector4(0, 0, 0, 0)
230  )
231  if not isinstance(beam, Iterable):
232  beam = (beam, beam)
233 
234  if printPythiaEventListing is None:
235  printShortEventListing = False
236  printLongEventListing = False
237  elif printPythiaEventListing == "short":
238  printShortEventListing = True
239  printLongEventListing = False
240  elif printPythiaEventListing == "long":
241  printShortEventListing = False
242  printLongEventListing = True
243  else:
244  raise RuntimeError("Invalid pythia config")
245 
246  generators = []
247  if nhard is not None and nhard > 0:
248  generators.append(
249  acts.examples.EventGenerator.Generator(
250  multiplicity=acts.examples.FixedMultiplicityGenerator(n=nhard),
251  vertex=vtxGen,
252  particles=acts.examples.pythia8.Pythia8Generator(
253  level=customLogLevel(),
255  pdgBeam0=beam[0],
256  pdgBeam1=beam[1],
257  cmsEnergy=cmsEnergy,
258  settings=hardProcess,
259  printLongEventListing=printLongEventListing,
260  printShortEventListing=printShortEventListing,
261  ),
262  ),
263  )
264  )
265  if npileup > 0:
266  generators.append(
267  acts.examples.EventGenerator.Generator(
268  multiplicity=acts.examples.FixedMultiplicityGenerator(n=npileup),
269  vertex=vtxGen,
270  particles=acts.examples.pythia8.Pythia8Generator(
271  level=customLogLevel(),
273  pdgBeam0=beam[0],
274  pdgBeam1=beam[1],
275  cmsEnergy=cmsEnergy,
276  settings=pileupProcess,
277  ),
278  ),
279  )
280  )
281 
282  # Input
283  evGen = acts.examples.EventGenerator(
284  level=customLogLevel(),
285  generators=generators,
286  outputParticles="particles_input",
287  randomNumbers=rnd,
288  )
289 
290  s.addReader(evGen)
291 
292  s.addWhiteboardAlias("particles", evGen.config.outputParticles)
293 
294  if printParticles:
295  s.addAlgorithm(
296  acts.examples.ParticlesPrinter(
297  level=customLogLevel(),
298  inputParticles=evGen.config.outputParticles,
299  )
300  )
301 
302  if outputDirCsv is not None:
303  outputDirCsv = Path(outputDirCsv)
304  if not outputDirCsv.exists():
305  outputDirCsv.mkdir()
306 
307  s.addWriter(
308  acts.examples.CsvParticleWriter(
309  level=customLogLevel(),
310  inputParticles=evGen.config.outputParticles,
311  outputDir=str(outputDirCsv),
312  outputStem="particles",
313  )
314  )
315 
316  if outputDirRoot is not None:
317  outputDirRoot = Path(outputDirRoot)
318  if not outputDirRoot.exists():
319  outputDirRoot.mkdir()
320 
321  s.addWriter(
322  acts.examples.RootParticleWriter(
323  level=customLogLevel(),
324  inputParticles=evGen.config.outputParticles,
325  filePath=str(outputDirRoot / "pythia8_particles.root"),
326  )
327  )
328 
329  return s
330 
331 
334  config: ParticleSelectorConfig,
335  inputParticles: str,
336  outputParticles: str,
337  inputMeasurementParticlesMap: str = "",
338  logLevel: Optional[acts.logging.Level] = None,
339 ) -> None:
340  """
341  This function steers the particle selection.
342 
343  Parameters
344  ----------
345  s: Sequencer
346  the sequencer module to which we add the ParticleSelector
347  preselectedParticles: ParticleSelectorConfig
348  the particle selection configuration
349  inputParticles: str
350  the identifier for the input particles to be selected
351  outputParticles: str
352  the identifier for the final selected particle collection
353  """
354  customLogLevel = acts.examples.defaultLogging(s, logLevel)
355 
356  s.addAlgorithm(
357  acts.examples.ParticleSelector(
359  rhoMin=config.rho[0],
360  rhoMax=config.rho[1],
361  absZMin=config.absZ[0],
362  absZMax=config.absZ[1],
363  timeMin=config.time[0],
364  timeMax=config.time[1],
365  phiMin=config.phi[0],
366  phiMax=config.phi[1],
367  etaMin=config.eta[0],
368  etaMax=config.eta[1],
369  absEtaMin=config.absEta[0],
370  absEtaMax=config.absEta[1],
371  ptMin=config.pt[0],
372  ptMax=config.pt[1],
373  mMin=config.m[0],
374  mMax=config.m[1],
375  measurementsMin=config.measurements[0],
376  measurementsMax=config.measurements[1],
377  removeCharged=config.removeCharged,
378  removeNeutral=config.removeNeutral,
379  removeSecondaries=config.removeSecondaries,
380  ),
381  level=customLogLevel(),
382  inputParticles=inputParticles,
383  outputParticles=outputParticles,
384  inputMeasurementParticlesMap=inputMeasurementParticlesMap,
385  )
386  )
387 
388 
389 def addFatras(
391  trackingGeometry: acts.TrackingGeometry,
392  field: acts.MagneticFieldProvider,
393  rnd: acts.examples.RandomNumbers,
394  preSelectParticles: Optional[ParticleSelectorConfig] = ParticleSelectorConfig(),
395  postSelectParticles: Optional[ParticleSelectorConfig] = None,
396  enableInteractions: bool = True,
397  pMin: Optional[float] = None,
398  inputParticles: str = "particles_input",
399  outputParticlesInitial: str = "particles_initial",
400  outputParticlesFinal: str = "particles_final",
401  outputSimHits: str = "simhits",
402  outputDirCsv: Optional[Union[Path, str]] = None,
403  outputDirRoot: Optional[Union[Path, str]] = None,
404  logLevel: Optional[acts.logging.Level] = None,
405 ) -> None:
406  """This function steers the detector simulation using Fatras
407 
408  Parameters
409  ----------
410  s: Sequencer
411  the sequencer module to which we add the Fatras steps (returned from addFatras)
412  trackingGeometry : tracking geometry
413  field : magnetic field
414  rnd : RandomNumbers
415  random number generator
416  preSelectParticles : ParticleSelectorConfig(rho, absZ, time, phi, eta, absEta, pt, removeCharged, removeNeutral), None
417  ParticleSelector configuration to select particles as input to Fatras. Each range is specified as a tuple of (min,max).
418  Default of no selections specified in Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/ParticleSelector.hpp
419  Specify preSelectParticles=None to inhibit ParticleSelector altogether.
420  postSelectParticles : ParticleSelectorConfig(rho, absZ, time, phi, eta, absEta, pt, removeCharged, removeNeutral), None
421  Similar to preSelectParticles but applied after simulation to "particles_initial", therefore also filters secondaries.
422  enableInteractions : Enable the particle interactions in the simulation
423  pMin : Minimum monmentum of particles simulated by FATRAS
424  outputDirCsv : Path|str, path, None
425  the output folder for the Csv output, None triggers no output
426  outputDirRoot : Path|str, path, None
427  the output folder for the Root output, None triggers no output
428  """
429 
430  customLogLevel = acts.examples.defaultLogging(s, logLevel)
431 
432  # Selector
433  if preSelectParticles is not None:
434  particles_selected = "particles_selected"
436  s,
437  preSelectParticles,
438  inputParticles=inputParticles,
439  outputParticles=particles_selected,
440  )
441  else:
442  particles_selected = inputParticles
443 
444  # Simulation
445  alg = acts.examples.FatrasSimulation(
447  level=customLogLevel(),
448  inputParticles=particles_selected,
449  outputParticlesInitial=outputParticlesInitial,
450  outputParticlesFinal=outputParticlesFinal,
451  outputSimHits=outputSimHits,
452  randomNumbers=rnd,
453  trackingGeometry=trackingGeometry,
454  magneticField=field,
455  generateHitsOnSensitive=True,
456  emScattering=enableInteractions,
457  emEnergyLossIonisation=enableInteractions,
458  emEnergyLossRadiation=enableInteractions,
459  emPhotonConversion=enableInteractions,
460  pMin=pMin,
461  )
462  )
463 
464  # Sequencer
465  s.addAlgorithm(alg)
466 
467  # Selector
468  if postSelectParticles is not None:
469  particlesInitial = "fatras_particles_initial_selected"
471  s,
472  postSelectParticles,
473  inputParticles=alg.config.outputParticlesInitial,
474  outputParticles=particlesInitial,
475  )
476 
477  particlesFinal = "fatras_particles_final_selected"
479  s,
480  postSelectParticles,
481  inputParticles=alg.config.outputParticlesFinal,
482  outputParticles=particlesFinal,
483  )
484  else:
485  particlesInitial = alg.config.outputParticlesInitial
486  particlesFinal = alg.config.outputParticlesFinal
487 
488  # Only add alias for 'particles_initial' as this is the one we use most
489  s.addWhiteboardAlias("particles", particlesInitial)
490 
491  # Output
493  s,
494  alg.config.outputSimHits,
495  outputDirCsv,
496  outputDirRoot,
497  logLevel=logLevel,
498  particlesInitial=particlesInitial,
499  particlesFinal=particlesFinal,
500  )
501 
502  return s
503 
504 
505 def addSimWriters(
507  inputSimHits: Optional[str] = None,
508  outputDirCsv: Optional[Union[Path, str]] = None,
509  outputDirRoot: Optional[Union[Path, str]] = None,
510  logLevel: Optional[acts.logging.Level] = None,
511  particlesInitial="particles_initial",
512  particlesFinal="particles_final",
513 ) -> None:
514  customLogLevel = acts.examples.defaultLogging(s, logLevel)
515 
516  if outputDirCsv is not None:
517  outputDirCsv = Path(outputDirCsv)
518  if not outputDirCsv.exists():
519  outputDirCsv.mkdir()
520  s.addWriter(
521  acts.examples.CsvParticleWriter(
522  level=customLogLevel(),
523  outputDir=str(outputDirCsv),
524  inputParticles=particlesInitial,
525  outputStem="particles_initial",
526  )
527  )
528  s.addWriter(
529  acts.examples.CsvParticleWriter(
530  level=customLogLevel(),
531  outputDir=str(outputDirCsv),
532  inputParticles=particlesFinal,
533  outputStem="particles_final",
534  )
535  )
536  s.addWriter(
537  acts.examples.CsvSimHitWriter(
538  level=customLogLevel(),
539  inputSimHits=inputSimHits,
540  outputDir=str(outputDirCsv),
541  outputStem="hits",
542  )
543  )
544 
545  if outputDirRoot is not None:
546  outputDirRoot = Path(outputDirRoot)
547  if not outputDirRoot.exists():
548  outputDirRoot.mkdir()
549  s.addWriter(
550  acts.examples.RootParticleWriter(
551  level=customLogLevel(),
552  inputParticles=particlesInitial,
553  filePath=str(outputDirRoot / "particles_initial.root"),
554  )
555  )
556  s.addWriter(
557  acts.examples.RootParticleWriter(
558  level=customLogLevel(),
559  inputParticles=particlesFinal,
560  filePath=str(outputDirRoot / "particles_final.root"),
561  )
562  )
563  s.addWriter(
564  acts.examples.RootSimHitWriter(
565  level=customLogLevel(),
566  inputSimHits=inputSimHits,
567  filePath=str(outputDirRoot / "hits.root"),
568  )
569  )
570 
571 
573  detector: Any,
574 ) -> Any:
575  try:
576  from acts.examples import TelescopeDetector
577  from acts.examples.geant4 import TelescopeG4DetectorConstructionFactory
578 
579  if type(detector) is TelescopeDetector:
580  return TelescopeG4DetectorConstructionFactory(detector)
581  except Exception as e:
582  print(e)
583 
584  try:
585  from acts.examples.dd4hep import DD4hepDetector
586  from acts.examples.geant4.dd4hep import DDG4DetectorConstructionFactory
587 
588  if type(detector) is DD4hepDetector:
589  return DDG4DetectorConstructionFactory(detector)
590  except Exception as e:
591  print(e)
592 
593  raise AttributeError(f"cannot find a suitable detector construction for {detector}")
594 
595 
596 # holds the Geant4Handle for potential reuse
597 __geant4Handle = None
598 
599 
600 def addGeant4(
602  detector: Optional[Any],
603  trackingGeometry: acts.TrackingGeometry,
604  field: acts.MagneticFieldProvider,
605  rnd: acts.examples.RandomNumbers,
606  g4DetectorConstructionFactory: Optional[Any] = None,
607  volumeMappings: List[str] = [],
608  materialMappings: List[str] = [],
609  inputParticles: str = "particles_input",
610  outputParticlesInitial: str = "particles_initial",
611  outputParticlesFinal: str = "particles_final",
612  outputSimHits: str = "simhits",
613  preSelectParticles: Optional[ParticleSelectorConfig] = ParticleSelectorConfig(),
614  postSelectParticles: Optional[ParticleSelectorConfig] = None,
615  recordHitsOfSecondaries=True,
616  keepParticlesWithoutHits=True,
617  outputDirCsv: Optional[Union[Path, str]] = None,
618  outputDirRoot: Optional[Union[Path, str]] = None,
619  logLevel: Optional[acts.logging.Level] = None,
620  killVolume: Optional[acts.Volume] = None,
621  killAfterTime: float = float("inf"),
622  killSecondaries: bool = False,
623  physicsList: str = "FTFP_BERT",
624 ) -> None:
625  """This function steers the detector simulation using Geant4
626 
627  Parameters
628  ----------
629  s: Sequencer
630  the sequencer module to which we add the Geant4 steps (returned from addGeant4)
631  trackingGeometry : tracking geometry
632  field : magnetic field
633  rnd : RandomNumbers, None
634  random number generator
635  preSelectParticles : ParticleSelectorConfig(rho, absZ, time, phi, eta, absEta, pt, removeCharged, removeNeutral), None
636  ParticleSelector configuration to select particles as input to Geant4. Each range is specified as a tuple of (min,max).
637  Default of no selections specified in Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/ParticleSelector.hpp
638  Specify preSelectParticles=None to inhibit ParticleSelector altogether.
639  postSelectParticles : ParticleSelectorConfig(rho, absZ, time, phi, eta, absEta, pt, removeCharged, removeNeutral), None
640  Similar to preSelectParticles but applied after simulation to "particles_initial", therefore also filters secondaries.
641  outputDirCsv : Path|str, path, None
642  the output folder for the Csv output, None triggers no output
643  outputDirRoot : Path|str, path, None
644  the output folder for the Root output, None triggers no output
645  killVolume: acts.Volume, None
646  if given, particles are killed when going outside this volume.
647  killAfterTime: float
648  if given, particle are killed after the global time since event creation exceeds the given value
649  killSecondaries: bool
650  if given, secondary particles are removed from simulation
651  """
652 
653  from acts.examples.geant4 import Geant4Simulation
654 
655  customLogLevel = acts.examples.defaultLogging(s, logLevel)
656 
657  # Selector
658  if preSelectParticles is not None:
659  particles_selected = "particles_selected"
661  s,
662  preSelectParticles,
663  inputParticles=inputParticles,
664  outputParticles=particles_selected,
665  )
666  else:
667  particles_selected = inputParticles
668 
669  if g4DetectorConstructionFactory is None:
670  if detector is None:
671  raise AttributeError("detector not given")
672  g4DetectorConstructionFactory = getG4DetectorConstructionFactory(detector)
673 
674  global __geant4Handle
675 
676  # Simulation
677  alg = Geant4Simulation(
678  level=customLogLevel(),
679  geant4Handle=__geant4Handle,
680  detectorConstructionFactory=g4DetectorConstructionFactory,
681  randomNumbers=rnd,
682  inputParticles=particles_selected,
683  outputSimHits=outputSimHits,
684  outputParticlesInitial=outputParticlesInitial,
685  outputParticlesFinal=outputParticlesFinal,
686  trackingGeometry=trackingGeometry,
687  magneticField=field,
688  physicsList=physicsList,
689  volumeMappings=volumeMappings,
690  materialMappings=materialMappings,
691  killVolume=killVolume,
692  killAfterTime=killAfterTime,
693  killSecondaries=killSecondaries,
694  recordHitsOfSecondaries=recordHitsOfSecondaries,
695  keepParticlesWithoutHits=keepParticlesWithoutHits,
696  )
697 
698  __geant4Handle = alg.geant4Handle
699 
700  # Sequencer
701  s.addAlgorithm(alg)
702 
703  # Selector
704  if postSelectParticles is not None:
705  particlesInitial = "geant4_particles_initial_selected"
707  s,
708  postSelectParticles,
709  inputParticles=alg.config.outputParticlesInitial,
710  outputParticles=particlesInitial,
711  )
712 
713  particlesFinal = "geant4_particles_final_selected"
715  s,
716  postSelectParticles,
717  inputParticles=alg.config.outputParticlesFinal,
718  outputParticles=particlesFinal,
719  )
720  else:
721  particlesInitial = alg.config.outputParticlesInitial
722  particlesFinal = alg.config.outputParticlesFinal
723 
724  # Only add alias for 'particles_initial' as this is the one we use most
725  s.addWhiteboardAlias("particles", particlesInitial)
726 
727  # Output
729  s,
730  alg.config.outputSimHits,
731  outputDirCsv,
732  outputDirRoot,
733  logLevel=logLevel,
734  particlesInitial=particlesInitial,
735  particlesFinal=particlesFinal,
736  )
737 
738  return s
739 
740 
741 def addDigitization(
743  trackingGeometry: acts.TrackingGeometry,
744  field: acts.MagneticFieldProvider,
745  digiConfigFile: Union[Path, str],
746  outputDirCsv: Optional[Union[Path, str]] = None,
747  outputDirRoot: Optional[Union[Path, str]] = None,
748  rnd: Optional[acts.examples.RandomNumbers] = None,
749  doMerge: Optional[bool] = None,
750  minEnergyDeposit: Optional[float] = None,
751  logLevel: Optional[acts.logging.Level] = None,
753  """This function steers the digitization step
754 
755  Parameters
756  ----------
757  s: Sequencer
758  the sequencer module to which we add the Digitization steps (returned from addDigitization)
759  trackingGeometry : tracking geometry
760  field : magnetic field
761  digiConfigFile : Path|str, path
762  Configuration (.json) file for digitization or smearing description
763  outputDirCsv : Path|str, path, None
764  the output folder for the Csv output, None triggers no output
765  outputDirRoot : Path|str, path, None
766  the output folder for the Root output, None triggers no output
767  rnd : RandomNumbers, None
768  random number generator
769  """
770 
771  customLogLevel = acts.examples.defaultLogging(s, logLevel)
772 
773  # Preliminaries
774  rnd = rnd or acts.examples.RandomNumbers()
775 
776  # Digitization
777  digiCfg = acts.examples.DigitizationConfig(
778  acts.examples.readDigiConfigFromJson(
779  str(digiConfigFile),
780  ),
781  trackingGeometry=trackingGeometry,
782  randomNumbers=rnd,
783  inputSimHits="simhits",
784  outputSourceLinks="sourcelinks",
785  outputMeasurements="measurements",
786  outputMeasurementParticlesMap="measurement_particles_map",
787  outputMeasurementSimHitsMap="measurement_simhits_map",
788  doMerge=doMerge,
789  )
790 
791  # Not sure how to do this in our style
792  if minEnergyDeposit is not None:
793  digiCfg.minEnergyDeposit = minEnergyDeposit
794 
795  digiAlg = acts.examples.DigitizationAlgorithm(digiCfg, customLogLevel())
796 
797  s.addAlgorithm(digiAlg)
798 
799  if outputDirRoot is not None:
800  outputDirRoot = Path(outputDirRoot)
801  if not outputDirRoot.exists():
802  outputDirRoot.mkdir()
803  rmwConfig = acts.examples.RootMeasurementWriter.Config(
804  inputMeasurements=digiAlg.config.outputMeasurements,
805  inputClusters=digiAlg.config.outputClusters,
806  inputSimHits=digiAlg.config.inputSimHits,
807  inputMeasurementSimHitsMap=digiAlg.config.outputMeasurementSimHitsMap,
808  filePath=str(outputDirRoot / f"{digiAlg.config.outputMeasurements}.root"),
809  trackingGeometry=trackingGeometry,
810  )
811  rmwConfig.addBoundIndicesFromDigiConfig(digiAlg.config)
812  s.addWriter(acts.examples.RootMeasurementWriter(rmwConfig, customLogLevel()))
813 
814  if outputDirCsv is not None:
815  outputDirCsv = Path(outputDirCsv)
816  if not outputDirCsv.exists():
817  outputDirCsv.mkdir()
818  s.addWriter(
819  acts.examples.CsvMeasurementWriter(
820  level=customLogLevel(),
821  inputMeasurements=digiAlg.config.outputMeasurements,
822  inputClusters=digiAlg.config.outputClusters,
823  inputMeasurementSimHitsMap=digiAlg.config.outputMeasurementSimHitsMap,
824  outputDir=str(outputDirCsv),
825  )
826  )
827 
828  return s