Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-05 08:14:13

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