Ejemplo n.º 1
0
def TabulatePhotonsFromSource(tray,
                              name,
                              PhotonSource="cascade",
                              Zenith=0. * I3Units.degree,
                              Azimuth=0. * I3Units.degree,
                              ZCoordinate=0. * I3Units.m,
                              Energy=1. * I3Units.GeV,
                              FlasherWidth=127,
                              FlasherBrightness=127,
                              Seed=12345,
                              NEvents=100,
                              IceModel='spice_mie',
                              DisableTilt=False,
                              Filename="",
                              TabulateImpactAngle=False,
                              PhotonPrescale=1,
                              Axes=None,
                              Directions=None,
                              Sensor='DOM',
                              RecordErrors=False):
    """
    Tabulate the distribution of photoelectron yields on IceCube DOMs from various
    light sources. The light profiles of the sources are computed from the same
    parameterizations used in PPC, but like in the direct propagation mode can
    be computed using GEANT4 instead.

    The mode of tabulation is controlled primarily by the **PhotonSource** parameter.
    
    - *'cascade'* will simulate an electromagnetic cascade of **Energy** GeV at
      (0, 0, **ZCoordinate**), oriented according to **Zenith** and **Azimuth**.
      The default coordinate system is spherical and centered the given vertex,
      with 200 quadratically spaced bins in radius, 36 linear bins in azimuthal
      angle (only from 0 to 180 degrees by default), 100 linear bins in the
      cosine of the polar angle, and 105 quadratic bins in time residual w.r.t
      the direct path from (0, 0, **ZCoordinate**).
    - *'flasher'* will simulate a 405 nm LED flasher pulse with the given
      **FlasherWidth** and **FlasherBrightness** settings. The source position
      and coordinate system are the same as for the 'cascade' case.
    - *'infinite-muon'* will simulate a "bare" muon of infinite length. The
      coordinate system is cylindrical and centered on the axis of the muon.
      Since the muon's position is degenerate with time, the usual parallel
      distance is replaced by the z coordinate of the closest approach to the
      detection position, and the starting positions of the simulated muons are
      sampled randomly (**ZCoordinate** is ignored). There are 100 quadratic
      bins in perpendicular distance to the source axis, 36 linear bins in
      azimuthal angle (0 to :math:`\pi` radians), 100 linear bins in z
      coordinate of closest approach, and 105 quadratic bins in time residual
      w.r.t. the earliest possible Cherenkov photon.

    :param PhotonSource: the type of photon source ('cascade', 'flasher', or 'infinite-muon').
    :param Zenith: the orientation of the source
    :param ZCoordinate: the depth of the source
    :param Energy: the energy of the source (only for cascade tables)
    :param FlasherWidth: the width of the flasher pulse (only for flasher tables)
    :param FlasherBrightness: the brightness of the flasher pulse (only for flasher tables)
    :param Seed: the seed for the random number service
    :param NEvents: the number of events to simulate
    :param RecordErrors: record the squares of weights as well (useful for error bars)
    :param IceModel: the path to an ice model in $I3_SRC/clsim/resources/ice. Likely values include:
        'spice_mie' ppc-style SPICE-Mie parametrization
        'photonics_spice_1/Ice_table.spice.i3coords.cos080.10feb2010.txt' Photonics-style SPICE1 table
        'photonics_wham/Ice_table.wham.i3coords.cos094.11jul2011.txt' Photonics-style WHAM! table
    :param DisableTilt: if true, disable tilt in ice model
    :param Filename: the name of the FITS file to write
    :param TabulateImpactAngle: if True, tabulate the impact position of the
           photon on the DOM instead of weighting by the DOM's angular acceptance
    :param Axes: a subclass of :cpp:class:`clsim::tabulator::Axes` that defines the coordinate system.
                 If None, an appropriate default will be chosen based on **PhotonSource**.
    :param Directions: a set of directions to allow table generation for multiple sources.
                 If None, only one direction given by **Zenith** and **Azimuth** is used.
       """

    # check sanity of args
    PhotonSource = PhotonSource.lower()
    if PhotonSource not in ['cascade', 'flasher', 'infinite-muon']:
        raise ValueError(
            "photon source %s is unknown. Please specify either 'cascade', 'flasher', or 'infinite-muon'"
            % PhotonSource)

    from icecube import icetray, dataclasses, dataio, phys_services, sim_services, clsim
    from os.path import expandvars

    # a random number generator
    randomService = phys_services.I3GSLRandomService(Seed)

    tray.AddModule("I3InfiniteSource",
                   name + "streams",
                   Stream=icetray.I3Frame.DAQ)

    tray.AddModule("I3MCEventHeaderGenerator",
                   name + "gen_header",
                   Year=2009,
                   DAQTime=158100000000000000,
                   RunNumber=1,
                   EventID=1,
                   IncrementEventID=True)

    if Directions is None:
        Directions = numpy.asarray([(Zenith, Azimuth)])

    if PhotonSource in ('cascade', 'flasher', 'muon-segment'):

        if PhotonSource == 'muon-segment':
            ptype = I3Particle.ParticleType.MuMinus
        else:
            ptype = I3Particle.ParticleType.EMinus

        def reference_source(zenith, azimuth, scale):
            source = I3Particle()
            source.type = ptype
            source.energy = Energy * scale
            source.pos = I3Position(0., 0., ZCoordinate)
            source.dir = I3Direction(zenith, azimuth)
            source.time = 0.
            if PhotonSource == 'muon-segment':
                source.length = 3.
            else:
                source.length = 0.
            source.location_type = I3Particle.LocationType.InIce

            return source

    elif PhotonSource == 'infinite-muon':

        from icecube import MuonGun
        # pad depth to ensure that the track appears effectively infinite
        surface = MuonGun.Cylinder(1800, 800)
        # account for zenith-dependent distribution of track lengths
        length_scale = surface.area(dataclasses.I3Direction(
            0, 0)) / surface.area(dataclasses.I3Direction(Zenith, 0))

        ptype = I3Particle.ParticleType.MuMinus

        def reference_source(zenith, azimuth, scale):
            source = I3Particle()
            source.type = ptype
            source.energy = Energy * scale
            source.dir = I3Direction(zenith, azimuth)
            source.pos = surface.sample_impact_position(
                source.dir, randomService)
            crossings = surface.intersection(source.pos, source.dir)
            source.length = crossings.second - crossings.first
            source.time = 0.
            source.location_type = I3Particle.LocationType.InIce

            return source

    import copy

    class MakeParticle(icetray.I3Module):
        def __init__(self, ctx):
            super(MakeParticle, self).__init__(ctx)
            self.AddOutBox("OutBox")
            self.AddParameter("SourceFunction", "", lambda: None)
            self.AddParameter("NEvents", "", 100)

        def Configure(self):
            self.reference_source = self.GetParameter("SourceFunction")
            self.nevents = self.GetParameter("NEvents")
            self.emittedEvents = 0

        def DAQ(self, frame):
            if PhotonSource != "flasher":
                primary = I3Particle()
                mctree = I3MCTree()
                mctree.add_primary(primary)
                for zenith, azimuth in Directions:
                    source = self.reference_source(zenith, azimuth,
                                                   1. / len(Directions))
                    mctree.append_child(primary, source)
                frame["I3MCTree"] = mctree
                # use the emitting particle as a geometrical reference
                frame["ReferenceParticle"] = source
            else:
                pulseseries = I3CLSimFlasherPulseSeries()
                for zenith, azimuth in Directions:
                    pulse = makeFlasherPulse(0, 0, ZCoordinate, zenith,
                                             azimuth, FlasherWidth,
                                             FlasherBrightness,
                                             1. / len(Directions))
                    pulseseries.append(pulse)
                frame["I3FlasherPulseSeriesMap"] = pulseseries
                frame["ReferenceParticle"] = self.reference_source(
                    Zenith, Azimuth, 1.)

            self.PushFrame(frame)

            self.emittedEvents += 1
            if self.emittedEvents >= self.nevents:
                self.RequestSuspension()

    tray.AddModule(MakeParticle,
                   SourceFunction=reference_source,
                   NEvents=NEvents)

    if PhotonSource == "flasher":
        flasherpulse = "I3FlasherPulseSeriesMap"
        mctree = None
    else:
        flasherpulse = None
        mctree = "I3MCTree"

    header = dict(FITSTable.empty_header)
    header['zenith'] = Zenith / I3Units.degree
    header['azimuth'] = Azimuth / I3Units.degree
    header['z'] = ZCoordinate
    header['energy'] = Energy
    header['type'] = int(ptype)
    header['efficiency'] = Efficiency.RECEIVER | Efficiency.WAVELENGTH
    if PhotonSource == 'infinite-muon':
        header['n_events'] = length_scale * NEvents / float(PhotonPrescale)

    if Axes is None:
        if PhotonSource != "infinite-muon":
            dims = [
                clsim.tabulator.PowerAxis(0, 580, 200, 2),
                clsim.tabulator.LinearAxis(0, 180, 36),
                clsim.tabulator.LinearAxis(-1, 1, 100),
                clsim.tabulator.PowerAxis(0, 7e3, 105, 2),
            ]
            geo = clsim.tabulator.SphericalAxes
        else:
            dims = [
                clsim.tabulator.PowerAxis(0, 580, 100, 2),
                clsim.tabulator.LinearAxis(0, numpy.pi, 36),
                clsim.tabulator.LinearAxis(-8e2, 8e2, 80),
                clsim.tabulator.PowerAxis(0, 7e3, 105, 2),
            ]
            geo = clsim.tabulator.CylindricalAxes
        # Add a dimension for the impact angle
        if TabulateImpactAngle:
            dims.append(clsim.tabulator.LinearAxis(-1, 1, 20))
        Axes = geo(dims)

    if PhotonSource == "flasher":
        header['flasherwidth'] = FlasherWidth
        header['flasherbrightness'] = FlasherBrightness

    # some constants
    DOMRadius = 0.16510 * icetray.I3Units.m  # 13" diameter
    referenceArea = dataclasses.I3Constants.pi * DOMRadius**2

    # NB: GetIceCubeDOMAcceptance() calculates the quantum efficiency by
    #     dividing the geometric area (a circle of radius domRadius) by the
    #     tabulated effective area. Scaling that radius by *sqrt(prescale)*
    #     _reduces_ the effective quantum efficiency by a factor *prescale*.
    #     Since we draw photons directly from the QE-weighted Cherenkov
    #     spectrum, this causes *prescale* fewer photons to be progagated per
    #     light source. We compensate by dividing the number of events by
    #     *prescale* in the header above.
    #     to be propagated per light source.
    domAcceptance = clsim.GetIceCubeDOMAcceptance(
        domRadius=math.sqrt(PhotonPrescale) * DOMRadius)

    if Sensor.lower() == 'dom':
        angularAcceptance = clsim.GetIceCubeDOMAngularSensitivity(holeIce=True)
    elif Sensor.lower() == 'degg':
        referenceArea = dataclasses.I3Constants.pi * (300. * I3Units.mm / 2)**2
        angularAcceptance = Gen2Sensors.GetDEggAngularSensitivity(pmt='both')
        domAcceptance = Gen2Sensors.GetDEggAcceptance(active_fraction=1. /
                                                      PhotonPrescale)
    elif Sensor.lower() == 'wom':
        # outer diameter of the pressure vessel is 11.4 cm, walls are 9 mm thick
        referenceArea = (11 - 2 * 0.9) * 90 * icetray.I3Units.cm2
        angularAcceptance = Gen2Sensors.GetWOMAngularSensitivity()
        domAcceptance = Gen2Sensors.GetWOMAcceptance(active_fraction=1. /
                                                     PhotonPrescale)

    else:
        raise ValueError("Don't know how to simulate %ds yet" % (sensor))

    tray.AddSegment(
        I3CLSimTabulatePhotons,
        name + "makeCLSimPhotons",
        MCTreeName=
        mctree,  # if source is a cascade this will point to the I3MCTree
        FlasherPulseSeriesName=
        flasherpulse,  # if source is a flasher this will point to the I3CLSimFlasherPulseSeries
        MMCTrackListName=None,  # do NOT use MMC
        ParallelEvents=
        1,  # only work at one event at a time (it'll take long enough)
        RandomService=randomService,
        # UnWeightedPhotons=True,
        UseGPUs=
        False,  # table-making is not a workload particularly suited to GPUs
        UseCPUs=True,  # it should work fine on CPUs, though
        Area=referenceArea,
        WavelengthAcceptance=domAcceptance,
        AngularAcceptance=angularAcceptance,
        DoNotParallelize=True,  # no multithreading
        UseGeant4=False,
        OverrideApproximateNumberOfWorkItems=
        1,  # if you *would* use multi-threading, this would be the maximum number of jobs to run in parallel (OpenCL is free to split them)
        ExtraArgumentsToI3CLSimModule=dict(Filename=Filename,
                                           TableHeader=header,
                                           Axes=Axes,
                                           PhotonsPerBunch=200,
                                           EntriesPerPhoton=5000,
                                           RecordErrors=RecordErrors),
        MediumProperties=parseIceModel(
            expandvars("$I3_SRC/clsim/resources/ice/" + IceModel),
            disableTilt=DisableTilt),
    )
