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
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
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
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
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"
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
0233 ConfigHelper.addAllHelper(self, parser)
0234
0235
0236
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:
0270 logger.setLevel(logging.DEBUG)
0271
0272
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
0340
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
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
0372 self.__setMagneticFieldOptions(geant4)
0373
0374
0375 self.geometry.constructGeometry(kernel, geant4, self.output.geometry)
0376
0377
0378
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
0393 run1 = DDG4.RunAction(kernel, 'Geant4TestRunAction/RunInit')
0394 kernel.registerGlobalAction(run1)
0395 kernel.runAction().add(run1)
0396
0397
0398 self.random.initialize(DDG4, kernel, self.output.random)
0399
0400
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
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
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
0467 gen = DDG4.GeneratorAction(kernel, "Geant4InputAction/EDM4hep%d" % index)
0468 gen.Parameters = self.edm4hep.getParameters()
0469 gen.Input = "EDM4hepFileReader|" + inputFile
0470 else:
0471
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
0488 part = DDG4.GeneratorAction(kernel, "Geant4ParticleHandler/ParticleHandler")
0489 kernel.generatorAction().adopt(part)
0490
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
0508 try:
0509 self.filter.setupFilters(kernel)
0510 except RuntimeError as e:
0511 logger.error("%s", e)
0512 return 1
0513
0514
0515
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
0531 _phys = self.physics.setupPhysics(kernel, name=self.physicsList)
0532 _phys.verbosity = self.output.physics
0533
0534
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
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
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
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
0727 optionObj = optionDict.get("--" + parName, None)
0728 if isinstance(optionObj, argparse._StoreAction):
0729 steeringFileBase += "## %s\n" % "\n## ".join(optionObj.help.splitlines())
0730
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
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
0806
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
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
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
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
0843 return
0844 else:
0845
0846
0847
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
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]