From 413823e63f09a6f55c1bbd75ebd0a4386b38aa76 Mon Sep 17 00:00:00 2001
From: Andre Sailer <andre.philippe.sailer@cern.ch>
Date: Tue, 6 Oct 2015 16:26:30 +0000
Subject: [PATCH] Refactor and improve features for ddsim

* Move DD4hepSimulation et al into python module DDSim, needed for proper
  importing

* Can now control magneticfield setup, gun, particle handler, output via command
  line.

** added ConfigHelper and daughter classes implementing these options
   DDSim/DDSim/Helper, easily extensible to additional features

** also easier changing of these parameters via steering file See
   example/steeringFile.py

*** No more functions, no more "global GeV"

** --help string can be given by using @property, see e.g. Helper/Output.py

* added optional argcomplete, allows command line "tab"complete for all command line options (and values!)
  Needs argcomplete (sudo pip install argcomplete), bash completion and bash (or zsh).
  and 'eval "$(register-python-argcomplete ddsim)"'

* move import statements to reduce startup time until "--help", mandatory to
  reduce latency for argcomplete
---
 .../DD4hepSimulation.py                       | 235 +++++++++++-------
 DDSim/Helper/ConfigHelper.py                  |  45 ++++
 DDSim/Helper/Gun.py                           |  17 ++
 DDSim/Helper/MagneticField.py                 |  16 ++
 DDSim/Helper/Output.py                        |  36 +++
 DDSim/Helper/ParticleHandler.py               |  54 ++++
 DDSim/Helper/__init__.py                      |   1 +
 DDSim/__init__.py                             |   1 +
 ddsim                                         |   2 +-
 9 files changed, 319 insertions(+), 88 deletions(-)
 rename DD4hepSimulation.py => DDSim/DD4hepSimulation.py (74%)
 create mode 100644 DDSim/Helper/ConfigHelper.py
 create mode 100644 DDSim/Helper/Gun.py
 create mode 100644 DDSim/Helper/MagneticField.py
 create mode 100644 DDSim/Helper/Output.py
 create mode 100644 DDSim/Helper/ParticleHandler.py
 create mode 100644 DDSim/Helper/__init__.py
 create mode 100644 DDSim/__init__.py