Ejemplo n.º 2
0
def I3CLSimMakePhotons(tray, name,
                       UseCPUs=False,
                       UseGPUs=True,
                       UseOnlyDeviceNumber=None,
                       MCTreeName="I3MCTree",
                       OutputMCTreeName=None,
                       FlasherInfoVectName=None,
                       FlasherPulseSeriesName=None,
                       MMCTrackListName="MMCTrackList",
                       PhotonSeriesName="PhotonSeriesMap",
                       ParallelEvents=1000,
                       RandomService=None,
                       IceModelLocation=expandvars("$I3_SRC/clsim/resources/ice/spice_mie"),
                       DisableTilt=False,
                       UnWeightedPhotons=False,
                       UseGeant4=False,
                       StopDetectedPhotons=True,
                       PhotonHistoryEntries=0,
                       DoNotParallelize=False,
                       DOMOversizeFactor=5.,
                       UnshadowedFraction=0.9,
                       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 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 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 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 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 UseHoleIceParameterization:
        Use an angular acceptance correction for hole ice scattering.
    :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()

    # 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("********************")

    # some constants
    DOMRadius = 0.16510*icetray.I3Units.m # 13" diameter
    Jitter = 2.*icetray.I3Units.ns
    numFlasherPhotonsAtFullBrightness = 8.0e9

    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,
                       NumberOfPhotonsAtMaxBrightness = numFlasherPhotonsAtFullBrightness,
                       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 UseHoleIceParameterization:
        # the hole ice acceptance curve peaks at 0.75 instead of 1
        domEfficiencyCorrection = UnshadowedFraction*0.75*1.35 * 1.01 # DeepCore DOMs have a relative efficiency of 1.35 plus security margin of +1%
    else:
        domEfficiencyCorrection = UnshadowedFraction*1.35      * 1.01 # security margin of +1%
    domAcceptance = clsim.GetIceCubeDOMAcceptance(domRadius = DOMRadius*DOMOversizeFactor, efficiency=domEfficiencyCorrection)

    # photon generation wavelength bias
    #if isinstance(UnWeightedPhotons, float) or isinstance(UnWeightedPhotons, int):
    #    print("***** running unweighted simulation with a photon pre-scaling of", UnWeightedPhotons)
    #    wavelengthGenerationBias = clsim.I3CLSimFunctionConstant(UnWeightedPhotons)
    #else:
    if not UnWeightedPhotons:
        wavelengthGenerationBias = domAcceptance
    else:
        wavelengthGenerationBias = None

    # muon&cascade parameterizations
    ppcConverter = clsim.I3CLSimLightSourceToStepConverterPPC(photonsPerStep=200)
    if not UseGeant4:
        particleParameterizations = GetDefaultParameterizationList(ppcConverter, muonOnly=False)
    else:
        if 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
	)

    tray.AddModule("I3CLSimModule", 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,
                   # ignore IceTop
                   IgnoreSubdetectors = ["IceTop"],
                   #IgnoreNonIceCubeOMNumbers=False,
                   GenerateCherenkovPhotonsWithoutDispersion=False,
                   WavelengthGenerationBias=wavelengthGenerationBias,
                   ParameterizationList=particleParameterizations,
                   MaxNumParallelEvents=ParallelEvents,
                   OpenCLDeviceList=openCLDevices,
                   #UseHardcodedDeepCoreSubdetector=False, # setting this to true saves GPU constant memory but will reduce performance
                   StopDetectedPhotons=StopDetectedPhotons,
                   PhotonHistoryEntries=PhotonHistoryEntries,
                   If=If,
                   **ExtraArgumentsToI3CLSimModule
                   )
