Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:14:22

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