diff --git a/DD4hepSimulation.py b/DDSim/DD4hepSimulation.py
similarity index 74%
rename from DD4hepSimulation.py
rename to DDSim/DD4hepSimulation.py
index d89930f3c..656d4ce98 100644
--- a/DD4hepSimulation.py
+++ b/DDSim/DD4hepSimulation.py
@@ -7,17 +7,39 @@ Based on M. Frank and F. Gaede runSim.py
 
 """
 __RCSID__ = "$Id$"
-import ROOT
-ROOT.PyConfig.IgnoreCommandLineOptions = True
-
-import DDG4, DD4hep
-from DDG4 import OutputLevel as Output
 from SystemOfUnits import *
 import argparse
-
+try:
+  import argcomplete
+  ARGCOMPLETEENABLED=True
+except ImportError:
+  ARGCOMPLETEENABLED=False
+
+def outputLevel( level ):
+  """return INT for outputlevel"""
+  if isinstance(level, int):
+    if level < 1 or 7 < level:
+      raise KeyError
+    return level
+  outputlevels = { "VERBOSE": 1,
+                   "DEBUG":   2,
+                   "INFO":    3,
+                   "WARNING": 4,
+                   "ERROR":   5,
+                   "FATAL":   6,
+                   "ALWAYS":  7 }
+  return outputlevels[level.upper()]
+
+
+from DDSim.Helper.Gun import Gun
+from DDSim.Helper.ParticleHandler import ParticleHandler
+from DDSim.Helper.Output import Output
+from DDSim.Helper.MagneticField import MagneticField
+from DDSim.Helper.ConfigHelper import ConfigHelper
 import os
 import sys
 
+
 class DD4hepSimulation(object):
   """Class to hold all the parameters and functions to run simulation"""
 
@@ -26,14 +48,14 @@ class DD4hepSimulation(object):
     self.inputFiles = []
     self.outputFile = "dummyOutput.slcio"
     self.runType = "batch"
-    self.printLevel = Output.INFO
+    self.printLevel = 3
 
     self.numberOfEvents = 0
     self.skipNEvents = 0
     self.physicsList = "FTFP_BERT"
     self.crossingAngleBoost = 0.0
     self.macroFile = ''
-    self.gun = False
+    self.enableGun = False
     self.vertexSigma = [0.0, 0.0, 0.0, 0.0]
     self.vertexOffset = [0.0, 0.0, 0.0, 0.0]
     self.magneticFieldDict = {}
@@ -41,37 +63,15 @@ class DD4hepSimulation(object):
 
     self.errorMessages = []
 
+    ## dummy objects for extended configuration option
+    self.output = Output()
+    self.gun = Gun()
+    self.part = ParticleHandler()
+    self.field = MagneticField()
+
     ### use TCSH geant UI instead of QT
     os.environ['G4UI_USE_TCSH'] = "1"
 
-  def getOutputLevel(self, level):
-    """return output.LEVEL"""
-    try:
-      level = int(level)
-      levels = { 1: Output.VERBOSE,
-                 2: Output.DEBUG,
-                 3: Output.INFO,
-                 4: Output.WARNING,
-                 5: Output.ERROR,
-                 6: Output.FATAL,
-                 7: Output.ALWAYS }
-      return levels[level]
-    except ValueError:
-      try:
-        levels = { "VERBOSE": Output.VERBOSE,
-                   "DEBUG": Output.DEBUG,
-                   "INFO": Output.INFO,
-                   "WARNING": Output.WARNING,
-                   "ERROR": Output.ERROR,
-                   "FATAL": Output.FATAL,
-                   "ALWAYS": Output.ALWAYS }
-        return levels[level.upper()]
-      except ValueError:
-        self.errorMessages.append( "ERROR: printLevel is neither integer nor string" )
-        return -1
-    except KeyError:
-      self.errorMessages.append( "ERROR: printLevel '%s' unknown" % level )
-      return -1
 
   def readSteeringFile(self, steeringFile):
     """Reads a steering file and sets the parameters to that of the
@@ -116,6 +116,7 @@ class DD4hepSimulation(object):
                         help="Outputfile from the simulation,only lcio output is supported")
 
     parser.add_argument("-v", "--printLevel", action="store", default=self.printLevel, dest="printLevel",
+                        choices=(1,2,3,4,5,6,7,'VERBOSE','DEBUG', 'INFO', 'WARNING', 'ERROR', 'FATAL', 'ALWAYS'),
                         help="Verbosity use integers from 1(most) to 7(least) verbose"
                         "\nor strings: VERBOSE, DEBUG, INFO, WARNING, ERROR, FATAL, ALWAYS")
 
@@ -131,24 +132,30 @@ class DD4hepSimulation(object):
     parser.add_argument("--crossingAngleBoost", action="store", dest="crossingAngleBoost", default=self.crossingAngleBoost,
                         type=float, help="Lorentz boost for the crossing angle, in radian!")
 
