Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-11 08:24:52

0001 """
0002 
0003 DD4hep simulation with some argument parsing
0004 Based on M. Frank and F. Gaede runSim.py
0005    @author  A.Sailer
0006    @version 0.1
0007 
0008 """
0009 import argparse
0010 import io
0011 import logging
0012 import os
0013 import sys
0014 import textwrap
0015 import traceback
0016 from urllib.parse import urlparse
0017 from DDSim.Helper.Meta import Meta
0018 from DDSim.Helper.LCIO import LCIO
0019 from DDSim.Helper.EDM4hep import EDM4hep
0020 from DDSim.Helper.HepMC3 import HepMC3
0021 from DDSim.Helper.GuineaPig import GuineaPig
0022 from DDSim.Helper.Physics import Physics
0023 from DDSim.Helper.Filter import Filter
0024 from DDSim.Helper.Geometry import Geometry
0025 from DDSim.Helper.Random import Random
0026 from DDSim.Helper.Action import Action
0027 from DDSim.Helper.Output import Output, outputLevel, outputLevelType
0028 from DDSim.Helper.OutputConfig import OutputConfig, defaultOutputFile
0029 from DDSim.Helper.InputConfig import InputConfig
0030 from DDSim.Helper.ConfigHelper import ConfigHelper
0031 from DDSim.Helper.MagneticField import MagneticField
0032 from DDSim.Helper.ParticleHandler import ParticleHandler
0033 from DDSim.Helper.Gun import Gun
0034 from DDSim.Helper.UI import UI
0035 
0036 logger = logging.getLogger('DDSim')
0037 
0038 try:
0039   import argcomplete
0040   ARGCOMPLETEENABLED = True
0041 except ImportError:
0042   ARGCOMPLETEENABLED = False
0043 
0044 HEPMC3_SUPPORTED_EXTENSIONS = [
0045     ".hepmc.gz", ".hepmc.xz", ".hepmc.bz2",
0046     ".hepmc3", ".hepmc3.gz", ".hepmc3.xz", ".hepmc3.bz2",
0047     ".hepmc3.tree.root",
0048     ]
0049 EDM4HEP_INPUT_EXTENSIONS = [
0050     ".root",
0051     ".sio",
0052     ]
0053 POSSIBLEINPUTFILES = [
0054     ".stdhep", ".slcio", ".HEPEvt", ".hepevt",
0055     ".pairs",
0056     ".hepmc",
0057     ]
0058 POSSIBLEINPUTFILES += HEPMC3_SUPPORTED_EXTENSIONS
0059 POSSIBLEINPUTFILES += EDM4HEP_INPUT_EXTENSIONS
0060 
0061 
0062 class DD4hepSimulation(object):
0063   """Class to hold all the parameters and functions to run simulation"""
0064 
0065   def __init__(self):
0066     self.steeringFile = None
0067     self.compactFile = []
0068     self.inputFiles = []
0069     self.outputFile = defaultOutputFile()
0070     self.runType = "batch"
0071     self.printLevel = 3
0072 
0073     self.numberOfEvents = 0
0074     self.skipNEvents = 0
0075     self.physicsList = None  # deprecated use physics.list
0076     self.crossingAngleBoost = 0.0
0077     self.macroFile = ''
0078     self.enableGun = False
0079     self.enableG4GPS = False
0080     self.enableG4Gun = False
0081     self._g4gun = None
0082     self._g4gps = None
0083     self.vertexSigma = [0.0, 0.0, 0.0, 0.0]
0084     self.vertexOffset = [0.0, 0.0, 0.0, 0.0]
0085     self.enableDetailedShowerMode = False
0086     self.disableSignalHandler = False
0087 
0088     self._errorMessages = []
0089     self._dumpParameter = False
0090     self._dumpSteeringFile = False
0091 
0092     # objects for extended configuration option
0093     self.output = Output()
0094     self.random = Random()
0095     self.gun = Gun()
0096     self.part = ParticleHandler()
0097     self.field = MagneticField()
0098     self.action = Action()
0099     self.outputConfig = OutputConfig()
0100     self.inputConfig = InputConfig()
0101     self.guineapig = GuineaPig()
0102     self.lcio = LCIO()
0103     self.edm4hep = EDM4hep()
0104     self.hepmc3 = HepMC3()
0105     self.meta = Meta()
0106 
0107     self.geometry = Geometry()
0108     self.filter = Filter()
0109     self.physics = Physics()
0110     self.ui = UI()
0111     self._gdb = False
0112 
0113     self._argv = None
0114 
0115   def readSteeringFile(self):
0116     """Reads a steering file and sets the parameters to that of the
0117     DD4hepSimulation object present in the steering file.
0118     """
0119     globs = {}
0120     locs = {"SIM": self}
0121     if not self.steeringFile:
0122       return
0123     sFileTemp = self.steeringFile
0124     exec(compile(io.open(self.steeringFile).read(), self.steeringFile, 'exec'), globs, locs)
0125     for _name, obj in locs.items():
0126       if isinstance(obj, DD4hepSimulation) and obj is not self:
0127         self.__dict__ = obj.__dict__
0128     self.steeringFile = os.path.abspath(sFileTemp)
0129 
0130   def parseOptions(self, argv=None):
0131     """parse the command line options"""
0132 
0133     if argv is None:
0134       self._argv = list(sys.argv)
0135 
0136     parser = argparse.ArgumentParser("Running DD4hep Simulations:",
0137                                      formatter_class=argparse.RawTextHelpFormatter)
0138 
0139     parser.add_argument("--steeringFile", "-S", action="store", default=self.steeringFile,
0140                         help="Steering file to change default behaviour")
0141 
0142     # first we parse just the steering file, but only if we don't want to see the help message
0143     if not any(opt in self._argv for opt in ('-h', '--help')):
0144       parsed, _unknown = parser.parse_known_args()
0145       self.steeringFile = parsed.steeringFile
0146       self.readSteeringFile()
0147 
0148     # readSteeringFile will set self._argv to None if there is a steering file
0149     if self._argv is None:
0150       self._argv = list(argv) if argv else list(sys.argv)
0151 
0152     parser.add_argument("--compactFile", nargs='+', action="store",
0153                         default=ConfigHelper.makeList(self.compactFile), type=str,
0154                         help="The compact XML file, or multiple compact files, if the last one is the closer.")
0155 
0156     parser.add_argument("--runType", action="store", choices=("batch", "vis", "run", "shell", "qt"),
0157                         default=self.runType,
0158                         help="The type of action to do in this invocation"  # Note: implicit string concatenation
0159                         "\nbatch: just simulate some events, needs numberOfEvents, and input file or gun"
0160                         "\nvis: enable visualisation, run the macroFile if it is set"
0161                         "\nqt: enable visualisation in Qt shell, run the macroFile if it is set"
0162                         "\nrun: run the macroFile and exit"
0163                         "\nshell: enable interactive session")
0164 
0165     parser.add_argument("--inputFiles", "-I", nargs='+', action="store", default=self.inputFiles,
0166                         help="InputFiles for simulation %s files are supported"
0167                         "\nEDM4hep files are also supported under the .root extension" % ", ".join(POSSIBLEINPUTFILES))
0168 
0169     parser.add_argument("--outputFile", "-O", action="store", default=self.outputFile,
0170                         help="Outputfile from the simulation: .slcio, edm4hep.root and .root"
0171                         " output files are supported")
0172 
0173     parser.add_argument("-v", "--printLevel", action="store", default=self.printLevel, dest="printLevel",
0174                         choices=(1, 2, 3, 4, 5, 6, 7, 'VERBOSE', 'DEBUG',
0175                                  'INFO', 'WARNING', 'ERROR', 'FATAL', 'ALWAYS'),
0176                         type=outputLevelType,
0177                         help="Verbosity use integers from 1(most) to 7(least) verbose"
0178                         "\nor strings: VERBOSE, DEBUG, INFO, WARNING, ERROR, FATAL, ALWAYS")
0179 
0180     parser.add_argument("--numberOfEvents", "-N", action="store", dest="numberOfEvents", default=self.numberOfEvents,
0181                         type=int, help="number of events to simulate, used in batch mode")
0182 
0183     parser.add_argument("--skipNEvents", action="store", dest="skipNEvents", default=self.skipNEvents, type=int,
0184                         help="Skip first N events when reading a file")
0185 
0186     parser.add_argument("--physicsList", action="store", dest="physicsList", default=self.physicsList,
0187                         help="Physics list to use in simulation. Deprecated, use physics.list")
0188 
0189     parser.add_argument("--crossingAngleBoost", action="store", dest="crossingAngleBoost",
0190                         default=self.crossingAngleBoost,
0191                         type=float, help="Lorentz boost for the crossing angle, in radian!")
0192 
0193     parser.add_argument("--vertexSigma", nargs=4, action="store", dest="vertexSigma",
0194                         default=self.vertexSigma, metavar=('X', 'Y', 'Z', 'T'),
0195                         type=float, help="FourVector of the Sigma for the Smearing of the Vertex position: x y z t")
0196 
0197     parser.add_argument("--vertexOffset", nargs=4, action="store", dest="vertexOffset",
0198                         default=self.vertexOffset, metavar=('X', 'Y', 'Z', 'T'),
0199                         type=float, help="FourVector of translation for the Smearing of the Vertex position: x y z t")
0200 
0201     parser.add_argument("--macroFile", "-M", action="store", dest="macroFile", default=self.macroFile,
0202                         help="Macro file to execute for runType 'run' or 'vis'")
0203 
0204     parser.add_argument("--enableGun", "-G", action="store_true", dest="enableGun", default=self.enableGun,
0205                         help="enable the DDG4 particle gun")
0206 
0207     parser.add_argument("--enableG4GPS", action="store_true", dest="enableG4GPS", default=self.enableG4GPS,
0208                         help="enable the Geant4 GeneralParticleSource. Needs a macroFile (runType run)"
0209                         "or use it with the shell (runType shell)")
0210 
0211     parser.add_argument("--enableG4Gun", action="store_true", dest="enableG4Gun", default=self.enableG4Gun,
0212                         help="enable the Geant4 particle gun. Needs a macroFile (runType run)"
0213                         " or use it with the shell (runType shell)")
0214 
0215     parser.add_argument("--dumpParameter", "--dump", action="store_true", dest="dumpParameter",
0216                         default=self._dumpParameter, help="Print all configuration Parameters and exit")
0217 
0218     parser.add_argument("--enableDetailedShowerMode", action="store_true", dest="enableDetailedShowerMode",
0219                         default=self.enableDetailedShowerMode,
0220                         help="use detailed shower mode")
0221 
0222     parser.add_argument("--disableSignalHandler", action="store_true", dest="disableSignalHandler",
0223                         default=self.disableSignalHandler,
0224                         help="disable the Signal Handler of DD4hep")
0225 
0226     parser.add_argument("--dumpSteeringFile", action="store_true", dest="dumpSteeringFile",
0227                         default=self._dumpSteeringFile, help="print an example steering file to stdout")
0228 
0229     parser.add_argument("--gdb", action="store_true", dest="_gdb",
0230                         default=False, help="run the simulation under gdb")
0231 
0232     # output, or do something smarter with fullHelp only for example
0233     ConfigHelper.addAllHelper(self, parser)
0234     # now parse everything. The default values are now taken from the
0235     # steeringFile if they were set so that the steering file parameters can be
0236     # overwritten from the command line
0237     if ARGCOMPLETEENABLED:
0238       argcomplete.autocomplete(parser)
0239     parsed = parser.parse_args()
0240 
0241     self._dumpParameter = parsed.dumpParameter
0242     self._dumpSteeringFile = parsed.dumpSteeringFile
0243 
0244     self.compactFile = ConfigHelper.makeList(parsed.compactFile)
0245     self.__checkFilesExist(self.compactFile, fileType='compact')
0246     self.inputFiles = parsed.inputFiles
0247     self.inputFiles = self.__checkFileFormat(self.inputFiles, POSSIBLEINPUTFILES)
0248     self.__checkFilesExist(self.inputFiles, fileType='input')
0249     self.outputFile = parsed.outputFile
0250     self.__checkFileFormat(self.outputFile, ('.root', '.slcio'))
0251     self.runType = parsed.runType
0252     self.printLevel = self.__checkOutputLevel(parsed.printLevel)
0253     self._gdb = parsed._gdb
0254 
0255     self.numberOfEvents = parsed.numberOfEvents
0256     self.skipNEvents = parsed.skipNEvents
0257     self.physicsList = parsed.physicsList
0258     self.crossingAngleBoost = parsed.crossingAngleBoost
0259     self.macroFile = parsed.macroFile
0260     self.enableGun = parsed.enableGun
0261     self.enableG4Gun = parsed.enableG4Gun
0262     self.enableG4GPS = parsed.enableG4GPS
0263     self.enableDetailedShowerMode = parsed.enableDetailedShowerMode
0264     self.vertexOffset = parsed.vertexOffset
0265     self.vertexSigma = parsed.vertexSigma
0266 
0267     self._consistencyChecks()
0268 
0269     if self.printLevel <= 2:  # VERBOSE or DEBUG
0270       logger.setLevel(logging.DEBUG)
0271 
0272     # self.__treatUnknownArgs( parsed, unknown )
0273     self.__parseAllHelper(parsed)
0274     if self._errorMessages and not (self._dumpParameter or self._dumpSteeringFile):
0275       parser.epilog = "\n".join(self._errorMessages)
0276       parser.print_help()
0277       exit(1)
0278 
0279     if self._dumpParameter:
0280       from pprint import pprint
0281       logger.info("=" * 80)
0282       pprint(vars(self))
0283       logger.info("=" * 80)
0284       exit(0)
0285 
0286     if self._dumpSteeringFile:
0287       self.__printSteeringFile(parser)
0288       exit(0)
0289 
0290     if self._gdb:
0291       self.__attachGDB()
0292 
0293   def getDetectorLists(self, detectorDescription):
0294     ''' get lists of trackers and calorimeters that are defined in detectorDescription (the compact xml file)'''
0295     import DDG4
0296     trackers, calos, unknown = [], [], []
0297     for i in detectorDescription.detectors():
0298       det = DDG4.DetElement(i.second.ptr())
0299       name = det.name()
0300       sd = detectorDescription.sensitiveDetector(name)
0301       if sd.isValid():
0302         detType = sd.type()
0303         logger.info('getDetectorLists - found active detector %s type: %s', name, detType)
0304         if any(pat.lower() in detType.lower() for pat in self.action.trackerSDTypes):
0305           logger.info('getDetectorLists - Identified %s as a tracker', name)
0306           trackers.append(det.name())
0307         elif any(pat.lower() in detType.lower() for pat in self.action.calorimeterSDTypes):
0308           logger.info('getDetectorLists - Identified %s as a calorimeter', name)
0309           calos.append(det.name())
0310         else:
0311           logger.warning('getDetectorLists - Unknown sensitive detector type: %s', detType)
0312           unknown.append(det.name())
0313 
0314     return trackers, calos, unknown
0315 
0316 # ==================================================================================
0317 
0318   def run(self):
0319     """setup the geometry and dd4hep and geant4 and do what was asked to be done"""
0320     import ROOT
0321     ROOT.PyConfig.IgnoreCommandLineOptions = True
0322 
0323     import DDG4
0324     import dd4hep
0325 
0326     self.printLevel = getOutputLevel(self.printLevel)
0327 
0328     kernel = DDG4.Kernel()
0329     dd4hep.setPrintLevel(self.printLevel)
0330 
0331     for compactFile in self.compactFile:
0332       kernel.loadGeometry(str("file:" + os.path.abspath(compactFile)))
0333     detectorDescription = kernel.detectorDescription()
0334 
0335     DDG4.importConstants(detectorDescription)
0336 
0337   # ----------------------------------------------------------------------------------
0338 
0339     # simple = DDG4.Geant4( kernel, tracker='Geant4TrackerAction',calo='Geant4CalorimeterAction')
0340     # geant4 = DDG4.Geant4( kernel, tracker='Geant4TrackerCombineAction',calo='Geant4ScintillatorCalorimeterAction')
0341     geant4 = DDG4.Geant4(kernel, tracker=self.action.tracker, calo=self.action.calo)
0342     if not self.disableSignalHandler:
0343       geant4.registerInterruptHandler()
0344 
0345     geant4.printDetectors()
0346 
0347     if self.runType == "vis":
0348       uiaction = geant4.setupUI(typ="tcsh", vis=True, macro=self.macroFile)
0349     elif self.runType == "qt":
0350       uiaction = geant4.setupUI(typ="qt", vis=True, macro=self.macroFile)
0351     elif self.runType == "run":
0352       uiaction = geant4.setupUI(typ="tcsh", vis=False, macro=self.macroFile, ui=False)
0353     elif self.runType == "shell":
0354       uiaction = geant4.setupUI(typ="tcsh", vis=False, macro=None, ui=True)
0355     elif self.runType == "batch":
0356       uiaction = geant4.setupUI(typ="tcsh", vis=False, macro=None, ui=False)
0357     else:
0358       logger.error("unknown runType")
0359       return 1
0360 
0361     # User Configuration for the Geant4Phases
0362     uiaction.ConfigureCommands = self.ui._commandsConfigure
0363     uiaction.InitializeCommands = self.ui._commandsInitialize
0364     uiaction.PostRunCommands = self.ui._commandsPostRun
0365     uiaction.PreRunCommands = self.ui._commandsPreRun
0366     uiaction.TerminateCommands = self.ui._commandsTerminate
0367 
0368     kernel.NumEvents = self.numberOfEvents
0369 
0370     # -----------------------------------------------------------------------------------
0371     # setup the magnetic field:
0372     self.__setMagneticFieldOptions(geant4)
0373 
0374     # configure geometry creation
0375     self.geometry.constructGeometry(kernel, geant4, self.output.geometry)
0376 
0377     # ----------------------------------------------------------------------------------
0378     # Configure run, event, track, step, and stack actions, if present
0379     for action_list, DDG4_Action, kernel_Action in \
0380         [(self.action.run, DDG4.RunAction, kernel.runAction),
0381          (self.action.event, DDG4.EventAction, kernel.eventAction),
0382          (self.action.track, DDG4.TrackingAction, kernel.trackingAction),
0383          (self.action.step, DDG4.SteppingAction, kernel.steppingAction),
0384          (self.action.stack, DDG4.StackingAction, kernel.stackingAction)]:
0385       for action_dict in action_list:
0386         action = DDG4_Action(kernel, action_dict["name"])
0387         for parameter, value in action_dict.get('parameter', {}).items():
0388           setattr(action, parameter, value)
0389         kernel_Action().add(action)
0390 
0391     # ----------------------------------------------------------------------------------
0392     # Configure Run actions
0393     run1 = DDG4.RunAction(kernel, 'Geant4TestRunAction/RunInit')
0394     kernel.registerGlobalAction(run1)
0395     kernel.runAction().add(run1)
0396 
0397     # Configure the random seed, do it before the I/O because we might change the seed!
0398     self.random.initialize(DDG4, kernel, self.output.random)
0399 
0400     # Configure the output file format and plugin
0401     self.outputConfig.initialize(dd4hepsimulation=self, geant4=geant4)
0402 
0403     actionList = []
0404 
0405     if self.enableGun:
0406       gun = DDG4.GeneratorAction(kernel, "Geant4ParticleGun/" + "Gun")
0407       self.gun.setOptions(gun)
0408       gun.Standalone = False
0409       gun.Mask = 1
0410       actionList.append(gun)
0411       self.__applyBoostOrSmear(kernel, actionList, 1)
0412       logger.info("++++ Adding DD4hep Particle Gun ++++")
0413 
0414     if self.enableG4Gun:
0415       # GPS Create something
0416       self._g4gun = DDG4.GeneratorAction(kernel, "Geant4GeneratorWrapper/Gun")
0417       self._g4gun.Uses = 'G4ParticleGun'
0418       self._g4gun.Mask = 2
0419       logger.info("++++ Adding Geant4 Particle Gun ++++")
0420       actionList.append(self._g4gun)
0421 
0422     if self.enableG4GPS:
0423       # GPS Create something
0424       self._g4gps = DDG4.GeneratorAction(kernel, "Geant4GeneratorWrapper/GPS")
0425       self._g4gps.Uses = 'G4GeneralParticleSource'
0426       self._g4gps.Mask = 3
0427       logger.info("++++ Adding Geant4 General Particle Source ++++")
0428       actionList.append(self._g4gps)
0429 
0430     start = 4
0431     for index, plugin in enumerate(self.inputConfig.userInputPlugin, start=start):
0432       gen = plugin(self)
0433       gen.Mask = index
0434       start = index + 1
0435       actionList.append(gen)
0436       self.__applyBoostOrSmear(kernel, actionList, index)
0437       logger.info("++++ Adding User Plugin %s ++++", gen.Name)
0438 
0439     for index, inputFile in enumerate(self.inputFiles, start=start):
0440       if inputFile.endswith(".slcio"):
0441         gen = DDG4.GeneratorAction(kernel, "LCIOInputAction/LCIO%d" % index)
0442         gen.Parameters = self.lcio.getParameters()
0443         gen.Input = "LCIOFileReader|" + inputFile
0444       elif inputFile.endswith(".stdhep"):
0445         gen = DDG4.GeneratorAction(kernel, "LCIOInputAction/STDHEP%d" % index)
0446         gen.Input = "LCIOStdHepReader|" + inputFile
0447       elif inputFile.endswith(".HEPEvt"):
0448         gen = DDG4.GeneratorAction(kernel, "Geant4InputAction/HEPEvt%d" % index)
0449         gen.Input = "Geant4EventReaderHepEvtShort|" + inputFile
0450       elif inputFile.endswith(".hepevt"):
0451         gen = DDG4.GeneratorAction(kernel, "Geant4InputAction/hepevt%d" % index)
0452         gen.Input = "Geant4EventReaderHepEvtLong|" + inputFile
0453       elif inputFile.endswith(tuple([".hepmc"] + HEPMC3_SUPPORTED_EXTENSIONS)):
0454         if self.hepmc3.useHepMC3:
0455           gen = DDG4.GeneratorAction(kernel, "Geant4InputAction/hepmc%d" % index)
0456           gen.Parameters = self.hepmc3.getParameters()
0457           gen.Input = "HEPMC3FileReader|" + inputFile
0458         else:
0459           gen = DDG4.GeneratorAction(kernel, "Geant4InputAction/hepmc%d" % index)
0460           gen.Input = "Geant4EventReaderHepMC|" + inputFile
0461       elif inputFile.endswith(".pairs"):
0462         gen = DDG4.GeneratorAction(kernel, "Geant4InputAction/GuineaPig%d" % index)
0463         gen.Input = "Geant4EventReaderGuineaPig|" + inputFile
0464         gen.Parameters = self.guineapig.getParameters()
0465       elif inputFile.endswith(tuple(EDM4HEP_INPUT_EXTENSIONS)):
0466         # EDM4HEP must come after HEPMC3 because of .root also part of hepmc3 extensions
0467         gen = DDG4.GeneratorAction(kernel, "Geant4InputAction/EDM4hep%d" % index)
0468         gen.Parameters = self.edm4hep.getParameters()
0469         gen.Input = "EDM4hepFileReader|" + inputFile
0470       else:
0471         # this should never happen because we already check at the top, but in case of some LogicError...
0472         raise RuntimeError("Unknown input file type: %s" % inputFile)
0473       gen.AlternativeDecayStatuses = self.physics.alternativeDecayStatuses
0474       gen.AlternativeStableStatuses = self.physics.alternativeStableStatuses
0475       gen.Sync = self.skipNEvents
0476       gen.Mask = index
0477       actionList.append(gen)
0478       self.__applyBoostOrSmear(kernel, actionList, index)
0479 
0480     generationInit = None
0481     if actionList:
0482       generationInit = self._buildInputStage(geant4, actionList, output_level=self.output.inputStage,
0483                                              have_mctruth=self._enablePrimaryHandler())
0484 
0485     # ================================================================================================
0486 
0487     # And handle the simulation particles.
0488     part = DDG4.GeneratorAction(kernel, "Geant4ParticleHandler/ParticleHandler")
0489     kernel.generatorAction().adopt(part)
0490     # part.SaveProcesses = ['conv','Decay']
0491     part.SaveProcesses = self.part.saveProcesses
0492     part.MinimalKineticEnergy = self.part.minimalKineticEnergy
0493     part.KeepAllParticles = self.part.keepAllParticles
0494     part.PrintEndTracking = self.part.printEndTracking
0495     part.PrintStartTracking = self.part.printStartTracking
0496     part.MinDistToParentVertex = self.part.minDistToParentVertex
0497     part.OutputLevel = self.output.part
0498     part.enableUI()
0499 
0500     if self.part.enableDetailedHitsAndParticleInfo:
0501       self.part.setDumpDetailedParticleInfo(kernel, DDG4)
0502 
0503     self.part.setupUserParticleHandler(part, kernel, DDG4)
0504 
0505     # =================================================================================
0506 
0507     # Setup global filters for use in sensitive detectors
0508     try:
0509       self.filter.setupFilters(kernel)
0510     except RuntimeError as e:
0511       logger.error("%s", e)
0512       return 1
0513 
0514     # =================================================================================
0515     # get lists of trackers and calorimeters in detectorDescription
0516 
0517     trk, cal, unk = self.getDetectorLists(detectorDescription)
0518     for detectors, function, defFilter, defAction, abort in \
0519         [(trk, geant4.setupTracker, self.filter.tracker, self.action.tracker, False),
0520          (cal, geant4.setupCalorimeter, self.filter.calo, self.action.calo, False),
0521          (unk, geant4.setupDetector, None, "No Default", True),
0522          ]:
0523       try:
0524         self.__setupSensitiveDetectors(detectors, function, defFilter, defAction, abort)
0525       except Exception as e:
0526         logger.error("Failed setting up sensitive detector %s", e)
0527         raise
0528 
0529   # =================================================================================
0530     # Now build the physics list:
0531     _phys = self.physics.setupPhysics(kernel, name=self.physicsList)
0532     _phys.verbosity = self.output.physics
0533 
0534     # add the G4StepLimiterPhysics to activate the max step limits in volumes
0535     ph = DDG4.PhysicsList(kernel, 'Geant4PhysicsList/Myphysics')
0536     ph.addPhysicsConstructor(str('G4StepLimiterPhysics'))
0537     _phys.add(ph)
0538 
0539     dd4hep.setPrintLevel(self.printLevel)
0540 
0541     kernel.configure()
0542     kernel.initialize()
0543 
0544     # GPS
0545     if self._g4gun is not None:
0546       self._g4gun.generator()
0547     if self._g4gps is not None:
0548       self._g4gps.generator()
0549 
0550     startUpTime, _sysTime, _cuTime, _csTime, _elapsedTime = os.times()
0551 
0552     exitCode = 0
0553     if not kernel.run():
0554       logger.error("Simulation failed!")
0555       exitCode += 1
0556     if not kernel.terminate():
0557       exitCode += 1
0558       logger.error("Termination failed!")
0559 
0560     totalTimeUser, totalTimeSys, _cuTime, _csTime, _elapsedTime = os.times()
0561     processedEvents = self.numberOfEvents
0562     if generationInit:
0563       processedEvents = int(generationInit.numberOfEvents)
0564       if self.numberOfEvents < 0:
0565         processedEvents -= 1
0566         logger.debug(f"Correcting number of events to: {processedEvents}")
0567 
0568     if self.printLevel <= 3:
0569       logger.info("Total Time:   %3.2f s (User), %3.2f s (System)" %
0570                   (totalTimeUser, totalTimeSys))
0571       if processedEvents != 0:
0572         eventTime = totalTimeUser - startUpTime
0573         perEventTime = eventTime / processedEvents
0574         logger.info("StartUp Time: %3.2f s, Processing and Init: %3.2f s (~%3.2f s/Event) "
0575                     % (startUpTime, eventTime, perEventTime))
0576     return exitCode
0577 
0578   def __setMagneticFieldOptions(self, geant4):
0579     """ create and configure the magnetic tracking setup """
0580     field = geant4.addConfig('Geant4FieldTrackingSetupAction/MagFieldTrackingSetup')
0581     field.stepper = self.field.stepper
0582     field.equation = self.field.equation
0583     field.eps_min = self.field.eps_min
0584     field.eps_max = self.field.eps_max
0585     field.min_chord_step = self.field.min_chord_step
0586     field.delta_chord = self.field.delta_chord
0587     field.delta_intersection = self.field.delta_intersection
0588     field.delta_one_step = self.field.delta_one_step
0589     field.largest_step = self.field.largest_step
0590 
0591   def __checkFilesExist(self, fileNames, fileType=''):
0592     """Make sure all files in the given list exist, add to errorMessage otherwise.
0593 
0594 
0595     :param list fileNames: list of files to check for existence
0596     :param str fileType: type if file, for nicer error message
0597     """
0598     if isinstance(fileNames, str):
0599       fileNames = [fileNames]
0600     for fileName in fileNames:
0601       if not os.path.exists(fileName) and not urlparse(fileName).scheme:
0602         self._errorMessages.append(f"ERROR: The {fileType}file '{fileName}' does not exist")
0603 
0604   def __checkFileFormat(self, fileNames, extensions):
0605     """check if the fileName is allowed, note that the filenames are case
0606     sensitive, and in case of hepevt we depend on this to identify short and long versions of the content
0607     """
0608     if isinstance(fileNames, str):
0609       fileNames = [fileNames]
0610     if not all(fileName.endswith(tuple(extensions)) for fileName in fileNames):
0611       self._errorMessages.append(f"ERROR: Unknown fileformat for file(s): {','.join(fileNames)}")
0612     is_hepmc3_extension = any(fileName.endswith(tuple(HEPMC3_SUPPORTED_EXTENSIONS)) for fileName in fileNames)
0613     if not self.hepmc3.useHepMC3 and is_hepmc3_extension:
0614       self._errorMessages.append("ERROR: HepMC3 files or compressed HepMC2 require the use of HepMC3 library")
0615     return fileNames
0616 
0617   def __applyBoostOrSmear(self, kernel, actionList, mask):
0618     """apply boost or smearing for given mask index"""
0619     import DDG4
0620     if self.crossingAngleBoost:
0621       lbo = DDG4.GeneratorAction(kernel, "Geant4InteractionVertexBoost")
0622       lbo.Angle = self.crossingAngleBoost
0623       lbo.Mask = mask
0624       actionList.append(lbo)
0625 
0626     if any(self.vertexSigma) or any(self.vertexOffset):
0627       vSmear = DDG4.GeneratorAction(kernel, "Geant4InteractionVertexSmear")
0628       vSmear.Offset = self.vertexOffset
0629       vSmear.Sigma = self.vertexSigma
0630       vSmear.Mask = mask
0631       actionList.append(vSmear)
0632 
0633   def __parseAllHelper(self, parsed):
0634     """ parse all the options for the helper """
0635     parsedDict = vars(parsed)
0636     for name, obj in vars(self).items():
0637       if isinstance(obj, ConfigHelper):
0638         for var in obj.getOptions():
0639           key = "%s.%s" % (name, var)
0640           if key in parsedDict:
0641             try:
0642               obj.setOption(var, parsedDict[key])
0643             except RuntimeError as e:
0644               self._errorMessages.append("ERROR: %s " % e)
0645               if logger.level <= logging.DEBUG:
0646                 self._errorMessages.append(traceback.format_exc())
0647         obj._checkProperties()
0648 
0649   def __checkOutputLevel(self, level):
0650     """return outputlevel as int so we don't have to import anything for faster startup"""
0651     try:
0652       return outputLevel(level)
0653     except ValueError:
0654       self._errorMessages.append("ERROR: printLevel is neither integer nor string")
0655       return -1
0656     except KeyError:
0657       self._errorMessages.append("ERROR: printLevel '%s' unknown" % level)
0658       return -1
0659 
0660   def __setupSensitiveDetectors(self, detectors, setupFunction, defaultFilter=None,
0661                                 defaultAction=None, abortForMissingAction=False,
0662                                 ):
0663     """Attach sensitive detector actions for all subdetectors.
0664 
0665     Can be steered with the `Action` ConfigHelpers
0666 
0667     :param detectors: list of detectors
0668     :param setupFunction: function used to register the sensitive detector
0669     :param defaultFilter: default filter to apply for given types
0670     :param abortForMissingAction: if true end program if there is no action found
0671     """
0672     for det in detectors:
0673       logger.info('Setting up SD for %s with %s', det, defaultAction)
0674       action = None
0675       for pattern in self.action.mapActions:
0676         if pattern.lower() in det.lower():
0677           action = self.action.mapActions[pattern]
0678           logger.info('       replace default action with : %s', action)
0679           break
0680       if abortForMissingAction and action is None:
0681         logger.error('Cannot find Action for detector %s. You have to extend "action.mapAction"', det)
0682         raise RuntimeError("Cannot find Action")
0683       seq, act = setupFunction(det, action)
0684       self.filter.applyFilters(seq, det, defaultFilter)
0685 
0686       # set detailed hit creation mode for this
0687       if self.enableDetailedShowerMode:
0688         if isinstance(act, list):
0689           for a in act:
0690             a.HitCreationMode = 2
0691         else:
0692           act.HitCreationMode = 2
0693 
0694   def __printSteeringFile(self, parser):
0695     """print the parameters formated as a steering file"""
0696 
0697     steeringFileBase = textwrap.dedent("""\
0698         from DDSim.DD4hepSimulation import DD4hepSimulation
0699         from g4units import mm, GeV, MeV
0700         SIM = DD4hepSimulation()
0701         """)
0702     steeringFileBase += "\n"
0703     optionDict = parser._option_string_actions
0704     parameters = vars(self)
0705     for parName, parameter in sorted(list(parameters.items()), key=sortParameters):
0706       if parName.startswith("_"):
0707         continue
0708       if isinstance(parameter, ConfigHelper):
0709         steeringFileBase += "\n\n"
0710         steeringFileBase += "################################################################################\n"
0711         steeringFileBase += "## %s \n" % "\n## ".join(parameter.__doc__.splitlines())
0712         steeringFileBase += "################################################################################\n"
0713         options = parameter.getOptions()
0714         for opt, optionsDict in sorted(options.items(), key=sortParameters):
0715           if opt.startswith("_"):
0716             continue
0717           parValue = optionsDict['default']
0718           if isinstance(optionsDict.get('help'), str):
0719             steeringFileBase += "\n## %s\n" % "\n## ".join(optionsDict.get('help').splitlines())
0720           # add quotes if it is a string
0721           if isinstance(parValue, str):
0722             steeringFileBase += "SIM.%s.%s = \"%s\"\n" % (parName, opt, parValue)
0723           else:
0724             steeringFileBase += "SIM.%s.%s = %s\n" % (parName, opt, parValue)
0725       else:
0726         # get the docstring from the command line parameter
0727         optionObj = optionDict.get("--" + parName, None)
0728         if isinstance(optionObj, argparse._StoreAction):
0729           steeringFileBase += "## %s\n" % "\n## ".join(optionObj.help.splitlines())
0730         # add quotes if it is a string
0731         if isinstance(parameter, str):
0732           steeringFileBase += "SIM.%s = \"%s\"" % (parName, str(parameter))
0733         else:
0734           steeringFileBase += "SIM.%s = %s" % (parName, str(parameter))
0735         steeringFileBase += "\n"
0736     for line in steeringFileBase.splitlines():
0737       print(line)
0738 
0739   def _consistencyChecks(self):
0740     """Check if the requested setup makes sense, or if there is something preventing it from working correctly
0741 
0742     Appends error messages to self._errorMessages
0743 
0744     :returns: None
0745     """
0746 
0747     if not self.compactFile:
0748       self._errorMessages.append("ERROR: No geometry compact file provided")
0749 
0750     if self.runType == "batch":
0751       if not self.numberOfEvents:
0752         self._errorMessages.append("ERROR: Batch mode requested, but did not set number of events")
0753       if not any([self.inputFiles,
0754                   self.enableGun,
0755                   self.enableG4Gun,
0756                   self.enableG4GPS,
0757                   self.inputConfig.userInputPlugin]):
0758         self._errorMessages.append("ERROR: Batch mode requested, but did not set inputFile(s), gun, or userInputPlugin")
0759 
0760     if self.inputFiles and (self.enableG4Gun or self.enableG4GPS):
0761       self._errorMessages.append("ERROR: Cannot use both inputFiles and Geant4Gun or GeneralParticleSource")
0762 
0763     if self.enableGun and (self.enableG4Gun or self.enableG4GPS):
0764       self._errorMessages.append("ERROR: Cannot use both DD4hepGun and Geant4 Gun or GeneralParticleSource")
0765 
0766     if self.inputConfig.userInputPlugin and (self.enableG4Gun or self.enableG4GPS):
0767       self._errorMessages.append("ERROR: Cannot use both userInputPlugin and Geant4 Gun or GeneralParticleSource")
0768 
0769     if self.numberOfEvents < 0 and not self.inputFiles:
0770       self._errorMessages.append("ERROR: Negative number of events only sensible for inputFiles")
0771 
0772   def _enablePrimaryHandler(self):
0773     """ the geant4 Gun or GeneralParticleSource cannot be used together with the PrimaryHandler.
0774         Particles would be simulated multiple times
0775 
0776     :returns: True or False
0777     """
0778     enablePrimaryHandler = not (self.enableG4Gun or self.enableG4GPS)
0779     if enablePrimaryHandler:
0780       logger.info("Enabling the PrimaryHandler")
0781     else:
0782       logger.info("Disabling the PrimaryHandler")
0783     return enablePrimaryHandler
0784 
0785   def _buildInputStage(self, geant4, generator_input_modules, output_level=None, have_mctruth=True):
0786     """
0787     Generic build of the input stage with multiple input modules.
0788     Actions executed are:
0789     1) Register Generation initialization action
0790     2) Append all modules to build the complete input record
0791     These modules are readers/particle sources, boosters and/or smearing actions.
0792     3) Merge all existing interaction records
0793     4) Add the MC truth handler
0794     """
0795     from DDG4 import GeneratorAction
0796     ga = geant4.kernel().generatorAction()
0797 
0798     # Register Generation initialization action
0799     gen = GeneratorAction(geant4.kernel(), "Geant4GeneratorActionInit/GenerationInit")
0800     generationInit = gen
0801     if output_level is not None:
0802       gen.OutputLevel = output_level
0803     ga.adopt(gen)
0804 
0805     # Now append all modules to build the complete input record
0806     # These modules are readers/particle sources, boosters and/or smearing actions
0807     for gen in generator_input_modules:
0808       gen.enableUI()
0809       if output_level is not None:
0810         gen.OutputLevel = output_level
0811       ga.adopt(gen)
0812 
0813     # Merge all existing interaction records
0814     gen = GeneratorAction(geant4.kernel(), "Geant4InteractionMerger/InteractionMerger")
0815     gen.enableUI()
0816     if output_level is not None:
0817       gen.OutputLevel = output_level
0818     ga.adopt(gen)
0819 
0820     # Finally generate Geant4 primaries
0821     if have_mctruth:
0822       gen = GeneratorAction(geant4.kernel(), "Geant4PrimaryHandler/PrimaryHandler")
0823       gen.RejectPDGs = ConfigHelper.makeString(self.physics.rejectPDGs)
0824       gen.ZeroTimePDGs = ConfigHelper.makeString(self.physics.zeroTimePDGs)
0825       gen.DecayByGeant = ConfigHelper.makeString(self.physics.decayByGeant)
0826       gen.enableUI()
0827       if output_level is not None:
0828         gen.OutputLevel = output_level
0829       ga.adopt(gen)
0830     # Puuuhh! All done.
0831     return generationInit
0832 
0833   def __attachGDB(self):
0834     """Hook gdb to the current session. This is done by forking
0835     the current process and replacing the parent with gdb, while
0836     the child continues to run the program.
0837     """
0838 
0839     child_pid = os.fork()
0840 
0841     if child_pid == 0:
0842         # Child process: runs the actual program
0843         return
0844     else:
0845         # Parent process: becomes GDB
0846 
0847         # Shells don't like '*' in args
0848         args = [arg.replace("*", "\\*") for arg in sys.argv if arg != "--gdb"]
0849         os.execvp(
0850             "gdb",
0851             [
0852                 "gdb",
0853                 "-q",
0854                 "-p",
0855                 str(child_pid),
0856                 "-ex",
0857                 f"set args {' '.join(args)}",
0858                 ],
0859             )
0860 
0861 
0862 ################################################################################
0863 # MODULE FUNCTIONS GO HERE
0864 ################################################################################
0865 
0866 
0867 def sortParameters(key):
0868   from functools import cmp_to_key
0869 
0870   def _sortParameters(parA, parB):
0871     """sort the parameters by name: first normal parameters, then set of
0872     parameters based on ConfigHelper objects
0873     """
0874     parTypeA = parA[1]
0875     parTypeB = parB[1]
0876     if isinstance(parTypeA, ConfigHelper) and isinstance(parTypeB, ConfigHelper):
0877       return 1 if str(parA[0]) > str(parB[0]) else -1
0878     elif isinstance(parTypeA, ConfigHelper):
0879       return 1
0880     elif isinstance(parTypeB, ConfigHelper):
0881       return -1
0882     else:
0883       return 1 if str(parA[0]) > str(parB[0]) else -1
0884 
0885   return cmp_to_key(_sortParameters)(key)
0886 
0887 
0888 def getOutputLevel(level):
0889   """return output.LEVEL"""
0890   from DDG4 import OutputLevel
0891   levels = {1: OutputLevel.VERBOSE,
0892             2: OutputLevel.DEBUG,
0893             3: OutputLevel.INFO,
0894             4: OutputLevel.WARNING,
0895             5: OutputLevel.ERROR,
0896             6: OutputLevel.FATAL,
0897             7: OutputLevel.ALWAYS}
0898   return levels[level]