Ejemplo n.º 3
0
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)
# pylint: disable=invalid-name
"""
Display photon production info for tracks and cascades, as parameterized in
IceCube software
"""

from __future__ import absolute_import, division, print_function

from os.path import expandvars

from icecube.clsim.traysegments.common import parseIceModel
from icecube.clsim import NumberOfPhotonsPerMeter
from icecube import clsim
from icecube.icetray import I3Units

mediumProperties = parseIceModel(
    expandvars("$I3_SRC/clsim/resources/ice/spice_mie"), disableTilt=True)
domAcceptance = clsim.GetIceCubeDOMAcceptance()
photons_per_m_trck = NumberOfPhotonsPerMeter(
    mediumProperties.GetPhaseRefractiveIndex(0), domAcceptance,
    mediumProperties.GetMinWavelength(), mediumProperties.GetMaxWavelength())

density = mediumProperties.GetMediumDensity() * (I3Units.cm3 / I3Units.g)
"""Density in units of g/cm^3"""

# PPC parametrerization
photons_per_gev_em_cscd = 5.21 * 0.924 / density

# TODO: what factor to use here?
#photons_per_gev_had_cscd = photons_per_gev_em_cscd * ???
m_per_gev_trck = 15 / 3.33
photons_per_gev_trck = photons_per_m_trck * m_per_gev_trck
Ejemplo n.º 5
0
def TabulateRetroSources(
    tray,
    name,
    source_gcd_i3_md5,
    binning_kw,
    axes,
    ice_model,
    angular_sensitivity,
    disable_tilt,
    disable_anisotropy,
    hash_val,
    dom_spec,
    dom_x,
    dom_y,
    dom_z,
    dom_zenith,
    dom_azimuth,
    seed,
    n_events,
    tablepath,
    tile=None,
    record_errors=False,
):
    dom_x = dom_x * I3Units.m
    dom_y = dom_y * I3Units.m
    dom_z = dom_z * I3Units.m
    dom_zenith = dom_zenith * I3Units.rad
    dom_azimuth = dom_azimuth * I3Units.rad

    tablepath = expanduser(expandvars(tablepath))

    random_service = I3GSLRandomService(seed)

    tray.AddModule('I3InfiniteSource', name + 'streams', Stream=I3Frame.DAQ)

    tray.AddModule('I3MCEventHeaderGenerator',
                   name + 'gen_header',
                   Year=2009,
                   DAQTime=158100000000000000,
                   RunNumber=1,
                   EventID=1,
                   IncrementEventID=True)

    flasher_pulse_series_name = 'I3FlasherPulseSeriesMap'

    def reference_source(x, y, z, zenith, azimuth, scale):
        source = I3Particle()
        source.pos = I3Position(x, y, z)
        source.dir = I3Direction(zenith, azimuth)
        source.time = 0.0
        # Following are not used (at least not yet)
        source.type = I3Particle.ParticleType.EMinus
        source.energy = 1.0 * scale
        source.length = 0.0
        source.location_type = I3Particle.LocationType.InIce

        return source

    class MakeParticle(I3Module):
        def __init__(self, ctx):
            super(MakeParticle, self).__init__(ctx)
            self.AddOutBox('OutBox')
            self.AddParameter('source_function', '', lambda: None)
            self.AddParameter('n_events', '', 100)
            self.reference_source = None
            self.n_events = None
            self.emitted_events = None

        def Configure(self):
            self.reference_source = self.GetParameter('source_function')
            self.n_events = self.GetParameter('n_events')
            self.emitted_events = 0

        def DAQ(self, frame):
            pulseseries = I3CLSimFlasherPulseSeries()
            pulse = make_retro_pulse(
                x=dom_x,
                y=dom_y,
                z=dom_z,
                zenith=dom_zenith,
                azimuth=dom_azimuth,
            )
            pulseseries.append(pulse)
            frame[flasher_pulse_series_name] = pulseseries
            frame['ReferenceParticle'] = self.reference_source(
                x=dom_x,
                y=dom_y,
                z=dom_z,
                zenith=dom_zenith,
                azimuth=dom_azimuth,
                scale=1.0,
            )
            self.PushFrame(frame)
            self.emitted_events += 1
            if self.emitted_events >= self.n_events:
                self.RequestSuspension()

    tray.AddModule(
        MakeParticle,
        source_function=reference_source,
        n_events=n_events,
    )

    header = OrderedDict(FITSTable.empty_header)
    header['retro_dom_table'] = 0
    header['gcd_i3_md5_{:s}'.format(source_gcd_i3_md5)] = 0
    for n, axname in enumerate(binning_kw.keys()):
        header['ax_{:s}'.format(axname)] = n
        for key, val in binning_kw[axname].items():
            header['{}_{}'.format(axname, key)] = val
        if axname == 't':
            header['t_is_residual_time'] = 1
    header['ice_{:s}'.format(ice_model.replace('.', '_'))] = 0
    header['angsens_{:s}'.format(angular_sensitivity.replace('.', '_'))] = 0
    header['disable_tilt'] = disable_tilt
    header['disable_anisotropy'] = disable_anisotropy
    header['hash_{:s}'.format(hash_val)] = 0
    if tile is not None:
        header['tile'] = tile
    for key, value in dom_spec.items():
        header[key] = value
    header['dom_x'] = dom_x
    header['dom_y'] = dom_y
    header['dom_z'] = dom_z
    header['dom_zenith'] = dom_zenith
    header['dom_azimuth'] = dom_azimuth
    header['seed'] = seed
    header['n_events'] = n_events

    if hasattr(dataclasses, 'I3ModuleGeo'):
        tray.AddModule('I3GeometryDecomposer',
                       name + '_decomposeGeometry',
                       If=lambda frame: 'I3OMGeoMap' not in frame)

    # at the moment the Geant4 paths need to be set, even if it isn't used
    # TODO: fix this
    if I3CLSimLightSourceToStepConverterGeant4.can_use_geant4:
        AutoSetGeant4Environment()

    ppc_converter = I3CLSimLightSourceToStepConverterPPC(photonsPerStep=200)
    # Is this even necessary?
    ppc_converter.SetUseCascadeExtension(False)
    particle_parameterizations = GetDefaultParameterizationList(
        ppc_converter,
        muonOnly=False,
    )

    # need a spectrum table in order to pass spectra to OpenCL
    spectrum_table = I3CLSimSpectrumTable()
    particle_parameterizations += GetFlasherParameterizationList(
        spectrum_table)

    logging.log_debug(
        'number of spectra (1x Cherenkov + Nx flasher): %d' %
        len(spectrum_table),
        unit='clsim',
    )

    opencl_devices = configureOpenCLDevices(
        UseGPUs=False,
        UseCPUs=True,
        OverrideApproximateNumberOfWorkItems=None,
        DoNotParallelize=True,
        UseOnlyDeviceNumber=None,
    )

    medium_properties = parseIceModel(
        expandvars('$I3_SRC/ice-models/resources/models/' + ice_model),
        disableTilt=disable_tilt,
        disableAnisotropy=disable_anisotropy,
    )

    tray.AddModule(
        'I3CLSimTabulatorModule',
        name + '_clsim',
        MCTreeName='',  # doesn't apply since we use pulse series
        FlasherPulseSeriesName=flasher_pulse_series_name,
        RandomService=random_service,
        Area=DOM_SURFACE_AREA,
        WavelengthAcceptance=I3CLSimFunctionConstant(
            1.0),  #GetIceCubeDOMAcceptance(domRadius=DOM_RADIUS),
        AngularAcceptance=I3CLSimFunctionConstant(1.0),
        MediumProperties=medium_properties,
        ParameterizationList=particle_parameterizations,
        SpectrumTable=spectrum_table,
        OpenCLDeviceList=opencl_devices,
        PhotonsPerBunch=200,
        EntriesPerPhoton=5000,
        Filename=tablepath,
        RecordErrors=record_errors,
        TableHeader=header,
        Axes=axes,
        SensorNormalize=False)

    unpin_threads()