-    parser.add_argument("--vertexSigma", nargs=4, action="store", dest="vertexSigma", default=self.vertexSigma,
+    parser.add_argument("--vertexSigma", nargs=4, action="store", dest="vertexSigma", default=self.vertexSigma, metavar = ('X','Y','Z','T'),
                         type=float, help="FourVector of the Sigma for the Smearing of the Vertex position: x y z t")
 
-    parser.add_argument("--vertexOffset", nargs=4, action="store", dest="vertexOffset", default=self.vertexOffset,
+    parser.add_argument("--vertexOffset", nargs=4, action="store", dest="vertexOffset", default=self.vertexOffset, metavar = ('X','Y','Z','T'),
                         type=float, help="FourVector of translation for the Smearing of the Vertex position: x y z t")
 
     parser.add_argument("--macroFile", "-M", action="store", dest="macroFile", default=self.macroFile,
                         help="Macro file to execute for runType 'run' or 'vis'")
 
-    parser.add_argument("--enableGun", "-G", action="store_true", dest="gun", default=self.gun,
+    parser.add_argument("--enableGun", "-G", action="store_true", dest="enableGun", default=self.enableGun,
                         help="enable the DDG4 particle gun")
 
     parser.add_argument("--enableDetailedShowerMode", action="store_true", dest="detailedShowerMode", default=self.detailedShowerMode,
                         help="use detailed shower mode")
 
+    #FIXME: Add all the things here, then they will show up in the help usage
+    #output, or do something smarter with fullHelp only for example
+    self.__addAllHelper( parser )
     ## now parse everything. The default values are now taken from the
     ## steeringFile if they were set so that the steering file parameters can be
     ## overwritten from the command line
+    #parsed = parser.parse_args()
+    if ARGCOMPLETEENABLED:
+      argcomplete.autocomplete(parser)
     parsed = parser.parse_args()
 
     self.compactFile = parsed.compactFile
@@ -157,14 +164,14 @@ class DD4hepSimulation(object):
     self.outputFile = parsed.outputFile
     self.__checkFileFormat( self.outputFile, ('.root', '.slcio'))
     self.runType = parsed.runType
-    self.printLevel = self.getOutputLevel(parsed.printLevel)
+    self.printLevel = self.__checkOutputLevel(parsed.printLevel)
 
     self.numberOfEvents = parsed.numberOfEvents
     self.skipNEvents = parsed.skipNEvents
     self.physicsList = parsed.physicsList
     self.crossingAngleBoost = parsed.crossingAngleBoost
     self.macroFile = parsed.macroFile
-    self.gun = parsed.gun
+    self.enableGun = parsed.enableGun
     self.detailedShowerMode = parsed.detailedShowerMode
     self.vertexOffset = parsed.vertexOffset
     self.vertexSigma = parsed.vertexSigma
@@ -175,43 +182,21 @@ class DD4hepSimulation(object):
     if self.runType == "batch":
       if not self.numberOfEvents:
         self.errorMessages.append("ERROR: Batch mode requested, but did not set number of events")
-      if not self.inputFiles and not self.gun:
+      if not self.inputFiles and not self.enableGun:
         self.errorMessages.append("ERROR: Batch mode requested, but did not set inputFile(s) or gun")
 
+    #self.__treatUnknownArgs( parsed, unknown )
+    self.__parseAllHelper( parsed )
+    #exit(1)
     if self.errorMessages:
       parser.epilog = "\n".join(self.errorMessages)
       parser.print_help()
       exit(1)
 
-  def setupMagneticField(self, simple):
-    self.magneticFieldSetup()
-    field = simple.addConfig('Geant4FieldTrackingSetupAction/MagFieldTrackingSetup')
-    field.stepper            = self.magneticFieldDict['field.stepper']
-    field.equation           = self.magneticFieldDict['field.equation']
-    field.eps_min            = self.magneticFieldDict['field.eps_min']
-    field.eps_max            = self.magneticFieldDict['field.eps_max']
-    field.min_chord_step     = self.magneticFieldDict['field.min_chord_step']
-    field.delta_chord        = self.magneticFieldDict['field.delta_chord']
-    field.delta_intersection = self.magneticFieldDict['field.delta_intersection']
-    field.delta_one_step     = self.magneticFieldDict['field.delta_one_step']
-
-
-  def magneticFieldSetup(self):
-    """options for the magneticFieldStepper"""
-
-    #---- B field stepping -------
-    self.magneticFieldDict['field.stepper']            = "HelixSimpleRunge"
-    self.magneticFieldDict['field.equation']           = "Mag_UsualEqRhs"
-    self.magneticFieldDict['field.eps_min']            = 5e-05*mm
-    self.magneticFieldDict['field.eps_max']            = 0.001*mm
-    self.magneticFieldDict['field.min_chord_step']     = 0.01*mm
-    self.magneticFieldDict['field.delta_chord']        = 0.25*mm
-    self.magneticFieldDict['field.delta_intersection'] = 1e-05*mm
-    self.magneticFieldDict['field.delta_one_step']     = 1e-04*mm
-
   @staticmethod
   def getDetectorLists( lcdd ):
     ''' get lists of trackers and calorimeters that are defined in lcdd (the compact xml file)'''
+    import DDG4
   #  if len(detectorList):
   #    print " subset list of detectors given - will only instantiate these: " , detectorList
     trackers,calos = [],[]
@@ -235,6 +220,12 @@ class DD4hepSimulation(object):
 
   def run(self):
     """setup the geometry and dd4hep and geant4 and do what was asked to be done"""
+    import ROOT
+    ROOT.PyConfig.IgnoreCommandLineOptions = True
+
+    import DDG4, DD4hep
+
+    self.printLevel = getOutputLevel(self.printLevel)
 
     kernel = DDG4.Kernel()
     DD4hep.setPrintLevel(self.printLevel)
@@ -270,7 +261,7 @@ class DD4hepSimulation(object):
 
     #-----------------------------------------------------------------------------------
     # setup the magnetic field:
-    self.setupMagneticField(simple)
+    self.__setMagneticFieldOptions(simple)
 
     #----------------------------------------------------------------------------------
 
@@ -288,9 +279,9 @@ class DD4hepSimulation(object):
 
     actionList = []
 
-    if self.gun:
+    if self.enableGun:
       gun = DDG4.GeneratorAction(kernel,"Geant4ParticleGun/"+"Gun")
-      gun = self.setGunOptions( gun )
+      self.__setGunOptions( gun )
       gun.Standalone = False
       gun.Mask = 1
       actionList.append(gun)
@@ -318,7 +309,7 @@ class DD4hepSimulation(object):
       self.__applyBoostOrSmear(kernel, actionList, index)
 
     if actionList:
-      simple.buildInputStage( actionList , output_level=DDG4.OutputLevel.DEBUG )
+      simple.buildInputStage( actionList , output_level=self.output.inputStage )
 
     #================================================================================================
 
@@ -326,12 +317,12 @@ class DD4hepSimulation(object):
     part = DDG4.GeneratorAction(kernel,"Geant4ParticleHandler/ParticleHandler")
     kernel.generatorAction().adopt(part)
     #part.SaveProcesses = ['conv','Decay']
-    part.SaveProcesses = ['Decay']
-    part.MinimalKineticEnergy = 1*MeV
-    part.KeepAllParticles = False
-    part.PrintEndTracking = False
-    part.PrintStartTracking = False
-    #part.OutputLevel = Output.INFO #generator_output_level
+    part.SaveProcesses        = self.part.saveProcesses
+    part.MinimalKineticEnergy = self.part.minimalKineticEnergy
+    part.KeepAllParticles     = self.part.keepAllParticles
+    part.PrintEndTracking     = self.part.printEndTracking
+    part.PrintStartTracking   = self.part.printStartTracking
+    part.OutputLevel = self.output.part
     part.enableUI()
     user = DDG4.Action(kernel,"Geant4TCUserParticleHandler/UserParticleHandler")
     try:
@@ -366,7 +357,7 @@ class DD4hepSimulation(object):
 
     for tracker in trk:
       print 'simple.setupTracker(  ' , tracker , ')'
- 
+
       if 'tpc' in tracker.lower():
         seq,act = simple.setupTracker( tracker, type='TPCSDAction')
       else:
@@ -408,15 +399,26 @@ class DD4hepSimulation(object):
     kernel.run()
     kernel.terminate()
 
-  def setGunOptions( self, gun ):
+  def __setGunOptions(self, gun):
     """set the starting properties of the DDG4 particle gun"""
-    gun.energy      = 10*GeV
-    gun.particle    = "mu-"
-    gun.multiplicity = 1
-    gun.position     = (0.0,0.0,0.0)
-    gun.isotrop      = False
-    gun.direction    = (0,0,1)
-    return gun
+    gun.energy       = self.gun.energy
+    gun.particle     = self.gun.particle
+    gun.multiplicity = self.gun.multiplicity
+    gun.position     = self.gun.position
+    gun.isotrop      = self.gun.isotrop
+    gun.direction    = self.gun.direction
+
+  def __setMagneticFieldOptions(self, simple):
+    """ create and configure the magnetic tracking setup """
+    field = simple.addConfig('Geant4FieldTrackingSetupAction/MagFieldTrackingSetup')
+    field.stepper            = self.field.stepper
+    field.equation           = self.field.equation
+    field.eps_min            = self.field.eps_min
+    field.eps_max            = self.field.eps_max
+    field.min_chord_step     = self.field.min_chord_step
+    field.delta_chord        = self.field.delta_chord
+    field.delta_intersection = self.field.delta_intersection
+    field.delta_one_step     = self.field.delta_one_step
 
   def __checkFileFormat(self, fileNames, extensions):
     """check if the fileName is allowed, note that the filenames are case
@@ -430,6 +432,7 @@ class DD4hepSimulation(object):
 
   def __applyBoostOrSmear( self, kernel, actionList, mask ):
     """apply boost or smearing for given mask index"""
+    import DDG4
     if self.crossingAngleBoost:
       lbo = DDG4.GeneratorAction(kernel, "Geant4InteractionVertexBoost")
       lbo.Angle = self.crossingAngleBoost
@@ -442,3 +445,61 @@ class DD4hepSimulation(object):
       vSmear.Sigma = self.vertexSigma
       vSmear.Mask = mask
       actionList.append(vSmear)
+
+  def __addAllHelper( self , parser ):
+    """all configHelper objects to commandline args"""
+    for name, obj in vars(self).iteritems():
+      if isinstance( obj, ConfigHelper ):
+        for var,valAndDoc in obj.getOptions().iteritems():
+          parser.add_argument("--%s.%s" % (name, var),
+                              action="store",
+                              dest="%s.%s" % (name, var),
+                              default = valAndDoc[0],
+                              help = valAndDoc[1]
+                              # type = type(val),
+                             )
+
+  def __parseAllHelper( self, parsed ):
+    """ parse all the options for the helper """
+    parsedDict = vars(parsed)
+    for name, obj in vars(self).iteritems():
+      if isinstance( obj, ConfigHelper ):
+        for var in obj.getOptions():
+          key = "%s.%s" %( name,var )
+          if key in parsedDict:
+            obj.setOption( var, parsedDict[key] )
+
+
+  def __checkOutputLevel(self, level):
+    """return outputlevel as int so we don't have to import anything for faster startup"""
+    try:
+      level = int(level)
+      if level < 1 or 7 < level:
+        raise KeyError
+      return level
+    except ValueError:
+      try:
+        return outputLevel[level.upper()]
+      except ValueError:
+        self.errorMessages.append( "ERROR: printLevel is neither integer nor string" )
+        return -1
+    except KeyError:
+      self.errorMessages.append( "ERROR: printLevel '%s' unknown" % level )
+      return -1
+
+
+################################################################################
+### MODULE FUNCTIONS GO HERE
+################################################################################
+
+def getOutputLevel(level):
+  """return output.LEVEL"""
+  from DDG4 import OutputLevel
+  levels = { 1: OutputLevel.VERBOSE,
+             2: OutputLevel.DEBUG,
+             3: OutputLevel.INFO,
+             4: OutputLevel.WARNING,
+             5: OutputLevel.ERROR,
+             6: OutputLevel.FATAL,
+             7: OutputLevel.ALWAYS }
+  return levels[level]
diff --git a/DDSim/Helper/ConfigHelper.py b/DDSim/Helper/ConfigHelper.py
new file mode 100644
index 000000000..3f28b63e0
--- /dev/null
+++ b/DDSim/Helper/ConfigHelper.py
@@ -0,0 +1,45 @@
+"""
+
+Helper object to identify configuration parameters so we can easily overwrite
+them via command line magic or via the steering file
+
+"""
+
+from pprint import pprint
+
+class ConfigHelper( object ):
+  """Base class for configuration helper"""
+  def __init__( self ):
+    pass
+
+  def getOptions(self):
+    finalVars = {}
+
+    # get all direct members not starting with underscore
+    allVars = vars(self)
+    for var,val in allVars.iteritems():
+      if not var.startswith('_'):
+        finalVars[var] = (val,'')
+
+    # now get things defined with @property
+    props = [(p, getattr(type(self),p)) for p in dir(type(self)) if isinstance(getattr(type(self),p),property)]
+    for propName, prop in props:
+      finalVars[propName] = (getattr(self, propName), prop.__doc__)
+
+    return finalVars
+
+  def printOptions( self ):
+    """print all paramters"""
+    return pprint(self.getOptions())
+
+  def setOption( self, name, val ):
+    """ set the attribute name to val """
+    setattr(self, name, val)
+
+  @staticmethod
+  def listifyString( stringVal, sep=" "):
+    """returns a list from a string separated by sep"""
+    if isinstance( stringVal, list ):
+      return stringVal
+    else:
+      return stringVal.split(sep)
diff --git a/DDSim/Helper/Gun.py b/DDSim/Helper/Gun.py
new file mode 100644
index 000000000..0babf1d3b
--- /dev/null
+++ b/DDSim/Helper/Gun.py
@@ -0,0 +1,17 @@
+"""Helper object for particle gun properties"""
+
+from DDSim.Helper.ConfigHelper import ConfigHelper
+from SystemOfUnits import GeV
+
+class Gun( ConfigHelper ):
+  """Gun holding all gun properties"""
+  def __init__( self ):
+    super(Gun, self).__init__()
+    self.energy = 10*GeV
+    self.particle = "mu-"
+    self.multiplicity = 1
+    self.position = (0.0,0.0,0.0)
+    self.isotrop = False
+    self.direction = (0,0,1)
+
+
diff --git a/DDSim/Helper/MagneticField.py b/DDSim/Helper/MagneticField.py
new file mode 100644
index 000000000..73777dfa1
--- /dev/null
+++ b/DDSim/Helper/MagneticField.py
@@ -0,0 +1,16 @@
+"""Helper object for Magnetic Field properties"""
+from SystemOfUnits import mm
+from DDSim.Helper.ConfigHelper import ConfigHelper
+
+class MagneticField( ConfigHelper ):
+  """MagneticField holding all field properties"""
+  def __init__( self ):
+    super(MagneticField, self).__init__()
+    self.stepper = "HelixSimpleRunge"
+    self.equation = "Mag_UsualEqRhs"
+    self.eps_min = 5e-05*mm
+    self.eps_max = 0.001*mm
+    self.min_chord_step = 0.01*mm
+    self.delta_chord = 0.25*mm
+    self.delta_intersection = 1e-05*mm
+    self.delta_one_step = 1e-04*mm
diff --git a/DDSim/Helper/Output.py b/DDSim/Helper/Output.py
new file mode 100644
index 000000000..a4e71efe7
--- /dev/null
+++ b/DDSim/Helper/Output.py
@@ -0,0 +1,36 @@
+"""Dummy helper object for particle gun properties"""
+
+from DDSim.Helper.ConfigHelper import ConfigHelper
+from DDSim.DD4hepSimulation import outputLevel
+
+class Output( ConfigHelper ):
+  """Output holding all gun properties so we can easily overwrite them via command line magic"""
+  def __init__( self ):
+    super(Output, self).__init__()
+    self._kernel = outputLevel('INFO')
+    self._part = outputLevel('INFO')
+    self._inputStage = outputLevel('INFO')
+
+  @property
+  def inputStage( self ):
+    """Output level for input sources"""
+    return self._inputStage
+  @inputStage.setter
+  def inputStage(self, level):
+    self._inputStage = outputLevel(level)
+
+  @property
+  def kernel( self ):
+    """Output level for Geant4 kernel"""
+    return self._kernel
+  @kernel.setter
+  def kernel(self, level):
+    self._kernel = outputLevel(level)
+
+  @property
+  def part( self ):
+    """Output level for ParticleHandler"""
+    return self._part
+  @part.setter
+  def part(self, level):
+    self._part = outputLevel(level)
diff --git a/DDSim/Helper/ParticleHandler.py b/DDSim/Helper/ParticleHandler.py
new file mode 100644
index 000000000..641ff5d21
--- /dev/null
+++ b/DDSim/Helper/ParticleHandler.py
@@ -0,0 +1,54 @@
+"""Configuration Helper for ParticleHandler"""
+from SystemOfUnits import MeV
+
+from DDSim.Helper.ConfigHelper import ConfigHelper
+
+class ParticleHandler( ConfigHelper ):
+  """Gun holding all gun properties so we can easily overwrite them via command line magic"""
+  def __init__( self ):
+    super(ParticleHandler, self).__init__()
+    self._saveProcesses = ['Decay']
+    self._minimalKineticEnergy = 1*MeV
+    self._keepAllParticles = False
+    self._printEndTracking = False
+    self._printStartTracking = False
+
+  @property
+  def saveProcesses(self):
+    """List of processes to save, give as whitespace separated string in quotation marks"""
+    return self._saveProcesses
+  @saveProcesses.setter
+  def saveProcesses(self, stringVal):
+    self._saveProcesses = ConfigHelper.listifyString( stringVal )
+
+  @property
+  def minimalKineticEnergy(self):
+    """MinimalKineticEnergy to store particles created in the tracking region"""
+    return self._minimalKineticEnergy
+  @minimalKineticEnergy.setter
+  def minimalKineticEnergy( self, val ):
+    self._minimalKineticEnergy = val
+
+  @property
+  def keepAllParticles( self ):
+    """ Keep all created particles """
+    return self._keepAllParticles
+  @keepAllParticles.setter
+  def keepAllParticles( self, val ):
+    self._keepAllParticles = val
+
+  @property
+  def printStartTracking( self ):
+    """ Printout at Start of Tracking """
+    return self._printStartTracking
+  @printStartTracking.setter
+  def printStartTracking( self, val ):
+    self._printEndTracking = val
+
+  @property
+  def printEndTracking( self ):
+    """ Printout at End of Tracking """
+    return self._printEndTracking
+  @printEndTracking.setter
+  def printEndTracking( self, val ):
+    self._printEndTracking = val
diff --git a/DDSim/Helper/__init__.py b/DDSim/Helper/__init__.py
new file mode 100644
index 000000000..09b8d4fcd
--- /dev/null
+++ b/DDSim/Helper/__init__.py
@@ -0,0 +1 @@
+""" DD4hepSimulation.Helper module """
diff --git a/DDSim/__init__.py b/DDSim/__init__.py
new file mode 100644
index 000000000..eccd680e0
--- /dev/null
+++ b/DDSim/__init__.py
@@ -0,0 +1 @@
+"""DD4hepSimulation module"""
diff --git a/ddsim b/ddsim
index d0d440791..8fb04710c 100755
--- a/ddsim
+++ b/ddsim
@@ -7,7 +7,7 @@ Based on M. Frank and F. Gaede runSim.py
    @version 0.1
 
 """
-from DD4hepSimulation import DD4hepSimulation
+from DDSim.DD4hepSimulation import DD4hepSimulation
 #------------------------------------------------
 if __name__ == "__main__":
   RUNNER = DD4hepSimulation()
-- 
GitLab