def I3CLSimTabulatePhotons( tray, name, UseCPUs=True, UseGPUs=False, UseOnlyDeviceNumber=None, MCTreeName="I3MCTree", OutputMCTreeName=None, FlasherInfoVectName=None, FlasherPulseSeriesName=None, MMCTrackListName="MMCTrackList", ParallelEvents=1000, RandomService=None, MediumProperties=expandvars("$I3_SRC/clsim/resources/ice/spice_mie"), UseGeant4=False, CrossoverEnergyEM=None, CrossoverEnergyHadron=None, UseCascadeExtension=False, DoNotParallelize=False, Area=None, WavelengthAcceptance=None, AngularAcceptance=None, UseHoleIceParameterization=True, OverrideApproximateNumberOfWorkItems=None, ExtraArgumentsToI3CLSimModule=dict(), If=lambda f: True): """Do standard clsim processing up to the I3Photon level. These photons still need to be converted to I3MCPEs to be usable for further steps in the standard IceCube MC processing chain. Reads its particles from an I3MCTree and writes an I3PhotonSeriesMap. All available OpenCL GPUs (and optionally CPUs) will be used. This will take over your entire machine, so make sure to configure your batch jobs correctly when using this on a cluster. When using nVidia cards, you can set the CUDA_VISIBLE_DEVICES environment variable to limit GPU visibility. A setting of CUDA_VISIBLE_DEVICES="0,3" would only use cards #0 and #3 and ignore cards #1 and #2. In case you are using a batch system, chances are this variable is already set. Unfortunately, there is no corresponding setting for the AMD driver. This segment assumes that MMC has been applied to the I3MCTree and that MMC was *NOT* run using the "-recc" option. :param UseCPUs: Turn this on to also use CPU-based devices. (This may potentially slow down photon generation, which is also done on the CPU in parallel.) :param UseGPUs: Turn this off to not use GPU-based devices. This may be useful if your GPU is used for display purposes and you don't want it to slow down. :param UseOnlyDeviceNumber: Use only a single device number, even if there is more than one device found matching the required description. The numbering starts at 0. :param MCTreeName: The name of the I3MCTree containing the particles to propagate. :param OutputMCTreeName: A copy of the (possibly sliced) MCTree will be stored as this name. :param FlasherInfoVectName: Set this to the name of I3FlasherInfoVect objects in the frame to enable flasher simulation. The module will read I3FlasherInfoVect objects and generate photons according to assumed parameterizations. :param FlasherPulseSeriesName: Set this to the name of an I3CLSimFlasherPulseSeries object in the frame to enable flasher/Standard Candle simulation. This cannot be used at the same time as FlasherInfoVectName. (I3CLSimFlasherPulseSeries objects are clsim's internal flasher representation, if "FlasherInfoVectName" is used, the I3FlasherInfoVect objects are converted to I3CLSimFlasherPulseSeries objects.) :param MMCTrackListName: Only used if *ChopMuons* is active. Set it to the name of the I3MMCTrackList object that contains additional muon energy loss information. :param ParallelEvents: clsim will work on a couple of events in parallel in order not to starve the GPU. Setting this too high will result in excessive memory usage (all your frames have to be cached in RAM). Setting it too low may impact simulation performance. The optimal value depends on your energy distribution/particle type. :param RandomService: Set this to an instance of a I3RandomService. Alternatively, you can specify the name of a configured I3RandomServiceFactory added to I3Tray using tray.AddService(). If you don't configure this, the default I3RandomServiceFactory will be used. :param MediumProperties: Set this either to a directory containing a PPC-compatible ice description (icemodel.dat, icemodel.par and cfg.txt) or to a photonics ice table file. PPC-compatible ice files should generally lead to faster execution times on GPUs since it involves less interpolation between table entries (the PPC ice-specification is parametric w.r.t. wavelength, whereas the photonics specification is not). :param Area: Geometric area of the sensor. If None, use the area of an IceCube DOM :param WavelengthAcceptance: Quantum efficiency of the sensor, relative to the geometric area. If None, use the IceCube DOM (standard QE) :param AngularAcceptance: Efficiency as a function of polar angle, relative to the geometric area. If None, use the IceCube angular efficiency, assuming hole ice. :param UseGeant4: Enabling this setting will disable all cascade and muon light yield parameterizations. All particles will sent to Geant4 for a full simulation. This does **not** apply to muons that do have a length assigned. These are assumed to have been generated by MMC and their light is generated according to the usual parameterization. :param CrossoverEnergyEM: If set it defines the crossover energy between full Geant4 simulations and light yield parameterizations for electro magnetic cascades. This only works when UseGeant4 is set to true. It works in conjunction with CrossoverEnergyHadron. If one of both is set to a positiv value greater 0 (GeV), the hybrid simulation is used. If CrossoverEnergyEM is set to None while CrossoverEnergyHadron is set so hybrid mode is working, GEANT4 is used for EM cascades. If CrossoverEnergyEM is set to 0 (GeV) while CrossoverEnergyHadron is set so hybrid mode is working, leptons and EM cascades will use parameterizations for the whole energy range. :param CrossoverEnergyHadron: If set it defines the crossover energy between full Geant4 simulations and light yield parameterizations for hadronic cascades. This only works when UseGeant4 is set to true. It works in conjunction with CrossoverEnergyEM. If one of both is set to a positiv value greater 0 (GeV), the hybrid simulation is used. If CrossoverEnergyHadron is set to None while CrossoverEnergyEM is set so hybrid mode is working, GEANT4 is used for hadronic cascades. If CrossoverEnergyHadron is set to 0 (GeV) while CrossoverEnergyHadron is set so hybrid mode is working, hadronic cascades will use parameterizations for the whole energy range. :param UseCascadeExtension: If True, simulate the longitudinal development of cascades. Otherwise, simulate cascades as pointlike objects. :param DoNotParallelize: Try only using a single work item in parallel when running the OpenCL simulation. This might be useful if you want to run jobs in parallel on a batch system. This will only affect CPUs and will be a no-op for GPUs. :param OverrideApproximateNumberOfWorkItems: Allows to override the auto-detection for the maximum number of parallel work items. You should only change this if you know what you are doing. :param If: Python function to use as conditional execution test for segment modules. """ from icecube import icetray, dataclasses, phys_services, clsim # make sure the geometry is updated to the new granular format (in case it is supported) if hasattr(dataclasses, "I3ModuleGeo"): tray.AddModule("I3GeometryDecomposer", name + "_decomposeGeometry", If=lambda frame: If(frame) and ("I3OMGeoMap" not in frame)) if UseGeant4: if not clsim.I3CLSimLightSourceToStepConverterGeant4.can_use_geant4: raise RuntimeError( "You have requested to use Geant 4, but clsim was compiled without Geant 4 support" ) # at the moment the Geant4 paths need to be set, even if it isn't used # TODO: fix this if clsim.I3CLSimLightSourceToStepConverterGeant4.can_use_geant4: AutoSetGeant4Environment() if MMCTrackListName is None or MMCTrackListName == "": # the input does not seem to have been processed by MMC ChopMuons = False else: ChopMuons = True if MCTreeName is None or MCTreeName == "": clSimMCTreeName = "" if ChopMuons: raise RuntimeError( "You cannot have \"MMCTrackListName\" enabled with no MCTree!") else: clSimMCTreeName = MCTreeName if FlasherInfoVectName is None or FlasherInfoVectName == "": if (FlasherPulseSeriesName is not None) and (FlasherPulseSeriesName != ""): SimulateFlashers = True clSimFlasherPulseSeriesName = FlasherPulseSeriesName clSimOMKeyMaskName = "" else: SimulateFlashers = False clSimFlasherPulseSeriesName = "" clSimOMKeyMaskName = "" else: if (FlasherPulseSeriesName is not None) and (FlasherPulseSeriesName != ""): raise RuntimeError( "You cannot use the FlasherPulseSeriesName and FlasherInfoVectName parameters at the same time!" ) SimulateFlashers = True clSimFlasherPulseSeriesName = FlasherInfoVectName + "_pulses" clSimOMKeyMaskName = FlasherInfoVectName + "_OMKeys" tray.AddModule(clsim.FlasherInfoVectToFlasherPulseSeriesConverter, name + "_FlasherInfoVectToFlasherPulseSeriesConverter", FlasherInfoVectName=FlasherInfoVectName, FlasherPulseSeriesName=clSimFlasherPulseSeriesName, FlasherOMKeyVectName=clSimOMKeyMaskName, If=If) # (optional) pre-processing if ChopMuons: if OutputMCTreeName is not None: clSimMCTreeName_new = OutputMCTreeName else: clSimMCTreeName_new = clSimMCTreeName + "_sliced" tray.AddModule("I3MuonSlicer", name + "_chopMuons", InputMCTreeName=clSimMCTreeName, MMCTrackListName=MMCTrackListName, OutputMCTreeName=clSimMCTreeName_new, If=If) clSimMCTreeName = clSimMCTreeName_new else: if (OutputMCTreeName is not None) and (OutputMCTreeName != ""): # copy the MCTree to the requested output name def copyMCTree(frame, inputName, outputName, If_=None): if If_ is not None: if not If_(frame): return frame[outputName] = frame[inputName] tray.AddModule(copyMCTree, name + "_copyMCTree", inputName=clSimMCTreeName, outputName=OutputMCTreeName, Streams=[icetray.I3Frame.DAQ], If_=If) clSimMCTreeName = OutputMCTreeName else: clSimMCTreeName = clSimMCTreeName # some constants DOMRadius = 0.16510 * icetray.I3Units.m # 13" diameter if Area is None: referenceArea = dataclasses.I3Constants.pi * DOMRadius**2 else: referenceArea = Area if WavelengthAcceptance is None: domAcceptance = clsim.GetIceCubeDOMAcceptance(domRadius=DOMRadius) else: domAcceptance = WavelengthAcceptance if AngularAcceptance is None: angularAcceptance = clsim.GetIceCubeDOMAngularSensitivity(holeIce=True) else: angularAcceptance = AngularAcceptance # muon&cascade parameterizations ppcConverter = clsim.I3CLSimLightSourceToStepConverterPPC( photonsPerStep=200) ppcConverter.SetUseCascadeExtension(UseCascadeExtension) if not UseGeant4: particleParameterizations = GetDefaultParameterizationList( ppcConverter, muonOnly=False) else: if CrossoverEnergyEM > 0 or CrossoverEnergyHadron > 0: particleParameterizations = GetHybridParameterizationList( ppcConverter, CrossoverEnergyEM=CrossoverEnergyEM, CrossoverEnergyHadron=CrossoverEnergyHadron) elif MMCTrackListName is None or MMCTrackListName == "": particleParameterizations = [ ] # make sure absolutely **no** parameterizations are used else: # use no parameterizations except for muons with lengths assigned to them # (those are assumed to have been generated by MMC) particleParameterizations = GetDefaultParameterizationList( ppcConverter, muonOnly=True) # flasher parameterizations if SimulateFlashers: # this needs a spectrum table in order to pass spectra to OpenCL spectrumTable = clsim.I3CLSimSpectrumTable() particleParameterizations += GetFlasherParameterizationList( spectrumTable) icetray.logging.log_debug( "number of spectra (1x Cherenkov + Nx flasher): %d" % len(spectrumTable), unit="clsim") else: # no spectrum table is necessary when only using the Cherenkov spectrum spectrumTable = None openCLDevices = configureOpenCLDevices( UseGPUs=UseGPUs, UseCPUs=UseCPUs, OverrideApproximateNumberOfWorkItems= OverrideApproximateNumberOfWorkItems, DoNotParallelize=DoNotParallelize, UseOnlyDeviceNumber=UseOnlyDeviceNumber) tray.AddModule( "I3CLSimTabulatorModule", name + "_clsim", MCTreeName=clSimMCTreeName, RandomService=RandomService, MediumProperties=MediumProperties, SpectrumTable=spectrumTable, FlasherPulseSeriesName=clSimFlasherPulseSeriesName, Area=referenceArea, WavelengthAcceptance=domAcceptance, AngularAcceptance=angularAcceptance, ParameterizationList=particleParameterizations, # MaxNumParallelEvents=ParallelEvents, OpenCLDeviceList=openCLDevices, **ExtraArgumentsToI3CLSimModule) unpin_threads()
def I3CLSimMakePhotons( tray, name, UseCPUs=False, UseGPUs=True, UseOnlyDeviceNumber=None, MCTreeName="I3MCTree", OutputMCTreeName=None, FlasherInfoVectName=None, FlasherPulseSeriesName=None, MMCTrackListName="MMCTrackList", PhotonSeriesName="PhotonSeriesMap", ParallelEvents=1000, TotalEnergyToProcess=0., RandomService=None, IceModelLocation=expandvars("$I3_SRC/clsim/resources/ice/spice_mie"), DisableTilt=False, UnWeightedPhotons=False, UnWeightedPhotonsScalingFactor=None, UseGeant4=False, CrossoverEnergyEM=None, CrossoverEnergyHadron=None, UseCascadeExtension=True, StopDetectedPhotons=True, PhotonHistoryEntries=0, DoNotParallelize=False, DOMOversizeFactor=5., UnshadowedFraction=0.9, HoleIceParameterization=expandvars( "$I3_SRC/ice-models/resources/models/angsens/as.h2-50cm"), WavelengthAcceptance=None, DOMRadius=0.16510 * icetray.I3Units.m, # 13" diameter OverrideApproximateNumberOfWorkItems=None, IgnoreSubdetectors=['IceTop'], ExtraArgumentsToI3CLSimModule=dict(), If=lambda f: True): """Do standard clsim processing up to the I3Photon level. These photons still need to be converted to I3MCPEs to be usable for further steps in the standard IceCube MC processing chain. Reads its particles from an I3MCTree and writes an I3PhotonSeriesMap. All available OpenCL GPUs (and optionally CPUs) will be used. This will take over your entire machine, so make sure to configure your batch jobs correctly when using this on a cluster. When using nVidia cards, you can set the CUDA_VISIBLE_DEVICES environment variable to limit GPU visibility. A setting of CUDA_VISIBLE_DEVICES="0,3" would only use cards #0 and #3 and ignore cards #1 and #2. In case you are using a batch system, chances are this variable is already set. Unfortunately, there is no corresponding setting for the AMD driver. This segment assumes that MMC has been applied to the I3MCTree and that MMC was *NOT* run using the "-recc" option. :param UseCPUs: Turn this on to also use CPU-based devices. (This may potentially slow down photon generation, which is also done on the CPU in parallel.) :param UseGPUs: Turn this off to not use GPU-based devices. This may be useful if your GPU is used for display purposes and you don't want it to slow down. :param UseOnlyDeviceNumber: Use only a single device number, even if there is more than one device found matching the required description. The numbering starts at 0. :param MCTreeName: The name of the I3MCTree containing the particles to propagate. :param OutputMCTreeName: A copy of the (possibly sliced) MCTree will be stored as this name. :param FlasherInfoVectName: Set this to the name of I3FlasherInfoVect objects in the frame to enable flasher simulation. The module will read I3FlasherInfoVect objects and generate photons according to assumed parameterizations. :param FlasherPulseSeriesName: Set this to the name of an I3CLSimFlasherPulseSeries object in the frame to enable flasher/Standard Candle simulation. This cannot be used at the same time as FlasherInfoVectName. (I3CLSimFlasherPulseSeries objects are clsim's internal flasher representation, if "FlasherInfoVectName" is used, the I3FlasherInfoVect objects are converted to I3CLSimFlasherPulseSeries objects.) :param MMCTrackListName: Only used if *ChopMuons* is active. Set it to the name of the I3MMCTrackList object that contains additional muon energy loss information. :param PhotonSeriesName: Configure this to enable writing an I3PhotonSeriesMap containing all photons that reached the DOM surface. :param ParallelEvents: clsim will work on a couple of events in parallel in order not to starve the GPU. Setting this too high will result in excessive memory usage (all your frames have to be cached in RAM). Setting it too low may impact simulation performance. The optimal value depends on your energy distribution/particle type. :param TotalEnergyToProcess: clsim will work on a couple of events in parallel in order not to starve the GPU. With this setting clsim will figure out how many frames to accumulate as to not starve the GPU based on the energy of the light sources that are producing the photons in the detector. Setting this too high will result in excessive memory usage (all your frames have to be cached in RAM). Setting it too low may impact simulation performance. This cannot be used in flasher mode, since we cannot measure the energy of the light sources. :param RandomService: Set this to an instance of a I3RandomService. Alternatively, you can specify the name of a configured I3RandomServiceFactory added to I3Tray using tray.AddService(). If you don't configure this, the default I3RandomServiceFactory will be used. :param IceModelLocation: Set this either to a directory containing a PPC-compatible ice description (icemodel.dat, icemodel.par and cfg.txt) or to a photonics ice table file. PPC-compatible ice files should generally lead to faster execution times on GPUs since it involves less interpolation between table entries (the PPC ice-specification is parametric w.r.t. wavelength, whereas the photonics specification is not). :param DisableTilt: Do not simulate ice tilt, even if the ice model directory provides tilt information. (Photonics-based models will never have tilt.) :param UnWeightedPhotons: Enabling this setting will disable all optimizations. These are currently a DOM oversize factor of 5 (with the appropriate timing correction) and a biased initial photon spectrum that includes the DOM spectral acceptance. Enabling this setting essentially means that all photons that would be generated in the real detector *will* actually be generated. This will siginificatly slow down the simulation, but the optional ``PhotonSeries`` will contain an unweighted sample of photons that arrive at your DOMs. This can be useful for DOM acceptance studies. :param UnWeightedPhotonsScalingFactor: If UnWeightedPhotons is turned on, this can be used to scale down the overall number of photons generated. This should normally not be touched (it can be used when generating photon paths for animation). Valid range is a float >0. and <=1. :param StopDetectedPhotons: Configures behaviour for photons that hit a DOM. If this is true (the default) photons will be stopped once they hit a DOM. If this is false, they continue to propagate. :param PhotonHistoryEntries: The maximum number of scatterings points to be saved for every photon hitting a DOM. Only the most recent positions are saved, older positions are overwritten if the maximum size is reached. :param UseGeant4: Enabling this setting will disable all cascade and muon light yield parameterizations. All particles will sent to Geant4 for a full simulation. This does **not** apply to muons that do have a length assigned. These are assumed to have been generated by MMC and their light is generated according to the usual parameterization. :param CrossoverEnergyEM: If set it defines the crossover energy between full Geant4 simulations and light yield parameterizations for electro magnetic cascades. This only works when UseGeant4 is set to true. It works in conjunction with CrossoverEnergyHadron. If one of both is set to a positiv value greater 0 (GeV), the hybrid simulation is used. If CrossoverEnergyEM is set to None while CrossoverEnergyHadron is set so hybrid mode is working, GEANT4 is used for EM cascades. If CrossoverEnergyEM is set to 0 (GeV) while CrossoverEnergyHadron is set so hybrid mode is working, leptons and EM cascades will use parameterizations for the whole energy range. :param CrossoverEnergyHadron: If set it defines the crossover energy between full Geant4 simulations and light yield parameterizations for hadronic cascades. This only works when UseGeant4 is set to true. It works in conjunction with CrossoverEnergyEM. If one of both is set to a positiv value greater 0 (GeV), the hybrid simulation is used. If CrossoverEnergyHadron is set to None while CrossoverEnergyEM is set so hybrid mode is working, GEANT4 is used for hadronic cascades. If CrossoverEnergyHadron is set to 0 (GeV) while CrossoverEnergyHadron is set so hybrid mode is working, hadronic cascades will use parameterizations for the whole energy range. :param UseCascadeExtension: If set, the cascade light emission parameterizations will include longitudinal extension. Otherwise, parameterized cascades will be treated as point-like. :param DoNotParallelize: Try only using a single work item in parallel when running the OpenCL simulation. This might be useful if you want to run jobs in parallel on a batch system. This will only affect CPUs and will be a no-op for GPUs. :param DOMOversizeFactor: Set the DOM oversize factor. To disable oversizing, set this to 1. :param UnshadowedFraction: Fraction of photocathode available to receive light (e.g. unshadowed by the cable) :param HoleIceParameterization: Set this to a hole ice parameterization file. The default file contains the coefficients for nominal angular acceptance correction due to hole ice (ice-models project is required). Use file $I3_SRC/ice-models/resources/models/angsens/as.nominal for no hole ice parameterization. :param WavelengthAcceptance: If specified, use this wavelength acceptance to scale the generated Cherenkov spectrum rather than using the DOM acceptance modified for oversizing and angular acceptance. :param DOMRadius: Allow the DOMRadius to be set externally, for things like mDOMs. :param OverrideApproximateNumberOfWorkItems: Allows to override the auto-detection for the maximum number of parallel work items. You should only change this if you know what you are doing. :param If: Python function to use as conditional execution test for segment modules. """ from icecube import icetray, dataclasses, phys_services, clsim # make sure the geometry is updated to the new granular format (in case it is supported) if hasattr(dataclasses, "I3ModuleGeo"): tray.AddModule("I3GeometryDecomposer", name + "_decomposeGeometry", If=lambda frame: If(frame) and ("I3OMGeoMap" not in frame) and ("I3ModuleGeoMap" not in frame)) if UseGeant4: if not clsim.I3CLSimLightSourceToStepConverterGeant4.can_use_geant4: raise RuntimeError( "You have requested to use Geant 4, but clsim was compiled without Geant 4 support" ) # at the moment the Geant4 paths need to be set, even if it isn't used # TODO: fix this if clsim.I3CLSimLightSourceToStepConverterGeant4.can_use_geant4: AutoSetGeant4Environment() # warn the user in case they might have done something they probably don't want if UnWeightedPhotons and (DOMOversizeFactor != 1.): print("********************") print( "Enabling the clsim.I3CLSimMakeHits() \"UnWeightedPhotons=True\" option without setting" ) print( "\"DOMOversizeFactor=1.\" will still apply a constant weighting factor of DOMOversizeFactor**2." ) print("If this is what you want, you can safely ignore this warning.") print("********************") # a constant Jitter = 2. * icetray.I3Units.ns if MMCTrackListName is None or MMCTrackListName == "": # the input does not seem to have been processed by MMC ChopMuons = False else: ChopMuons = True if MCTreeName is None or MCTreeName == "": clSimMCTreeName = "" if ChopMuons: raise RuntimeError( "You cannot have \"MMCTrackListName\" enabled with no MCTree!") else: clSimMCTreeName = MCTreeName if FlasherInfoVectName is None or FlasherInfoVectName == "": if (FlasherPulseSeriesName is not None) and (FlasherPulseSeriesName != ""): SimulateFlashers = True clSimFlasherPulseSeriesName = FlasherPulseSeriesName clSimOMKeyMaskName = "" else: SimulateFlashers = False clSimFlasherPulseSeriesName = "" clSimOMKeyMaskName = "" else: if (FlasherPulseSeriesName is not None) and (FlasherPulseSeriesName != ""): raise RuntimeError( "You cannot use the FlasherPulseSeriesName and FlasherInfoVectName parameters at the same time!" ) SimulateFlashers = True clSimFlasherPulseSeriesName = FlasherInfoVectName + "_pulses" clSimOMKeyMaskName = FlasherInfoVectName + "_OMKeys" tray.AddModule(clsim.FlasherInfoVectToFlasherPulseSeriesConverter, name + "_FlasherInfoVectToFlasherPulseSeriesConverter", FlasherInfoVectName=FlasherInfoVectName, FlasherPulseSeriesName=clSimFlasherPulseSeriesName, FlasherOMKeyVectName=clSimOMKeyMaskName, If=If) # (optional) pre-processing if ChopMuons: if OutputMCTreeName is not None: clSimMCTreeName_new = OutputMCTreeName else: clSimMCTreeName_new = clSimMCTreeName + "_sliced" tray.AddModule("I3MuonSlicer", name + "_chopMuons", InputMCTreeName=clSimMCTreeName, MMCTrackListName=MMCTrackListName, OutputMCTreeName=clSimMCTreeName_new, If=If) clSimMCTreeName = clSimMCTreeName_new else: if (OutputMCTreeName is not None) and (OutputMCTreeName != ""): # copy the MCTree to the requested output name def copyMCTree(frame, inputName, outputName, If_=None): if If_ is not None: if not If_(frame): return frame[outputName] = frame[inputName] tray.AddModule(copyMCTree, name + "_copyMCTree", inputName=clSimMCTreeName, outputName=OutputMCTreeName, Streams=[icetray.I3Frame.DAQ], If_=If) clSimMCTreeName = OutputMCTreeName else: clSimMCTreeName = clSimMCTreeName # ice properties if isinstance(IceModelLocation, str): mediumProperties = parseIceModel(IceModelLocation, disableTilt=DisableTilt) else: # get ice model directly if not a string mediumProperties = IceModelLocation # detector properties if WavelengthAcceptance is None: # the hole ice acceptance curve peaks at a value different than 1 peak = numpy.loadtxt(HoleIceParameterization)[ 0] # The value at which the hole ice acceptance curve peaks domEfficiencyCorrection = UnshadowedFraction * peak * 1.35 * 1.01 # DeepCore DOMs have a relative efficiency of 1.35 plus security margin of +1% domAcceptance = clsim.GetIceCubeDOMAcceptance( domRadius=DOMRadius * DOMOversizeFactor, efficiency=domEfficiencyCorrection) else: domAcceptance = WavelengthAcceptance # photon generation wavelength bias if not UnWeightedPhotons: wavelengthGenerationBias = domAcceptance if UnWeightedPhotonsScalingFactor is not None: raise RuntimeError( "UnWeightedPhotonsScalingFactor should not be set when UnWeightedPhotons is not set" ) else: if UnWeightedPhotonsScalingFactor is not None: print( "***** running unweighted simulation with a photon pre-scaling of", UnWeightedPhotonsScalingFactor) wavelengthGenerationBias = clsim.I3CLSimFunctionConstant( UnWeightedPhotonsScalingFactor) else: wavelengthGenerationBias = None # muon&cascade parameterizations ppcConverter = clsim.I3CLSimLightSourceToStepConverterPPC( photonsPerStep=200) ppcConverter.SetUseCascadeExtension(UseCascadeExtension) if not UseGeant4: particleParameterizations = GetDefaultParameterizationList( ppcConverter, muonOnly=False) else: if CrossoverEnergyEM > 0 or CrossoverEnergyHadron > 0: particleParameterizations = GetHybridParameterizationList( ppcConverter, CrossoverEnergyEM=CrossoverEnergyEM, CrossoverEnergyHadron=CrossoverEnergyHadron) elif MMCTrackListName is None or MMCTrackListName == "": particleParameterizations = [ ] # make sure absolutely **no** parameterizations are used else: # use no parameterizations except for muons with lengths assigned to them # (those are assumed to have been generated by MMC) particleParameterizations = GetDefaultParameterizationList( ppcConverter, muonOnly=True) # flasher parameterizations if SimulateFlashers: # this needs a spectrum table in order to pass spectra to OpenCL spectrumTable = clsim.I3CLSimSpectrumTable() particleParameterizations += GetFlasherParameterizationList( spectrumTable) print("number of spectra (1x Cherenkov + Nx flasher):", len(spectrumTable)) else: # no spectrum table is necessary when only using the Cherenkov spectrum spectrumTable = None openCLDevices = configureOpenCLDevices( UseGPUs=UseGPUs, UseCPUs=UseCPUs, OverrideApproximateNumberOfWorkItems= OverrideApproximateNumberOfWorkItems, DoNotParallelize=DoNotParallelize, UseOnlyDeviceNumber=UseOnlyDeviceNumber) if PhotonHistoryEntries == 0: module = 'I3CLSimModule<I3CompressedPhotonSeriesMap>' else: module = 'I3CLSimModule<I3PhotonSeriesMap>' tray.AddModule( module, name + "_clsim", MCTreeName=clSimMCTreeName, PhotonSeriesMapName=PhotonSeriesName, DOMRadius=DOMRadius, DOMOversizeFactor=DOMOversizeFactor, DOMPancakeFactor= DOMOversizeFactor, # you will probably want this to be the same as DOMOversizeFactor RandomService=RandomService, MediumProperties=mediumProperties, SpectrumTable=spectrumTable, FlasherPulseSeriesName=clSimFlasherPulseSeriesName, OMKeyMaskName=clSimOMKeyMaskName, #IgnoreNonIceCubeOMNumbers=False, GenerateCherenkovPhotonsWithoutDispersion=False, WavelengthGenerationBias=wavelengthGenerationBias, ParameterizationList=particleParameterizations, MaxNumParallelEvents=ParallelEvents, TotalEnergyToProcess=TotalEnergyToProcess, OpenCLDeviceList=openCLDevices, #UseHardcodedDeepCoreSubdetector=False, # setting this to true saves GPU constant memory but will reduce performance StopDetectedPhotons=StopDetectedPhotons, PhotonHistoryEntries=PhotonHistoryEntries, IgnoreSubdetectors=IgnoreSubdetectors, If=If, **ExtraArgumentsToI3CLSimModule)
def setupDetector(GCDFile, SimulateFlashers=False, IceModelLocation=expandvars("$I3_SRC/clsim/resources/ice/spice_mie"), DisableTilt=False, UnWeightedPhotons=False, UnWeightedPhotonsScalingFactor=None, UseI3PropagatorService=True, UseGeant4=False, CrossoverEnergyEM=None, CrossoverEnergyHadron=None, UseCascadeExtension=True, StopDetectedPhotons=True, DOMOversizeFactor=5., UnshadowedFraction=0.9, HoleIceParameterization=expandvars("$I3_SRC/ice-models/resources/models/angsens/as.h2-50cm"), WavelengthAcceptance=None, DOMRadius=0.16510*icetray.I3Units.m, # 13" diameter CableOrientation=None, IgnoreSubdetectors=['IceTop']): """ Set up data structures used in N different places in clsim :param GCDFile: either a filename or a tuple of (Geometry, Calibration) frames """ from icecube import clsim, dataclasses from icecube.icetray import logging from icecube.clsim import GetDefaultParameterizationList from icecube.clsim import GetFlasherParameterizationList from icecube.clsim import GetHybridParameterizationList from icecube.clsim.GetIceCubeCableShadow import GetIceCubeCableShadow import numpy def harvest_detector_parameters(GCDFile): from icecube import dataio, dataclasses from I3Tray import I3Tray tray = I3Tray() tray.Add(dataio.I3Reader, Filenamelist=[GCDFile]) # make sure the geometry is updated to the new granular format tray.AddModule("I3GeometryDecomposer", If=lambda frame: ("I3OMGeoMap" not in frame) and ("I3ModuleGeoMap" not in frame)) def pluck_geo(frame): pluck_geo.frame = frame pluck_geo.frame = None tray.Add(pluck_geo, Streams=[icetray.I3Frame.Geometry]) def pluck_calib(frame): pluck_calib.frame = frame pluck_calib.frame = None tray.Add(pluck_calib, Streams=[icetray.I3Frame.Calibration]) tray.Execute() if CableOrientation: icetray.logging.log_warn("Explicitly simulating cable shadow. This will reduce overall DOM efficiency by ~10%.", unit="clsim") pluck_geo.frame['CableShadow'] = GetIceCubeCableShadow(CableOrientation) if isinstance(CableOrientation, str) else CableOrientation geometry = clsim.I3CLSimSimpleGeometryFromI3Geometry( DOMRadius, DOMOversizeFactor, pluck_geo.frame, ignoreSubdetectors=dataclasses.ListString(IgnoreSubdetectors), # NB: we trust advanced users to properly label subdetectors, and disable any # min/max string/om numbers and strings/oms to ignore ignoreStrings=dataclasses.ListInt(), ignoreDomIDs=dataclasses.ListUInt(), ignoreStringIDsSmallerThan=1, ignoreStringIDsLargerThan=numpy.iinfo(numpy.int32).max, ignoreDomIDsSmallerThan=1, ignoreDomIDsLargerThan=numpy.iinfo(numpy.uint32).max, splitIntoPartsAccordingToPosition=False, useHardcodedDeepCoreSubdetector=False ) rde = dict() spe_compensation_factor = dict() for k, domcal in pluck_calib.frame['I3Calibration'].dom_cal.iteritems(): rde[k] = domcal.relative_dom_eff comp = domcal.combined_spe_charge_distribution.compensation_factor spe_compensation_factor[k] = comp if not math.isnan(comp) else 1. return geometry, rde, spe_compensation_factor geometry, rde, spe_compensation_factor = harvest_detector_parameters(GCDFile) # ice properties if isinstance(IceModelLocation, str): mediumProperties = parseIceModel(IceModelLocation, disableTilt=DisableTilt) else: # get ice model directly if not a string mediumProperties = IceModelLocation icemodel_efficiency_factor = mediumProperties.efficiency # detector properties if WavelengthAcceptance is None: # Combine all global factors that enter only the wavelength acceptance domEfficiencyCorrection = UnshadowedFraction*icemodel_efficiency_factor assert domEfficiencyCorrection > 0 # The hole ice acceptance curve peaks at a value different than 1. Use # this in the wavelength generation maxAngularAcceptance = numpy.loadtxt(HoleIceParameterization)[0] assert maxAngularAcceptance > 0 @memoize def getWavelengthAcceptance(rde, spe_comp, efficiency_scale): """ Construct a wavelength acceptance This is memoized to create only one instance per combination of parameters, e.g. one for IceCube and one for DeepCore. """ kwargs = {} if round(rde, 6) == 1.35: kwargs['highQE'] = True # reset RDE to 1; highQE curve is already scaled rde = 1 elif rde != 1: raise ValueError("Relative DOM efficiency {} is neither 1 nor 1.35. You probably need to add support for individual DOM efficiencies".format(rde)) try: if not math.isfinite(spe_comp): raise ValueError("SPE compensation factor is {}. Fix your GCD file.".format(spe_comp)) except AttributeError: # likely Python2 isfinite is python3. if math.isnan(spe_comp) or math.isinf(spe_comp): raise ValueError("SPE compensation factor is {}. Fix your GCD file.".format(spe_comp)) return clsim.GetIceCubeDOMAcceptance(domRadius = DOMRadius*DOMOversizeFactor, efficiency=rde*spe_comp*efficiency_scale, **kwargs) def getEnvelope(functions, scale=1): """Construct the supremum of a set of I3CLSimFunctionFromTable""" first = functions[0] return clsim.I3CLSimFunctionFromTable( first.GetMinWlen(), first.GetWavelengthStepping(), [scale*max((f.GetEntryValue(i) for f in functions)) for i in range(first.GetNumEntries())] ) # Wavelength acceptance of individual DOMs domAcceptance = clsim.I3CLSimFunctionMap() for string_id,dom_id in zip(geometry.stringIDs,geometry.domIDs): k = icetray.OMKey(string_id,dom_id,0) if math.isnan(rde.get(k,float('nan'))): continue try: domAcceptance[k] = getWavelengthAcceptance(rde.get(k,float('nan')), spe_compensation_factor.get(k,float('nan')), domEfficiencyCorrection) except ValueError as e: raise ValueError(str(k)+' '+e.args[0]) # The wavelength generation bias is the maximum possible value of # the product of wavelength acceptance (wvl) and angular acceptance # (ang), such that the PE conversion probability, given by # wvl*ang/bias, is never > 1 domAcceptanceEnvelope = getEnvelope(list(set(domAcceptance.values())), maxAngularAcceptance) else: domAcceptance = WavelengthAcceptance domAcceptanceEnvelope = WavelengthAcceptance angularAcceptance = clsim.GetIceCubeDOMAngularSensitivity(holeIce=HoleIceParameterization) # photon generation wavelength bias if not UnWeightedPhotons: wavelengthGenerationBias = domAcceptanceEnvelope if UnWeightedPhotonsScalingFactor is not None: raise RuntimeError("UnWeightedPhotonsScalingFactor should not be set when UnWeightedPhotons is not set") else: logging.log_info("***** running unweighted simulation with a photon pre-scaling of {}".format(UnWeightedPhotonsScalingFactor), unit="clsim") wavelengthGenerationBias = clsim.I3CLSimFunctionConstant(1. if UnWeightedPhotonsScalingFactor is None else UnWeightedPhotonsScalingFactor) # create wavelength generators wavelengthGenerators = [clsim.makeCherenkovWavelengthGenerator(wavelengthGenerationBias, UnWeightedPhotons, mediumProperties)] # muon&cascade parameterizations ppcConverter = clsim.I3CLSimLightSourceToStepConverterPPC(photonsPerStep=200) ppcConverter.SetUseCascadeExtension(UseCascadeExtension) if not UseGeant4: particleParameterizations = GetDefaultParameterizationList(ppcConverter, muonOnly=False) else: if CrossoverEnergyEM>0 or CrossoverEnergyHadron>0: particleParameterizations = GetHybridParameterizationList(ppcConverter, CrossoverEnergyEM=CrossoverEnergyEM, CrossoverEnergyHadron=CrossoverEnergyHadron) else: # use no parameterizations except for muons with lengths assigned to them # (those are assumed to have been generated by PROPOSAL) particleParameterizations = GetDefaultParameterizationList(ppcConverter, muonOnly=True) # flasher parameterizations if SimulateFlashers: # this needs a spectrum table in order to pass spectra to OpenCL spectrumTable = clsim.I3CLSimSpectrumTable() particleParameterizations += clsim.GetFlasherParameterizationList(spectrumTable) for spectrum in spectrumTable: if spectrum: wavelengthGenerators.append(clsim.makeWavelengthGenerator(spectrum, wavelengthGenerationBias, mediumProperties)) logging.log_info("number of spectra (1x Cherenkov + Nx flasher): {}".format(len(spectrumTable)), unit="clsim") else: # no spectrum table is necessary when only using the Cherenkov spectrum spectrumTable = None return dict(Geometry=geometry, MediumProperties=mediumProperties, IceModelLocation=IceModelLocation, WavelengthGenerationBias=wavelengthGenerationBias, SpectrumTable=spectrumTable, GenerateCherenkovPhotonsWithoutDispersion=UnWeightedPhotons, WavelengthGenerators=wavelengthGenerators, DOMRadius=DOMRadius, DOMOversizeFactor=DOMOversizeFactor, DOMPancakeFactor=DOMOversizeFactor, UnshadowedFraction=UnshadowedFraction, AngularAcceptance=angularAcceptance, WavelengthAcceptance=domAcceptance, ParameterizationList=particleParameterizations, UseGeant4=UseGeant4, UseI3PropagatorService=UseI3PropagatorService, IgnoreSubdetectors=IgnoreSubdetectors,)
def setupDetector( GCDFile, SimulateFlashers=False, IceModelLocation=expandvars("$I3_SRC/clsim/resources/ice/spice_mie"), DisableTilt=False, UnWeightedPhotons=False, UnWeightedPhotonsScalingFactor=None, MMCTrackListName="MMCTrackList", UseGeant4=False, CrossoverEnergyEM=None, CrossoverEnergyHadron=None, UseCascadeExtension=True, StopDetectedPhotons=True, DOMOversizeFactor=5., UnshadowedFraction=0.9, HoleIceParameterization=expandvars( "$I3_SRC/ice-models/resources/models/angsens/as.h2-50cm"), WavelengthAcceptance=None, DOMRadius=0.16510 * icetray.I3Units.m, # 13" diameter IgnoreSubdetectors=['IceTop']): """ Set up data structures used in N different places in clsim """ from icecube import clsim from icecube.clsim import GetDefaultParameterizationList from icecube.clsim import GetFlasherParameterizationList from icecube.clsim import GetHybridParameterizationList import numpy def harvest_detector_parameters(GCDFile): from icecube import dataio, dataclasses from I3Tray import I3Tray tray = I3Tray() tray.Add(dataio.I3Reader, Filenamelist=[GCDFile]) # make sure the geometry is updated to the new granular format tray.AddModule("I3GeometryDecomposer", If=lambda frame: ("I3OMGeoMap" not in frame) and ("I3ModuleGeoMap" not in frame)) def pluck_geo(frame): pluck_geo.frame = frame pluck_geo.frame = None tray.Add(pluck_geo, Streams=[icetray.I3Frame.Geometry]) def pluck_calib(frame): pluck_calib.frame = frame pluck_calib.frame = None tray.Add(pluck_calib, Streams=[icetray.I3Frame.Calibration]) tray.Execute() geometry = clsim.I3CLSimSimpleGeometryFromI3Geometry( DOMRadius, DOMOversizeFactor, pluck_geo.frame, ignoreSubdetectors=dataclasses.ListString(IgnoreSubdetectors), # NB: we trust advanced users to properly label subdetectors, and disable any # min/max string/om numbers and strings/oms to ignore ignoreStrings=dataclasses.ListInt(), ignoreDomIDs=dataclasses.ListUInt(), ignoreStringIDsSmallerThan=1, ignoreStringIDsLargerThan=numpy.iinfo(numpy.int32).max, ignoreDomIDsSmallerThan=1, ignoreDomIDsLargerThan=numpy.iinfo(numpy.uint32).max, splitIntoPartsAccordingToPosition=False, useHardcodedDeepCoreSubdetector=False) rde = dict() for k, domcal in pluck_calib.frame['I3Calibration'].dom_cal.iteritems( ): rde[k] = domcal.relative_dom_eff return geometry, rde geometry, rde = harvest_detector_parameters(GCDFile) # ice properties if isinstance(IceModelLocation, str): mediumProperties = parseIceModel(IceModelLocation, disableTilt=DisableTilt) else: # get ice model directly if not a string mediumProperties = IceModelLocation # detector properties if WavelengthAcceptance is None: # the hole ice acceptance curve peaks at a value different than 1 peak = numpy.loadtxt(HoleIceParameterization)[ 0] # The value at which the hole ice acceptance curve peaks domEfficiencyCorrection = UnshadowedFraction * peak acceptance = { 'IceCube': clsim.GetIceCubeDOMAcceptance(domRadius=DOMRadius * DOMOversizeFactor, efficiency=domEfficiencyCorrection), 'DeepCore': clsim.GetIceCubeDOMAcceptance(domRadius=DOMRadius * DOMOversizeFactor, efficiency=domEfficiencyCorrection, highQE=True), } domAcceptanceEnvelope = clsim.I3CLSimFunctionFromTable( acceptance['IceCube'].GetMinWlen(), acceptance['IceCube'].GetWavelengthStepping(), [ max((f.GetEntryValue(i) for f in acceptance.values())) for i in range(acceptance['IceCube'].GetNumEntries()) ]) domAcceptance = clsim.I3CLSimFunctionMap() #loop over every event in the geometry and determine what type of DOM it is #based on the relative efficiency in the detector status for string_id, dom_id in zip(geometry.stringIDs, geometry.domIDs): k = icetray.OMKey(string_id, dom_id, 0) releff = rde.get(k, float('nan')) if releff == 1: domAcceptance[k] = acceptance['IceCube'] elif round(releff, 6) == 1.35: domAcceptance[k] = acceptance['DeepCore'] elif math.isnan(releff): #DOMs where rde is nan are bad DOMs which are unplugged #call them IceCube for now, hits generated by them will be #removed later by detector simulation domAcceptance[k] = acceptance['IceCube'] else: raise ValueError( "Relative DOM efficiency is neither 1 nor 1.35. You probably need to add support for individual DOM efficiencies" ) else: domAcceptance = WavelengthAcceptance domAcceptanceEnvelope = WavelengthAcceptance angularAcceptance = clsim.GetIceCubeDOMAngularSensitivity( holeIce=HoleIceParameterization) # photon generation wavelength bias if not UnWeightedPhotons: wavelengthGenerationBias = domAcceptanceEnvelope if UnWeightedPhotonsScalingFactor is not None: raise RuntimeError( "UnWeightedPhotonsScalingFactor should not be set when UnWeightedPhotons is not set" ) else: if UnWeightedPhotonsScalingFactor is not None: print( "***** running unweighted simulation with a photon pre-scaling of", UnWeightedPhotonsScalingFactor) wavelengthGenerationBias = clsim.I3CLSimFunctionConstant( UnWeightedPhotonsScalingFactor) else: wavelengthGenerationBias = None # create wavelength generators wavelengthGenerators = [ clsim.makeCherenkovWavelengthGenerator(wavelengthGenerationBias, UnWeightedPhotons, mediumProperties) ] # flasher parameterizations if SimulateFlashers: # this needs a spectrum table in order to pass spectra to OpenCL spectrumTable = clsim.I3CLSimSpectrumTable() for spectrum in spectrumTable: wavelengthGenerators.append( makeWavelengthGenerator(spectrum, wavelengthGenerationBias, mediumProperties)) print("number of spectra (1x Cherenkov + Nx flasher):", len(spectrumTable)) else: # no spectrum table is necessary when only using the Cherenkov spectrum spectrumTable = None # muon&cascade parameterizations ppcConverter = clsim.I3CLSimLightSourceToStepConverterPPC( photonsPerStep=200) ppcConverter.SetUseCascadeExtension(UseCascadeExtension) if not UseGeant4: particleParameterizations = GetDefaultParameterizationList( ppcConverter, muonOnly=False) else: if CrossoverEnergyEM > 0 or CrossoverEnergyHadron > 0: particleParameterizations = GetHybridParameterizationList( ppcConverter, CrossoverEnergyEM=CrossoverEnergyEM, CrossoverEnergyHadron=CrossoverEnergyHadron) elif MMCTrackListName is None or MMCTrackListName == "": particleParameterizations = [ ] # make sure absolutely **no** parameterizations are used else: # use no parameterizations except for muons with lengths assigned to them # (those are assumed to have been generated by MMC) particleParameterizations = GetDefaultParameterizationList( ppcConverter, muonOnly=True) return dict(Geometry=geometry, MediumProperties=mediumProperties, IceModelLocation=IceModelLocation, WavelengthGenerationBias=wavelengthGenerationBias, SpectrumTable=spectrumTable, GenerateCherenkovPhotonsWithoutDispersion=UnWeightedPhotons, WavelengthGenerators=wavelengthGenerators, DOMRadius=DOMRadius, DOMOversizeFactor=DOMOversizeFactor, DOMPancakeFactor=DOMOversizeFactor, UnshadowedFraction=UnshadowedFraction, AngularAcceptance=angularAcceptance, WavelengthAcceptance=domAcceptance, ParameterizationList=particleParameterizations, IgnoreSubdetectors=IgnoreSubdetectors)