Ejemplo n.º 6
0
def I3CLSimMakeHitsFromPhotons(
        tray,
        name,
        PhotonSeriesName="PhotonSeriesMap",
        MCPESeriesName="MCPESeriesMap",
        RandomService=None,
        DOMOversizeFactor=5.,
        UnshadowedFraction=1.0,
        IceModelLocation=None,  #Needed for icemodel-dependent efficiency
        HoleIceParameterization=expandvars(
            "$I3_BUILD/ice-models/resources/models/angsens/as.h2-50cm"),
        MergeHits=False,
        GCDFile=None,
        If=lambda f: True):
    """
    Convert I3Photons into I3MCPEs. This applies the DOM
    angular acceptance (and wavenelgth acceptance in case
    you are using the unbiased photon propagation mode.)

    :param PhotonSeriesName:
        Name of the input I3PhotonSeriesMap to be converted.
    :param MCPESeriesName:
        Name of the output I3MCPESeriesMap written by the module.
        Set this to None to prevent generating MCPEs from
        Photons.
    :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 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_BUILD/ice-models/resources/models/angsens/as.nominal 
        for no hole ice parameterization. 
    :param MergeHits:
    	Set to true to perform time merging on the MCPE as they are produced. This is useful for 
    	reducing the memory and disk space used by high energy (bright) events, and can allow 
    	detector simulation to run much more quickly. This causes parent particle information to be 
    	stored in an additional frame object. 
    :param If:
        Python function to use as conditional execution test for segment modules.        
    """

    from icecube import icetray, dataclasses, clsim
    from icecube.clsim.traysegments.common import parseIceModel

    icemodel_efficiency_factor = 1.0
    # ice properties
    if isinstance(IceModelLocation, str):
        mediumProperties = parseIceModel(IceModelLocation)
    else:
        # get ice model directly if not a string
        mediumProperties = IceModelLocation

    if mediumProperties is not None:
        # IceModel-dependent efficiency
        icemodel_efficiency_factor = mediumProperties.efficiency
    else:
        icetray.logging.log_warn(
            "No IceModel configured. Using DOMefficiency of %f" %
            icemodel_efficiency_factor)

    if UnshadowedFraction <= 0:
        raise RuntimeError("UnshadowedFraction must be a positive number")

    # some constants
    DOMRadius = 0.16510 * icetray.I3Units.m  # 13" diameter
    Jitter = 2. * icetray.I3Units.ns

    # detector properties

    max_compensation_factor = 1.0
    if GCDFile is None:
        icetray.logging.log_warn(
            "No GCD file given. Setting compensation factor to 1.0!!!!")
    else:
        max_compensation_factor = get_compensation_factor(
            dataio.I3File(GCDFile))

    icetray.logging.log_info(
        "Net DOM efficiency (with compensation factor): %s" %
        (UnshadowedFraction * icemodel_efficiency_factor *
         max_compensation_factor))

    domAcceptance = clsim.GetIceCubeDOMAcceptance(
        domRadius=DOMRadius * DOMOversizeFactor,
        efficiency=icemodel_efficiency_factor * UnshadowedFraction)
    domAngularSensitivity = clsim.GetIceCubeDOMAngularSensitivity(
        holeIce=HoleIceParameterization)

    tray.AddModule(
        "I3PhotonToMCPEConverter",
        name + "_clsim_make_hits",
        RandomService=RandomService,
        InputPhotonSeriesMapName=PhotonSeriesName,
        OutputMCPESeriesMapName=MCPESeriesName,
        DOMRadiusWithoutOversize=DOMRadius,
        DOMOversizeFactor=DOMOversizeFactor,
        DOMPancakeFactor=DOMOversizeFactor,
        WavelengthAcceptance=domAcceptance,
        AngularAcceptance=domAngularSensitivity,
        IgnoreDOMsWithoutDetectorStatusEntry=
        False,  # in icesim4 it is the job of the DOM simulation tools to cut out these DOMs
        MergeHits=MergeHits,
        If=If)
