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
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
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
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
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"
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
0226 ConfigHelper.addAllHelper(self, parser)
0227
0228
0229
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:
0262 logger.setLevel(logging.DEBUG)
0263
0264
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
0329
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
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
0361 self.__setMagneticFieldOptions(geant4)
0362
0363
0364 self.geometry.constructGeometry(kernel, geant4, self.output.geometry)
0365
0366
0367
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
0382 run1 = DDG4.RunAction(kernel, 'Geant4TestRunAction/RunInit')
0383 kernel.registerGlobalAction(run1)
0384 kernel.runAction().add(run1)
0385
0386
0387 self.random.initialize(DDG4, kernel, self.output.random)
0388
0389
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
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
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
0456 gen = DDG4.GeneratorAction(kernel, "Geant4InputAction/EDM4hep%d" % index)
0457 gen.Input = "EDM4hepFileReader|" + inputFile
0458 else:
0459
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
0476 part = DDG4.GeneratorAction(kernel, "Geant4ParticleHandler/ParticleHandler")
0477 kernel.generatorAction().adopt(part)
0478
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
0496 try:
0497 self.filter.setupFilters(kernel)
0498 except RuntimeError as e:
0499 logger.error("%s", e)
0500 return 1
0501
0502
0503
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
0519 _phys = self.physics.setupPhysics(kernel, name=self.physicsList)
0520 _phys.verbosity = self.output.physics
0521
0522
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
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
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
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
0715 optionObj = optionDict.get("--" + parName, None)
0716 if isinstance(optionObj, argparse._StoreAction):
0717 steeringFileBase += "## %s\n" % "\n## ".join(optionObj.help.splitlines())
0718
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
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
0790
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
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
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
0814 return generationInit
0815
0816
0817
0818
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]