Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-06-14 07:49:43

0001 # ==========================================================================
0002 #  AIDA Detector description implementation
0003 # --------------------------------------------------------------------------
0004 # Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN)
0005 # All rights reserved.
0006 #
0007 # For the licensing terms see $DD4hepINSTALL/LICENSE.
0008 # For the list of contributors see $DD4hepINSTALL/doc/CREDITS.
0009 #
0010 # ==========================================================================
0011 import logging
0012 import signal
0013 import cppyy
0014 from dd4hep_base import *  # noqa: F403
0015 
0016 logger = logging.getLogger(__name__)
0017 
0018 
0019 # ---------------------------------------------------------------------------
0020 def loadDDG4():
0021   import ROOT
0022   from ROOT import gSystem
0023 
0024   # Try to load libglapi to avoid issues with TLS Static
0025   # Turn off all errors from ROOT about the library missing
0026   if 'libglapi' not in gSystem.GetLibraries():
0027     orgLevel = ROOT.gErrorIgnoreLevel
0028     ROOT.gErrorIgnoreLevel = 6000
0029     gSystem.Load("libglapi")
0030     ROOT.gErrorIgnoreLevel = orgLevel
0031 
0032   import platform
0033   import os
0034   if platform.system() == "Darwin":
0035     gSystem.SetDynamicPath(os.environ['DD4HEP_LIBRARY_PATH'])
0036     os.environ['DYLD_LIBRARY_PATH'] = os.pathsep.join([os.environ['DD4HEP_LIBRARY_PATH'],
0037                                                        os.environ.get('DYLD_LIBRARY_PATH', '')]).strip(os.pathsep)
0038 
0039   result = gSystem.Load("libDDG4Plugins")
0040   if result < 0:
0041     raise Exception('DDG4.py: Failed to load the DDG4 library libDDG4Plugins: ' + gSystem.GetErrorStr())
0042   from ROOT import dd4hep as module
0043   return module
0044 
0045 
0046 # We are nearly there ....
0047 current = __import__(__name__)
0048 
0049 
0050 # ---------------------------------------------------------------------------
0051 def _import_class(ns, nam):
0052   scope = getattr(current, ns)
0053   setattr(current, nam, getattr(scope, nam))
0054 
0055 
0056 # ---------------------------------------------------------------------------
0057 try:
0058   dd4hep = loadDDG4()
0059 except Exception as X:
0060   logger.error('+--%-100s--+', 100 * '-')
0061   logger.error('|  %-100s  |', 'Failed to load DDG4 library:')
0062   logger.error('|  %-100s  |', str(X))
0063   logger.error('+--%-100s--+', 100 * '-')
0064   exit(1)
0065 
0066 # ---------------------------------------------------------------------------
0067 from ROOT import CLHEP as CLHEP  # noqa
0068 Core = dd4hep
0069 Sim = dd4hep.sim
0070 Simulation = dd4hep.sim
0071 Kernel = Sim.KernelHandle
0072 Interface = Sim.Geant4ActionCreation
0073 Detector = Core.Detector
0074 
0075 
0076 # ---------------------------------------------------------------------------
0077 def _constant(self, name):
0078   return self.constantAsString(name)
0079 
0080 
0081 Detector.globalVal = _constant
0082 
0083 
0084 # ---------------------------------------------------------------------------
0085 def importConstants(description, namespace=None, debug=False):
0086   """
0087   Import the Detector constants into the DDG4 namespace
0088   """
0089   ns = current
0090   if namespace is not None and not hasattr(current, namespace):
0091     import types
0092     m = types.ModuleType('DDG4.' + namespace)
0093     setattr(current, namespace, m)
0094     ns = m
0095   evaluator = dd4hep.g4Evaluator()
0096   cnt = 0
0097   num = 0
0098   todo = {}
0099   strings = {}
0100   for c in description.constants():
0101     if c.second.dataType == 'string':
0102       strings[str(c.first)] = c.second.GetTitle()
0103     else:
0104       todo[str(c.first)] = c.second.GetTitle().replace('(int)', '')
0105   while len(todo) and cnt < 100:
0106     cnt = cnt + 1
0107     if cnt == 100:
0108       logger.error('%s %d out of %d %s "%s": [%s]\n+++ %s',
0109                    '+++ FAILED to import',
0110                    len(todo), len(todo) + num,
0111                    'global values into namespace',
0112                    ns.__name__, 'Try to continue anyway', 100 * '=')
0113       for k, v in todo.items():
0114         if not hasattr(ns, k):
0115           logger.error('+++ FAILED to import: "' + k + '" = "' + str(v) + '"')
0116       logger.info('+++ %s', 100 * '=')
0117 
0118     for k, v in list(todo.items()):
0119       if not hasattr(ns, k):
0120         val = evaluator.evaluate(str(v))
0121         if val.first == 0:
0122           evaluator.setVariable(str(k), val.second)
0123           setattr(ns, k, val.second)
0124           if debug:
0125             logger.info('Imported global value: "' + k + '" = "' + str(val.second) + '" into namespace' + ns.__name__)
0126           del todo[k]
0127           num = num + 1
0128   if cnt < 100:
0129     logger.info('+++ Imported %d global values to namespace:%s', num, ns.__name__,)
0130 
0131 
0132 # ---------------------------------------------------------------------------
0133 def _registerGlobalAction(self, action):
0134   self.get().registerGlobalAction(Interface.toAction(action))
0135 
0136 
0137 # ---------------------------------------------------------------------------
0138 def _registerGlobalFilter(self, filter):  # noqa: A002
0139   self.get().registerGlobalFilter(Interface.toAction(filter))
0140 
0141 
0142 # ---------------------------------------------------------------------------
0143 def _evalProperty(data):
0144   """
0145     Function necessary to extract real strings from the property value.
0146     Strings may be embraced by quotes: '<value>', or could be cppyy.gbl.std.string with extra "b''"
0147   """
0148   try:
0149     if isinstance(data, (cppyy.gbl.std.string, )):
0150       return _evalProperty(data.decode('utf-8'))
0151     if isinstance(data, str):
0152       import ast
0153       return ast.literal_eval(data)
0154   except ValueError:
0155     pass
0156   except TypeError:
0157     pass
0158   finally:
0159     pass
0160   return data
0161 
0162 
0163 # ---------------------------------------------------------------------------
0164 def _getKernelProperty(self, name):
0165   ret = Interface.getPropertyKernel(self.get(), name)
0166   if ret.status > 0:
0167     return _evalProperty(ret.data)
0168   elif hasattr(self.get(), name):
0169     return _evalProperty(getattr(self.get(), name))
0170   msg = 'Geant4Kernel::GetProperty [Unhandled]: Cannot access Kernel.' + name
0171   raise KeyError(msg)
0172 
0173 
0174 # ---------------------------------------------------------------------------
0175 def _setKernelProperty(self, name, value):
0176   if Interface.setPropertyKernel(self.get(), str(name), str(value)):
0177     return
0178   msg = 'Geant4Kernel::SetProperty [Unhandled]: Cannot set Kernel.' + name + ' = ' + str(value)
0179   raise KeyError(msg)
0180 
0181 
0182 # ---------------------------------------------------------------------------
0183 def _kernel_phase(self, name):
0184   return self.addSimplePhase(str(name), False)
0185 
0186 
0187 # ---------------------------------------------------------------------------
0188 def _kernel_worker(self):
0189   return Kernel(self.get().createWorker())
0190 
0191 
0192 # ---------------------------------------------------------------------------
0193 def _kernel_terminate(self):
0194   return self.get().terminate()
0195 
0196 
0197 Kernel.phase = _kernel_phase
0198 Kernel.registerGlobalAction = _registerGlobalAction
0199 Kernel.registerGlobalFilter = _registerGlobalFilter
0200 Kernel.createWorker = _kernel_worker
0201 Kernel.__getattr__ = _getKernelProperty
0202 Kernel.__setattr__ = _setKernelProperty
0203 Kernel.terminate = _kernel_terminate
0204 
0205 ActionHandle = Sim.ActionHandle
0206 
0207 
0208 # ---------------------------------------------------------------------------
0209 def SensitiveAction(kernel, nam, det, shared=False):
0210   return Interface.createSensitive(kernel, str(nam), str(det), shared)
0211 
0212 
0213 # ---------------------------------------------------------------------------
0214 def Action(kernel, nam, shared=False):
0215   return Interface.createAction(kernel, str(nam), shared)
0216 
0217 
0218 # ---------------------------------------------------------------------------
0219 def Filter(kernel, nam, shared=False):
0220   return Interface.createFilter(kernel, str(nam), shared)
0221 
0222 
0223 # ---------------------------------------------------------------------------
0224 def PhaseAction(kernel, nam, shared=False):
0225   return Interface.createPhaseAction(kernel, str(nam), shared)
0226 
0227 
0228 # ---------------------------------------------------------------------------
0229 def RunAction(kernel, nam, shared=False):
0230   return Interface.createRunAction(kernel, str(nam), shared)
0231 
0232 
0233 # ---------------------------------------------------------------------------
0234 def EventAction(kernel, nam, shared=False):
0235   return Interface.createEventAction(kernel, str(nam), shared)
0236 
0237 
0238 # ---------------------------------------------------------------------------
0239 def GeneratorAction(kernel, nam, shared=False):
0240   return Interface.createGeneratorAction(kernel, str(nam), shared)
0241 
0242 
0243 # ---------------------------------------------------------------------------
0244 def TrackingAction(kernel, nam, shared=False):
0245   return Interface.createTrackingAction(kernel, str(nam), shared)
0246 
0247 
0248 # ---------------------------------------------------------------------------
0249 def SteppingAction(kernel, nam, shared=False):
0250   return Interface.createSteppingAction(kernel, str(nam), shared)
0251 
0252 
0253 # ---------------------------------------------------------------------------
0254 def StackingAction(kernel, nam, shared=False):
0255   return Interface.createStackingAction(kernel, str(nam), shared)
0256 
0257 
0258 # ---------------------------------------------------------------------------
0259 def DetectorConstruction(kernel, nam):
0260   return Interface.createDetectorConstruction(kernel, str(nam))
0261 
0262 
0263 # ---------------------------------------------------------------------------
0264 def PhysicsList(kernel, nam):
0265   return Interface.createPhysicsList(kernel, str(nam))
0266 
0267 
0268 # ---------------------------------------------------------------------------
0269 def UserInitialization(kernel, nam):
0270   return Interface.createUserInitialization(kernel, str(nam))
0271 
0272 
0273 # ---------------------------------------------------------------------------
0274 def SensitiveSequence(kernel, nam):
0275   return Interface.createSensDetSequence(kernel, str(nam))
0276 
0277 
0278 # ---------------------------------------------------------------------------
0279 def _setup(obj):
0280   def _adopt(self, action):
0281     self.__adopt(action.get())
0282   _import_class('Sim', obj)
0283   o = getattr(current, obj)
0284   o.__adopt = o.adopt
0285   o.adopt = _adopt
0286   o.add = _adopt
0287 
0288 
0289 # ---------------------------------------------------------------------------
0290 def _setup_callback(obj):
0291   def _adopt(self, action):
0292     self.__adopt(action.get(), action.callback())
0293   _import_class('Sim', obj)
0294   o = getattr(current, obj)
0295   o.__adopt = o.add
0296   o.add = _adopt
0297 
0298 
0299 _setup_callback('Geant4ActionPhase')
0300 _setup('Geant4RunActionSequence')
0301 _setup('Geant4EventActionSequence')
0302 _setup('Geant4GeneratorActionSequence')
0303 _setup('Geant4TrackingActionSequence')
0304 _setup('Geant4SteppingActionSequence')
0305 _setup('Geant4StackingActionSequence')
0306 _setup('Geant4PhysicsListActionSequence')
0307 _setup('Geant4SensDetActionSequence')
0308 _setup('Geant4DetectorConstructionSequence')
0309 _setup('Geant4UserInitializationSequence')
0310 _setup('Geant4Sensitive')
0311 _setup('Geant4ParticleHandler')
0312 _import_class('Sim', 'Geant4Vertex')
0313 _import_class('Sim', 'Geant4Particle')
0314 _import_class('Sim', 'Geant4VertexVector')
0315 _import_class('Sim', 'Geant4ParticleVector')
0316 _import_class('Sim', 'Geant4Action')
0317 _import_class('Sim', 'Geant4Filter')
0318 _import_class('Sim', 'Geant4RunAction')
0319 _import_class('Sim', 'Geant4TrackingAction')
0320 _import_class('Sim', 'Geant4StackingAction')
0321 _import_class('Sim', 'Geant4PhaseAction')
0322 _import_class('Sim', 'Geant4UserParticleHandler')
0323 _import_class('Sim', 'Geant4UserInitialization')
0324 _import_class('Sim', 'Geant4DetectorConstruction')
0325 _import_class('Sim', 'Geant4GeneratorWrapper')
0326 _import_class('Sim', 'Geant4VolumeManager')
0327 _import_class('Sim', 'Geant4Random')
0328 _import_class('CLHEP', 'HepRandom')
0329 _import_class('CLHEP', 'HepRandomEngine')
0330 
0331 from ROOT import G4VPhysicsConstructor as G4VPhysicsConstructor  # noqa: F401, E402
0332 from ROOT import G4StepLimiterPhysics as StepLimiterPhysics  # noqa: F401, E402
0333 
0334 
0335 # ---------------------------------------------------------------------------
0336 def _get(self, name):
0337   a = Interface.toAction(self)
0338   ret = Interface.getProperty(a, name)
0339   if ret.status > 0:
0340     return _evalProperty(ret.data)
0341   elif hasattr(self.action, name):
0342     return _evalProperty(getattr(self.action, name))
0343   elif hasattr(a, name):
0344     return _evalProperty(getattr(a, name))
0345   msg = 'Geant4Action::GetProperty [Unhandled]: Cannot access property ' + a.name() + '.' + name
0346   raise KeyError(msg)
0347 
0348 
0349 # ---------------------------------------------------------------------------
0350 def _set(self, name, value):
0351   """This function is called when properties are passed to the c++ objects."""
0352   from dd4hep_base import unicode_2_string
0353   a = Interface.toAction(self)
0354   name = unicode_2_string(name)
0355   value = unicode_2_string(value)
0356   if Interface.setProperty(a, name, value):
0357     return
0358   msg = 'Geant4Action::SetProperty [Unhandled]: Cannot set ' + a.name() + '.' + name + ' = ' + value
0359   raise KeyError(msg)
0360 
0361 
0362 # ---------------------------------------------------------------------------
0363 def _props(obj):
0364   _import_class('Sim', obj)
0365   cl = getattr(current, obj)
0366   cl.__getattr__ = _get
0367   cl.__setattr__ = _set
0368 
0369 
0370 _props('FilterHandle')
0371 _props('ActionHandle')
0372 _props('PhaseActionHandle')
0373 _props('RunActionHandle')
0374 _props('EventActionHandle')
0375 _props('GeneratorActionHandle')
0376 _props('PhysicsListHandle')
0377 _props('TrackingActionHandle')
0378 _props('SteppingActionHandle')
0379 _props('StackingActionHandle')
0380 _props('DetectorConstructionHandle')
0381 _props('SensitiveHandle')
0382 _props('UserInitializationHandle')
0383 _props('Geant4ParticleHandler')
0384 _props('Geant4UserParticleHandler')
0385 
0386 _props('GeneratorActionSequenceHandle')
0387 _props('RunActionSequenceHandle')
0388 _props('EventActionSequenceHandle')
0389 _props('TrackingActionSequenceHandle')
0390 _props('SteppingActionSequenceHandle')
0391 _props('StackingActionSequenceHandle')
0392 _props('DetectorConstructionSequenceHandle')
0393 _props('PhysicsListActionSequenceHandle')
0394 _props('SensDetActionSequenceHandle')
0395 _props('UserInitializationSequenceHandle')
0396 
0397 _props('Geant4PhysicsListActionSequence')
0398 _import_class('Sim', 'Geant4HitData')
0399 _import_class('Sim', 'Geant4ParticleProperties')
0400 
0401 
0402 # ---------------------------------------------------------------------------
0403 #
0404 # Basic helper to configure the DDG4 instance from python
0405 #
0406 # ---------------------------------------------------------------------------
0407 class Geant4:
0408   """
0409   Helper object to perform stuff, which occurs very often.
0410   I am sick of typing the same over and over again.
0411   Hence, I grouped often used python fragments to this small
0412   class to re-usage.
0413 
0414   \author  M.Frank
0415   \version 1.0
0416   """
0417 
0418   def __init__(self, kernel=None,
0419                calo='Geant4CalorimeterAction',
0420                tracker='Geant4SimpleTrackerAction'):
0421     kernel.UI = "UI"
0422     kernel.printProperties()
0423     self._kernel = kernel
0424     if kernel is None:
0425       self._kernel = Kernel()
0426     self.description = self._kernel.detectorDescription()
0427     self.sensitive_types = {}
0428     self.sensitive_types['tracker'] = tracker
0429     self.sensitive_types['calorimeter'] = calo
0430     self.sensitive_types['escape_counter'] = 'Geant4EscapeCounter'
0431 
0432   def kernel(self):
0433     """
0434     Access the worker kernel object.
0435 
0436     \author  M.Frank
0437     """
0438     return self._kernel.worker()
0439 
0440   def master(self):
0441     """
0442     Access the master kernel object.
0443 
0444     \author  M.Frank
0445     """
0446     return self._kernel
0447 
0448   def setupUI(self, typ='csh', vis=False, ui=True, macro=None):
0449     """
0450     Configure the Geant4 command executive
0451 
0452     \author  M.Frank
0453     """
0454     ui_action = Action(self.master(), "Geant4UIManager/UI")
0455     if vis:
0456       ui_action.HaveVIS = True
0457     else:
0458       ui_action.HaveVIS = False
0459     if ui:
0460       ui_action.HaveUI = True
0461     else:
0462       ui_action.HaveUI = False
0463     ui_action.SessionType = typ
0464     if macro:
0465       ui_action.SetupUI = macro
0466     self.master().registerGlobalAction(ui_action)
0467     return ui_action
0468 
0469   def setupCshUI(self, typ='csh', vis=False, ui=True, macro=None):
0470     """
0471     Configure the Geant4 command executive with a csh like command prompt
0472 
0473     \author  M.Frank
0474     """
0475     return self.setupUI(typ='csh', vis=vis, ui=ui, macro=macro)
0476 
0477   def ui(self):
0478     """
0479     Access UI manager action from the kernel object
0480 
0481     \author  M.Frank
0482     """
0483     # calls __getattr__ implicitly, which calls getKernelProperty
0484     ui_name = self.master().UI
0485     return self.master().globalAction(ui_name)
0486 
0487   def registerInterruptHandler(self, signum=signal.SIGINT):
0488     """
0489     Enable interrupt handling: smooth handling of CTRL-C
0490       - Finish processing of the current event(s)
0491       - Drain the event loop
0492       - Properly finalize the job
0493 
0494     \author  M.Frank
0495     """
0496     return self.master().registerInterruptHandler(signum)
0497 
0498   def addUserInitialization(self, worker, worker_args=None, master=None, master_args=None):
0499     """
0500     Configure Geant4 user initialization for optionasl multi-threading mode
0501 
0502     \author  M.Frank
0503     """
0504     init_seq = self.master().userInitialization(True)
0505     init_action = UserInitialization(self.master(), 'Geant4PythonInitialization/PyG4Init')
0506     #
0507     if worker:
0508       init_action.setWorkerSetup(worker, worker_args)
0509     else:
0510       raise RuntimeError('Invalid argument for Geant4 worker initialization')
0511     #
0512     if master:
0513       init_action.setMasterSetup(master, master_args)
0514     #
0515     init_seq.adopt(init_action)
0516     return init_seq, init_action
0517 
0518   def detectorConstruction(self):
0519     seq = self.master().detectorConstruction(True)
0520     return seq
0521 
0522   def addDetectorConstruction(self, name_type,
0523                               field=None, field_args=None,
0524                               geometry=None, geometry_args=None,
0525                               sensitives=None, sensitives_args=None,
0526                               allow_threads=False):
0527     """
0528     Configure Geant4 user initialization for optional multi-threading mode
0529 
0530     \author  M.Frank
0531     """
0532     init_seq = self.master().detectorConstruction(True)
0533     init_action = DetectorConstruction(self.master(), name_type)
0534     #
0535     if geometry:
0536       init_action.setConstructGeo(geometry, geometry_args)
0537     #
0538     if field:
0539       init_action.setConstructField(field, field_args)
0540     #
0541     if sensitives:
0542       init_action.setConstructSensitives(sensitives, sensitives_args)
0543     #
0544     init_seq.adopt(init_action)
0545     if allow_threads:
0546       last_action = DetectorConstruction(self.master(), "Geant4PythonDetectorConstructionLast/LastDetectorAction")
0547       init_seq.adopt(last_action)
0548 
0549     return init_seq, init_action
0550 
0551   def addPhaseAction(self, phase_name, factory_specification, ui=True, instance=None):
0552     """
0553     Add a new phase action to an arbitrary step.
0554 
0555     \author  M.Frank
0556     """
0557     if instance is None:
0558       instance = self.kernel()
0559     action = PhaseAction(instance, factory_specification)
0560     instance.phase(phase_name).add(action)
0561     if ui:
0562       action.enableUI()
0563     return action
0564 
0565   def addConfig(self, factory_specification):
0566     """
0567     Add a new phase action to the 'configure' step.
0568     Called at the beginning of Geant4Exec::configure.
0569     The factory specification is the typical string "<factory_name>/<instance name>".
0570     If no instance name is specified it defaults to the factory name.
0571 
0572     \author  M.Frank
0573     """
0574     return self.addPhaseAction('configure', factory_specification, instance=self.master())
0575 
0576   def addInit(self, factory_specification):
0577     """
0578     Add a new phase action to the 'initialize' step.
0579     Called at the beginning of Geant4Exec::initialize.
0580     The factory specification is the typical string "<factory_name>/<instance name>".
0581     If no instance name is specified it defaults to the factory name.
0582 
0583     \author  M.Frank
0584     """
0585     return self.addPhaseAction('initialize', factory_specification)
0586 
0587   def addStart(self, factory_specification):
0588     """
0589     Add a new phase action to the 'start' step.
0590     Called at the beginning of Geant4Exec::run.
0591     The factory specification is the typical string "<factory_name>/<instance name>".
0592     If no instance name is specified it defaults to the factory name.
0593 
0594     \author  M.Frank
0595     """
0596     return self.addPhaseAction('start', factory_specification)
0597 
0598   def addStop(self, factory_specification):
0599     """
0600     Add a new phase action to the 'stop' step.
0601     Called at the end of Geant4Exec::run.
0602     The factory specification is the typical string "<factory_name>/<instance name>".
0603     If no instance name is specified it defaults to the factory name.
0604 
0605     \author  M.Frank
0606     """
0607     return self.addPhaseAction('stop', factory_specification)
0608 
0609   def execute(self, num_events=None):
0610     """
0611     Execute the Geant 4 program with all steps.
0612 
0613     \author  M.Frank
0614     """
0615     self.kernel().configure()
0616     self.kernel().initialize()
0617     if num_events:
0618       self.kernel().NumEvents = num_events
0619     self.kernel().run()
0620     self.kernel().terminate()
0621     return self
0622 
0623   def printDetectors(self, **kwargs):
0624     """
0625     Scan the list of detectors and print detector name and sensitive type
0626 
0627     \author  M.Frank
0628     """
0629     print_path = kwargs.get('print_path')
0630     sensitive = kwargs.get('sensitive') or True
0631     non_sensitive = kwargs.get('non_sensitive')
0632     if sensitive:
0633       logger.info('+++  List of sensitive detectors:')
0634       for i in self.description.detectors():
0635         o = DetElement(i.second.ptr())  # noqa: F405
0636         sd = self.description.sensitiveDetector(str(o.name()))
0637         if sd.isValid():
0638           path = ''
0639           typ = sd.type()
0640           sdtyp = 'Unknown'
0641           if typ in self.sensitive_types:
0642             sdtyp = self.sensitive_types[typ]
0643           if print_path:
0644             path = o.path()
0645           logger.info('+++  %-32s type:%-12s  --> Sensitive type: %-30s  %s', o.name(), typ, sdtyp, path)
0646     if non_sensitive:
0647       logger.info('+++  List of not sensitive detector elements:')
0648       for i in self.description.detectors():
0649         o = DetElement(i.second.ptr())  # noqa: F405
0650         sd = self.description.sensitiveDetector(str(o.name()))
0651         if not sd.isValid():
0652           path = ''
0653           if print_path:
0654             path = o.path()
0655           logger.info('+++  %-32s type:non-sensitive detector %42s %s', o.name(), '', path)
0656 
0657   def setupDetectors(self, **kwargs):
0658     """
0659     Scan the list of detectors and assign the proper sensitive actions
0660 
0661     \author  M.Frank
0662     """
0663     seq = None
0664     actions = []
0665     debug_volid = kwargs.get('debug_volid')
0666     logger.info('+++  Setting up sensitive detectors:')
0667     for i in self.description.detectors():
0668       o = DetElement(i.second.ptr())  # noqa: F405
0669       sd = self.description.sensitiveDetector(str(o.name()))
0670       if sd.isValid():
0671         typ = sd.type()
0672         sdtyp = 'Unknown'
0673         if typ in self.sensitive_types:
0674           sdtyp = self.sensitive_types[typ]
0675           seq, act = self.setupDetector(o.name(), sdtyp, collections=None, debug_volid=debug_volid)
0676           logger.info('+++  %-32s type:%-12s  --> Sensitive type: %s', o.name(), typ, sdtyp)
0677           actions.append(act)
0678           continue
0679         logger.info('+++  %-32s --> UNKNOWN Sensitive type: %s', o.name(), typ)
0680     return (seq, actions)
0681 
0682   def setupDetector(self, name, action, collections=None, debug_volid=False):
0683     """
0684     Setup single subdetector and assign the proper sensitive action
0685 
0686     \author  M.Frank
0687     """
0688     # fg: allow the action to be a tuple with parameter dictionary
0689     sensitive_type = ""
0690     parameterDict = {}
0691     if isinstance(action, tuple) or isinstance(action, list):
0692       sensitive_type = action[0]
0693       parameterDict = action[1]
0694     else:
0695       sensitive_type = action
0696 
0697     seq = SensitiveSequence(self.kernel(), 'Geant4SensDetActionSequence/' + name)
0698     seq.enableUI()
0699     acts = []
0700     if collections is None:
0701       sd = self.description.sensitiveDetector(str(name))
0702       ro = sd.readout()
0703       collections = ro.collectionNames()
0704       if len(collections) == 0:
0705         act = SensitiveAction(self.kernel(), sensitive_type + '/' + name + 'Handler', name)
0706         if debug_volid:
0707           act.DebugVolumeID = debug_volid
0708         for parameter, value in parameterDict.items():
0709           setattr(act, parameter, value)
0710         acts.append(act)
0711 
0712     # Work down the collections if present
0713     if collections is not None:
0714       for coll in collections:
0715         params = {}
0716         if isinstance(coll, tuple) or isinstance(coll, list):
0717           if len(coll) > 2:
0718             coll_nam = str(coll[0])
0719             sensitive_type = coll[1]
0720             params = str(coll[2])
0721           elif len(coll) > 1:
0722             coll_nam = str(coll[0])
0723             sensitive_type = coll[1]
0724           else:
0725             coll_nam = str(coll[0])
0726         else:
0727           coll_nam = str(coll)
0728         act = SensitiveAction(self.kernel(), sensitive_type + '/' + coll_nam + 'Handler', name)
0729         act.CollectionName = coll_nam
0730         for parameter, value in params.items():
0731           setattr(act, parameter, value)
0732         acts.append(act)
0733 
0734     for act in acts:
0735       act.enableUI()
0736       seq.add(act)
0737     if len(acts) > 1:
0738       return (seq, acts)
0739     return (seq, acts[0])
0740 
0741   def setupCalorimeter(self, name, type=None, collections=None, debug_volid=False):  # noqa: A002
0742     """
0743     Setup subdetector of type 'calorimeter' and assign the proper sensitive action
0744 
0745     \author  M.Frank
0746     """
0747     typ = type    # noqa: A002
0748     self.description.sensitiveDetector(str(name))
0749     # sd.setType('calorimeter')
0750     if typ is None:
0751       typ = self.sensitive_types['calorimeter']
0752     return self.setupDetector(name, typ, collections=collections, debug_volid=debug_volid)
0753 
0754   def setupTracker(self, name, type=None, collections=None, debug_volid=False):  # noqa: A002
0755     """
0756     Setup subdetector of type 'tracker' and assign the proper sensitive action
0757 
0758     \author  M.Frank
0759     """
0760     typ = type
0761     self.description.sensitiveDetector(str(name))
0762     # sd.setType('tracker')
0763     if typ is None:
0764       typ = self.sensitive_types['tracker']
0765     return self.setupDetector(name, typ, collections=collections, debug_volid=debug_volid)
0766 
0767   def _private_setupField(self, field, stepper, equation, prt):
0768     import g4units
0769     field.stepper = stepper
0770     field.equation = equation
0771     field.eps_min = 5e-05
0772     field.eps_max = 0.001
0773     field.min_chord_step = 0.01 * g4units.mm
0774     field.delta_chord = 0.25 * g4units.mm
0775     field.delta_intersection = 0.001 * g4units.mm
0776     field.delta_one_step = 0.01 * g4units.mm
0777     field.largest_step = 1000 * g4units.m
0778     if prt:
0779       logger.info('+++++> %s %s %s %s ', field.name, '-> stepper  = ', str(field.stepper), '')
0780       logger.info('+++++> %s %s %s %s ', field.name, '-> equation = ', str(field.equation), '')
0781       logger.info('+++++> %s %s %s %s ', field.name, '-> eps_min  = ', str(field.eps_min), '')
0782       logger.info('+++++> %s %s %s %s ', field.name, '-> eps_max  = ', str(field.eps_max), '')
0783       logger.info('+++++> %s %s %s %s ', field.name, '-> delta_chord        = ', str(field.delta_chord), '[mm]')
0784       logger.info('+++++> %s %s %s %s ', field.name, '-> min_chord_step     = ', str(field.min_chord_step), '[mm]')
0785       logger.info('+++++> %s %s %s %s ', field.name, '-> delta_one_step     = ', str(field.delta_one_step), '[mm]')
0786       logger.info('+++++> %s %s %s %s ', field.name, '-> delta_intersection = ', str(field.delta_intersection), '[mm]')
0787       logger.info('+++++> %s %s %s %s ', field.name, '-> largest_step       = ', str(field.largest_step), '[mm]')
0788     return field
0789 
0790   def setupTrackingFieldMT(self, name='MagFieldTrackingSetup',
0791                            stepper='ClassicalRK4', equation='Mag_UsualEqRhs', prt=False):
0792     seq, fld = self.addDetectorConstruction("Geant4FieldTrackingConstruction/" + name)
0793     self._private_setupField(fld, stepper, equation, prt)
0794     return (seq, fld)
0795 
0796   def setupTrackingField(self, name='MagFieldTrackingSetup',
0797                          stepper='ClassicalRK4', equation='Mag_UsualEqRhs', prt=False):
0798     field = self.addConfig('Geant4FieldTrackingSetupAction/' + name)
0799     self._private_setupField(field, stepper, equation, prt)
0800     return field
0801 
0802   # Create the master physics list
0803   def setupPhysics(self, name):
0804     phys = self.master().physicsList()
0805     phys.extends = name
0806     phys.decays = True
0807     phys.enableUI()
0808     phys.dump()
0809     return phys
0810 
0811   # Add a sub-physics list to the master physics list
0812   def addPhysics(self, name):
0813     phys = self.master().physicsList()
0814     opt = PhysicsList(self.master(), name)
0815     opt.enableUI()
0816     phys.adopt(opt)
0817     return opt
0818 
0819   def setupGun(self, name, particle, energy, typ="Geant4ParticleGun", isotrop=True,
0820                multiplicity=1, position=(0.0, 0.0, 0.0), register=True, **args):
0821     gun = GeneratorAction(self.kernel(), typ + "/" + name, True)
0822     for i in args.items():
0823       setattr(gun, i[0], i[1])
0824     gun.Energy = energy
0825     gun.particle = particle
0826     gun.multiplicity = multiplicity
0827     gun.position = position
0828     gun.isotrop = isotrop
0829     gun.enableUI()
0830     if register:
0831       self.kernel().generatorAction().add(gun)
0832     return gun
0833 
0834   def setupROOTOutput(self, name, output, mc_truth=True):
0835     """
0836     Configure ROOT output for the simulated events
0837 
0838     \author  M.Frank
0839     """
0840     evt_root = EventAction(self.kernel(), 'Geant4Output2ROOT/' + name, True)
0841     evt_root.HandleMCTruth = mc_truth
0842     evt_root.Control = True
0843     if not output.endswith('.root'):
0844       output = output + '.root'
0845     evt_root.Output = output
0846     evt_root.enableUI()
0847     self.kernel().eventAction().add(evt_root)
0848     return evt_root
0849 
0850   def setupLCIOOutput(self, name, output):
0851     """
0852     Configure LCIO output for the simulated events
0853 
0854     \author  M.Frank
0855     """
0856     evt_lcio = EventAction(self.kernel(), 'Geant4Output2LCIO/' + name, True)
0857     evt_lcio.Control = True
0858     evt_lcio.Output = output
0859     evt_lcio.enableUI()
0860     self.kernel().eventAction().add(evt_lcio)
0861     return evt_lcio
0862 
0863   def setupEDM4hepOutput(self, name, output):
0864     """Configure EDM4hep root output for the simulated events."""
0865     evt_edm4hep = EventAction(self.kernel(), 'Geant4Output2EDM4hep/' + name, True)
0866     evt_edm4hep.Control = True
0867     evt_edm4hep.Output = output
0868     evt_edm4hep.enableUI()
0869     self.kernel().eventAction().add(evt_edm4hep)
0870     return evt_edm4hep
0871 
0872   def buildInputStage(self, generator_input_modules, output_level=None, have_mctruth=True):
0873     """
0874     Generic build of the input stage with multiple input modules.
0875 
0876     Actions executed are:
0877     1) Register Generation initialization action
0878     2) Append all modules to build the complete input record
0879     These modules are readers/particle sources, boosters and/or smearing actions.
0880     3) Merge all existing interaction records
0881     4) Add the MC truth handler
0882 
0883     \author  M.Frank
0884     """
0885     ga = self.kernel().generatorAction()
0886     # Register Generation initialization action
0887     gen = GeneratorAction(self.kernel(), "Geant4GeneratorActionInit/GenerationInit")
0888     if output_level is not None:
0889       gen.OutputLevel = output_level
0890     ga.adopt(gen)
0891 
0892     # Now append all modules to build the complete input record
0893     # These modules are readers/particle sources, boosters and/or smearing actions
0894     for gen in generator_input_modules:
0895       gen.enableUI()
0896       if output_level is not None:
0897         gen.OutputLevel = output_level
0898       ga.adopt(gen)
0899 
0900     # Merge all existing interaction records
0901     gen = GeneratorAction(self.kernel(), "Geant4InteractionMerger/InteractionMerger")
0902     gen.enableUI()
0903     if output_level is not None:
0904       gen.OutputLevel = output_level
0905     ga.adopt(gen)
0906 
0907     # Finally generate Geant4 primaries
0908     if have_mctruth:
0909       gen = GeneratorAction(self.kernel(), "Geant4PrimaryHandler/PrimaryHandler")
0910       gen.RejectPDGs = "{1,2,3,4,5,6,21,23,24}"
0911       gen.enableUI()
0912       if output_level is not None:
0913         gen.OutputLevel = output_level
0914       ga.adopt(gen)
0915     # Puuuhh! All done.
0916     return self
0917 
0918   def run(self):
0919     """
0920     Execute the main Geant4 action
0921     \author  M.Frank
0922     """
0923     from ROOT import PyDDG4
0924     PyDDG4.run(self.master().get())
0925     return self
0926 
0927 
0928 # ---------------------------------------------------------------------------
0929 # Instantiate convenience python interface to DDG4 C++ classes
0930 Simple = Geant4
0931 
0932 
0933 # ---------------------------------------------------------------------------
0934 def import_geant4_class(class_name, header=None):
0935   try:
0936     from ROOT import gInterpreter
0937     if not header:
0938       header = class_name + '.hh'
0939     ret = gInterpreter.ProcessLine(f'#include <{header}>')
0940     if 0 == ret:
0941       g4_class = getattr(ROOT, class_name)  # noqa: F405
0942       if g4_class:
0943         logger.warning(f'+++ Successfully imported Geant4 class {class_name} from header {class_name}.hh')
0944         return g4_class
0945   except Exception:
0946     pass
0947   logger.error(f'+++ FAILED to import class Geant4 class {class_name}')
0948   return None
0949 
0950 
0951 # ---------------------------------------------------------------------------
0952 class Geant4_class_loader:
0953   all_classes = {}
0954 
0955   def __init__(self):
0956     self.gbl = globals()
0957 
0958   def get_class(self, name, header=None):
0959     clazz = Geant4_class_loader.all_classes.get(name)
0960     if not clazz:
0961       clazz = import_geant4_class(class_name=name, header=header)
0962       if clazz:
0963         Geant4_class_loader.all_classes[name] = clazz
0964     return clazz
0965 
0966   def __getattr__(self, name):
0967     return self.get_class(name)
0968 
0969 
0970 # ---------------------------------------------------------------------------
0971 # Instantiate python interface to Geant4 C++ classes
0972 geant4 = Geant4_class_loader()