def main():
    from icecube.clsim.traysegments.common import parseIceModel
    from icecube.clsim import NumberOfPhotonsPerMeter
    from icecube import clsim
    #from icecube.icetray import I3Units

    mediumProperties = parseIceModel(
        expandvars("$I3_SRC/clsim/resources/ice/spice_mie"),
        disableTilt=True,
    )
    domAcceptance = clsim.GetIceCubeDOMAcceptance()
    ppmt = []
    for i in range(1000):
        try:
            photons_per_m_trck = NumberOfPhotonsPerMeter(
                mediumProperties.GetPhaseRefractiveIndex(i),
                domAcceptance,
                mediumProperties.GetMinWavelength(),
                mediumProperties.GetMaxWavelength()
            )
        except RuntimeError:
            break
        ppmt.append(photons_per_m_trck)

    #density = mediumProperties.GetMediumDensity() * (I3Units.cm3 / I3Units.g)
    #"""Density in units of g/cm^3"""

    ppmt = np.array(ppmt)
    print(
        'photons_per_m_trck: min={}, mean={}, median={}, max={}'.format(
            np.min(ppmt), np.mean(ppmt), np.median(ppmt), np.max(ppmt)
        )
    )
    #densities = np.array(densities)
    #print(np.min(densities), np.mean(densities), np.median(densities), np.max(densities))

    # PPC parametrerization
    #
    # Following adapted slightly from C++ source at
    #   private/clsim/I3CLSimLightSourceToStepConverterPPC.cxx:
    #
    # For a layer in the ice i:
    # meanPhotonsPerMeter = NumberOfPhotonsPerMeter(
    #     *(mediumProperties_->GetPhaseRefractiveIndex(i)),
    #     *(wlenBias_),
    #     mediumProperties_->GetMinWavelength(),
    #     mediumProperties_->GetMaxWavelength()
    # );
    # const double meanNumPhotons = f*meanPhotonsPerMeter*nph*E;
    # (f = 1 unless emScaleSigma != 0; or else it's a random normal in [0, 1];
    # therefore, assume it's 1)
    photons_per_gev_em_cscd = 5.21 * 0.924 / NOMINAL_ICE_DENSITY * photons_per_m_trck

    em_equiv_energy_for_1gev_hadr = hadr2em(1.0)
    photons_per_gev_had_cscd = em_equiv_energy_for_1gev_hadr * photons_per_gev_em_cscd

    m_per_gev_trck = 15 / 3.33
    photons_per_gev_trck = photons_per_m_trck * m_per_gev_trck

    print('Medium density is reported to be %.15f g/cm^3' % NOMINAL_ICE_DENSITY)
    print('')
    print('Muon track:')
    print('  %10.3f photons per m' % photons_per_m_trck)
    print('  %10.3f photons per GeV' % photons_per_gev_trck)
    print('  %10.3f m per GeV' % m_per_gev_trck)
    print('')
    print('Electromagnetic cascade:')
    print('  %10.3f photons per GeV' % photons_per_gev_em_cscd)
    print('')
    print('Hadronic cascade:')
    print('  %10.3f photons AT 1 GeV (luminisoty is NOT linear wrt hadronic energy!)'
          % photons_per_gev_had_cscd)
    print('')
Ejemplo n.º 8
0
def I3CLSimMakeHitsFromPhotons(tray, name,
                               PhotonSeriesName="PhotonSeriesMap",
                               MCPESeriesName="MCPESeriesMap",
                               RandomService=None,
                               DOMOversizeFactor=5.,
                               UnshadowedFraction=1.0,
                               IceModelLocation=None, #Needed for icemodel-dependent efficiency
                               HoleIceParameterization=expandvars("$I3_BUILD/ice-models/resources/models/angsens/as.h2-50cm"),
                               MergeHits=False,
                               GCDFile= None,
                               If=lambda f: True
                               ):
    """
    Convert I3Photons into I3MCPEs. This applies the DOM
    angular acceptance (and wavenelgth acceptance in case
    you are using the unbiased photon propagation mode.)

    :param PhotonSeriesName:
        Name of the input I3PhotonSeriesMap to be converted.
    :param MCPESeriesName:
        Name of the output I3MCPESeriesMap written by the module.
        Set this to None to prevent generating MCPEs from
        Photons.
    :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 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_BUILD/ice-models/resources/models/angsens/as.nominal 
        for no hole ice parameterization. 
    :param MergeHits:
    	Set to true to perform time merging on the MCPE as they are produced. This is useful for 
    	reducing the memory and disk space used by high energy (bright) events, and can allow 
    	detector simulation to run much more quickly. This causes parent particle information to be 
    	stored in an additional frame object. 
    :param If:
        Python function to use as conditional execution test for segment modules.        
    """

    from icecube import icetray, dataclasses, clsim
    from icecube.clsim.traysegments.common import parseIceModel
    
    icemodel_efficiency_factor = 1.0
    # ice properties
    if isinstance(IceModelLocation, str):
        mediumProperties = parseIceModel(IceModelLocation)
    else:
        # get ice model directly if not a string
        mediumProperties = IceModelLocation


    if mediumProperties is not None:
        # IceModel-dependent efficiency
        icemodel_efficiency_factor = mediumProperties.efficiency 
    else:
        icetray.logging.log_warn("No IceModel configured. Using DOMefficiency of %f" % icemodel_efficiency_factor)
        

    if UnshadowedFraction<=0:
        raise RuntimeError("UnshadowedFraction must be a positive number")

    # some constants
    DOMRadius = 0.16510*icetray.I3Units.m # 13" diameter
    Jitter = 2.*icetray.I3Units.ns

    # detector properties
   
    max_compensation_factor = 1.0
    if GCDFile is None:
        icetray.logging.log_warn("No GCD file given. Setting compensation factor to 1.0!!!!")
    else:
        max_compensation_factor = get_compensation_factor(dataio.I3File(GCDFile))


    icetray.logging.log_info("Net DOM efficiency (with compensation factor): %s" % (UnshadowedFraction*icemodel_efficiency_factor*max_compensation_factor))

    domAcceptance = clsim.GetIceCubeDOMAcceptance(
                                domRadius = DOMRadius*DOMOversizeFactor, 
                                efficiency=icemodel_efficiency_factor*UnshadowedFraction)
    domAngularSensitivity = clsim.GetIceCubeDOMAngularSensitivity(holeIce=HoleIceParameterization)

    tray.AddModule("I3PhotonToMCPEConverter", name + "_clsim_make_hits",
                   RandomService = RandomService,
                   InputPhotonSeriesMapName = PhotonSeriesName,
                   OutputMCPESeriesMapName = MCPESeriesName,
                   DOMRadiusWithoutOversize=DOMRadius,
                   DOMOversizeFactor = DOMOversizeFactor,
                   DOMPancakeFactor = DOMOversizeFactor,
                   WavelengthAcceptance = domAcceptance,
                   AngularAcceptance = domAngularSensitivity,
                   IgnoreDOMsWithoutDetectorStatusEntry = False, # in icesim4 it is the job of the DOM simulation tools to cut out these DOMs
                   MergeHits=MergeHits,
                   If=If)
Ejemplo n.º 9
0
    def getDetectorParameters(
            IceModel="spice_lea",
            DisableTilt=False,
            UnshadowedFraction=0.95,  #1.,match the clsim setting
            UseHoleIceParameterization=True,
            DOMOversizeFactor=5.,
            UnWeightedPhotons=False):

        DOMRadius = 0.16510 * icetray.I3Units.m  # 13" diameter

        # ice properties
        if isinstance(IceModel, str):
            mediumProperties = parseIceModel(os.path.expandvars(
                '$I3_BUILD/clsim/resources/ice/%s' % IceModel),
                                             disableTilt=DisableTilt)
        else:
            # get ice model directly if not a string
            mediumProperties = IceModel

        # detector properties
        if UseHoleIceParameterization:
            print "calculating DOM efficiency correction using hole ice"
            # the hole ice acceptance curve peaks at 0.75 instead of 1
            domEfficiencyCorrection = UnshadowedFraction * 0.75 * 1.35 * 1.01  # DeepCore DOMs have a relative efficiency of 1.35 plus security margin of +1%
        else:
            domEfficiencyCorrection = UnshadowedFraction * 1.35 * 1.01  # security margin of +1%
        print "from tray segment: domEfficiencyCorrection is  ", domEfficiencyCorrection
        domAcceptance = clsim.GetIceCubeDOMAcceptance(
            domRadius=DOMRadius * DOMOversizeFactor,
            efficiency=domEfficiencyCorrection)
        print "from tray segment: domAcceptance NumEntries is  ", domAcceptance.GetNumEntries(
        )
        print "from tray segment: domAcceptance MinWlen is  ", domAcceptance.GetMinWlen(
        )
        print "from tray segment: domAcceptance MaxWlen is  ", domAcceptance.GetMaxWlen(
        )
        print "from tray segment: domAcceptance FirstWlen is  ", domAcceptance.GetFirstWavelength(
        )
        print "from tray segment: domAcceptance WlenStepping is  ", domAcceptance.GetWavelengthStepping(
        )
        print "from tray segment: domAcceptance 4th entry value is  ", domAcceptance.GetEntryValue(
            4)
        print "from tray segment: domAcceptance 4th entry wlen is  ", domAcceptance.GetEntryWavelength(
            4)
        domAngularSensitivity = clsim.GetIceCubeDOMAngularSensitivity(
            holeIce=UseHoleIceParameterization)
        print "from tray segment: domAngularSensitivity coefficients are  ", domAngularSensitivity.GetCoefficients(
        )
        print "from tray segment: with hole ice  ", UseHoleIceParameterization

        if not UnWeightedPhotons:
            wavelengthGenerationBias = domAcceptance
        else:
            wavelengthGenerationBias = None
        print "from tray segment: wavelengthGenerationBias is  ", wavelengthGenerationBias

        wavelengthGenerators = clsim.I3CLSimRandomValuePtrSeries()
        wavelengthGenerators.append(
            clsim.makeCherenkovWavelengthGenerator(wavelengthGenerationBias,
                                                   False, mediumProperties))
        print "from tray segment: wavelengthGenerators is  ", wavelengthGenerators  #[0].NumberOfParameters() #no pybindings?
        return mediumProperties, wavelengthGenerationBias, wavelengthGenerators, domAcceptance, domAngularSensitivity, DOMOversizeFactor
Ejemplo n.º 10
0
def TabulatePhotonsFromSource(tray, name, PhotonSource="cascade", Zenith=0.*I3Units.degree, Azimuth=0.*I3Units.degree, ZCoordinate=0.*I3Units.m,
    Energy=1.*I3Units.GeV, FlasherWidth=127, FlasherBrightness=127, Seed=12345, NEvents=100,
    IceModel='spice_mie', DisableTilt=False, Filename="", TabulateImpactAngle=False,
    PhotonPrescale=1, Axes=None, Directions=None, Sensor='DOM', RecordErrors=False):
    
    """
    Tabulate the distribution of photoelectron yields on IceCube DOMs from various
    light sources. The light profiles of the sources are computed from the same
    parameterizations used in PPC, but like in the direct propagation mode can
    be computed using GEANT4 instead.

    The mode of tabulation is controlled primarily by the **PhotonSource** parameter.
    
    - *'cascade'* will simulate an electromagnetic cascade of **Energy** GeV at
      (0, 0, **ZCoordinate**), oriented according to **Zenith** and **Azimuth**.
      The default coordinate system is spherical and centered the given vertex,
      with 200 quadratically spaced bins in radius, 36 linear bins in azimuthal
      angle (only from 0 to 180 degrees by default), 100 linear bins in the
      cosine of the polar angle, and 105 quadratic bins in time residual w.r.t
      the direct path from (0, 0, **ZCoordinate**).
    - *'flasher'* will simulate a 405 nm LED flasher pulse with the given
      **FlasherWidth** and **FlasherBrightness** settings. The source position
      and coordinate system are the same as for the 'cascade' case.
    - *'infinite-muon'* will simulate a "bare" muon of infinite length. The
      coordinate system is cylindrical and centered on the axis of the muon.
      Since the muon's position is degenerate with time, the usual parallel
      distance is replaced by the z coordinate of the closest approach to the
      detection position, and the starting positions of the simulated muons are
      sampled randomly (**ZCoordinate** is ignored). There are 100 quadratic
      bins in perpendicular distance to the source axis, 36 linear bins in
      azimuthal angle (0 to :math:`\pi` radians), 100 linear bins in z
      coordinate of closest approach, and 105 quadratic bins in time residual
      w.r.t. the earliest possible Cherenkov photon.

    :param PhotonSource: the type of photon source ('cascade', 'flasher', or 'infinite-muon').
    :param Zenith: the orientation of the source
    :param ZCoordinate: the depth of the source
    :param Energy: the energy of the source (only for cascade tables)
    :param FlasherWidth: the width of the flasher pulse (only for flasher tables)
    :param FlasherBrightness: the brightness of the flasher pulse (only for flasher tables)
    :param Seed: the seed for the random number service
    :param NEvents: the number of events to simulate
    :param RecordErrors: record the squares of weights as well (useful for error bars)
    :param IceModel: the path to an ice model in $I3_BUILD/ice-models/resources/models. Likely values include:
        'spice_mie' ppc-style SPICE-Mie parametrization
    :param DisableTilt: if true, disable tilt in ice model
    :param Filename: the name of the FITS file to write
    :param TabulateImpactAngle: if True, tabulate the impact position of the
           photon on the DOM instead of weighting by the DOM's angular acceptance
    :param Axes: a subclass of :cpp:class:`clsim::tabulator::Axes` that defines the coordinate system.
                 If None, an appropriate default will be chosen based on **PhotonSource**.
    :param Directions: a set of directions to allow table generation for multiple sources.
                 If None, only one direction given by **Zenith** and **Azimuth** is used.
       """

    # check sanity of args
    PhotonSource = PhotonSource.lower()
    if PhotonSource not in ['cascade', 'flasher', 'infinite-muon']:
        raise ValueError("photon source %s is unknown. Please specify either 'cascade', 'flasher', or 'infinite-muon'" % PhotonSource)
    
    from icecube import icetray, dataclasses, dataio, phys_services, sim_services, clsim
    from os.path import expandvars
    
    # a random number generator
    randomService = phys_services.I3GSLRandomService(Seed)
        
    tray.AddModule("I3InfiniteSource",name+"streams",
                   Stream=icetray.I3Frame.DAQ)

    tray.AddModule("I3MCEventHeaderGenerator",name+"gen_header",
                   Year=2009,
                   DAQTime=158100000000000000,
                   RunNumber=1,
                   EventID=1,
                   IncrementEventID=True)

    if Directions is None:
        Directions = numpy.asarray([(Zenith, Azimuth)])

    if PhotonSource in ('cascade', 'flasher', 'muon-segment'):
        
        if PhotonSource == 'muon-segment':
            ptype = I3Particle.ParticleType.MuMinus
        else:
            ptype = I3Particle.ParticleType.EMinus

        def reference_source(zenith, azimuth, scale):
            source = I3Particle()
            source.type = ptype
            source.energy = Energy*scale
            source.pos = I3Position(0., 0., ZCoordinate)
            source.dir = I3Direction(zenith, azimuth)
            source.time = 0.
            if PhotonSource == 'muon-segment':
                source.length = 3.
            else:
                source.length = 0.
            source.location_type = I3Particle.LocationType.InIce
        
            return source
    
    elif PhotonSource == 'infinite-muon':
        
        from icecube import MuonGun
        # pad depth to ensure that the track appears effectively infinite
        surface = MuonGun.Cylinder(1800, 800)
        # account for zenith-dependent distribution of track lengths
        length_scale = surface.area(dataclasses.I3Direction(0, 0))/surface.area(dataclasses.I3Direction(Zenith, 0))
        
        ptype = I3Particle.ParticleType.MuMinus
        
        def reference_source(zenith, azimuth, scale):
            source = I3Particle()
            source.type = ptype
            source.energy = Energy*scale
            source.dir = I3Direction(zenith, azimuth)
            source.pos = surface.sample_impact_position(source.dir, randomService)
            crossings = surface.intersection(source.pos, source.dir)
            source.length = crossings.second-crossings.first
            source.time = 0.
            source.location_type = I3Particle.LocationType.InIce
            
            return source
    
    import copy
    
    class MakeParticle(icetray.I3Module):
        def __init__(self, ctx):
            super(MakeParticle,self).__init__(ctx)
            self.AddOutBox("OutBox")
            self.AddParameter("SourceFunction", "", lambda : None)
            self.AddParameter("NEvents", "", 100)
        def Configure(self):
            self.reference_source = self.GetParameter("SourceFunction")
            self.nevents = self.GetParameter("NEvents")
            self.emittedEvents = 0
        def DAQ(self, frame):
            if PhotonSource != "flasher":
                primary = I3Particle()
                mctree = I3MCTree()
                mctree.add_primary(primary)
                for zenith, azimuth in Directions:
                    source = self.reference_source(zenith, azimuth, 1./len(Directions))
                    mctree.append_child(primary, source)
                frame["I3MCTree"] = mctree
                # use the emitting particle as a geometrical reference
                frame["ReferenceParticle"] = source
            else:
                pulseseries = I3CLSimFlasherPulseSeries()
                for zenith, azimuth in Directions:
                    pulse = makeFlasherPulse(0, 0, ZCoordinate, zenith, azimuth, FlasherWidth, FlasherBrightness, 1./len(Directions))
                    pulseseries.append(pulse)
                frame["I3FlasherPulseSeriesMap"] = pulseseries
                frame["ReferenceParticle"] = self.reference_source(Zenith, Azimuth, 1.)
            
            self.PushFrame(frame)
            
            self.emittedEvents += 1
            if self.emittedEvents >= self.nevents:
                self.RequestSuspension()

    tray.AddModule(MakeParticle, SourceFunction=reference_source, NEvents=NEvents)

    if PhotonSource == "flasher":
        flasherpulse = "I3FlasherPulseSeriesMap"
        mctree = None
    else:
        flasherpulse = None
        mctree = "I3MCTree"
    
    header = dict(FITSTable.empty_header)
    header['zenith'] = Zenith/I3Units.degree
    header['azimuth'] = Azimuth/I3Units.degree
    header['z'] = ZCoordinate
    header['energy'] = Energy
    header['type'] = int(ptype)
    header['efficiency'] = Efficiency.RECEIVER | Efficiency.WAVELENGTH
    if PhotonSource == 'infinite-muon':
        header['n_events'] = length_scale*NEvents/float(PhotonPrescale)
    
    if Axes is None:
        if PhotonSource != "infinite-muon":
            dims = [
                clsim.tabulator.PowerAxis(0, 580, 200, 2),
                clsim.tabulator.LinearAxis(0, 180, 36),
                clsim.tabulator.LinearAxis(-1, 1, 100),
                clsim.tabulator.PowerAxis(0, 7e3, 105, 2),
            ]
            geo = clsim.tabulator.SphericalAxes
        else:
            dims = [
                clsim.tabulator.PowerAxis(0, 580, 100, 2),
                clsim.tabulator.LinearAxis(0, numpy.pi, 36),
                clsim.tabulator.LinearAxis(-8e2, 8e2, 80),
                clsim.tabulator.PowerAxis(0, 7e3, 105, 2),
            ]
            geo = clsim.tabulator.CylindricalAxes
        # Add a dimension for the impact angle
        if TabulateImpactAngle:
            dims.append(clsim.tabulator.LinearAxis(-1, 1, 20))
        Axes = geo(dims)

    if PhotonSource == "flasher":
        header['flasherwidth'] = FlasherWidth
        header['flasherbrightness'] = FlasherBrightness
    
    # some constants
    DOMRadius = 0.16510*icetray.I3Units.m # 13" diameter
    referenceArea = dataclasses.I3Constants.pi*DOMRadius**2
    
    # NB: GetIceCubeDOMAcceptance() calculates the quantum efficiency by
    #     dividing the geometric area (a circle of radius domRadius) by the
    #     tabulated effective area. Scaling that radius by *sqrt(prescale)*
    #     _reduces_ the effective quantum efficiency by a factor *prescale*. 
    #     Since we draw photons directly from the QE-weighted Cherenkov
    #     spectrum, this causes *prescale* fewer photons to be progagated per
    #     light source. We compensate by dividing the number of events by
    #     *prescale* in the header above.
    #     to be propagated per light source.
    domAcceptance = clsim.GetIceCubeDOMAcceptance(domRadius=math.sqrt(PhotonPrescale)*DOMRadius)
    
    if Sensor.lower() == 'dom':
        angularAcceptance = clsim.GetIceCubeDOMAngularSensitivity(holeIce=expandvars("$I3_SRC/ice-models/resources/models/angsens/as.h2-50cm"))
    elif Sensor.lower() == 'degg':
        referenceArea = dataclasses.I3Constants.pi*(300.*I3Units.mm/2)**2
        angularAcceptance = Gen2Sensors.GetDEggAngularSensitivity(pmt='both')
        domAcceptance = Gen2Sensors.GetDEggAcceptance(active_fraction=1./PhotonPrescale)
    elif Sensor.lower() == 'wom':
       # outer diameter of the pressure vessel is 11.4 cm, walls are 9 mm thick
       referenceArea = (11-2*0.9)*90*icetray.I3Units.cm2
       angularAcceptance = Gen2Sensors.GetWOMAngularSensitivity()
       domAcceptance = Gen2Sensors.GetWOMAcceptance(active_fraction=1./PhotonPrescale)
       
    else:
        raise ValueError("Don't know how to simulate %ds yet" % (sensor))

    tray.AddSegment(I3CLSimTabulatePhotons, name+"makeCLSimPhotons",
        MCTreeName = mctree,                        # if source is a cascade this will point to the I3MCTree
        FlasherPulseSeriesName = flasherpulse,      # if source is a flasher this will point to the I3CLSimFlasherPulseSeries
        MMCTrackListName = None,                    # do NOT use MMC
        ParallelEvents = 1,                         # only work at one event at a time (it'll take long enough)
        RandomService = randomService,
        # UnWeightedPhotons=True,
        UseGPUs=False,                              # table-making is not a workload particularly suited to GPUs
        UseCPUs=True,                               # it should work fine on CPUs, though
        Area=referenceArea,
        WavelengthAcceptance=domAcceptance,
        AngularAcceptance=angularAcceptance,
        DoNotParallelize=True,                      # no multithreading
        UseGeant4=False,
        OverrideApproximateNumberOfWorkItems=1,     # if you *would* use multi-threading, this would be the maximum number of jobs to run in parallel (OpenCL is free to split them)
        ExtraArgumentsToI3CLSimModule=dict(Filename=Filename, TableHeader=header,
            Axes=Axes, PhotonsPerBunch=200, EntriesPerPhoton=5000, RecordErrors=RecordErrors),
        MediumProperties=parseIceModel(expandvars("$I3_BUILD/ice-models/resources/models/" + IceModel), disableTilt=DisableTilt),
    )