def GetKM3NeTOMQuantumEfficiency(peakQE=None, wpdQE=False): """ A function to return the quantum efficiency as instance of I3CLSimFunctionFromTable """ if peakQE is None: if wpdQE: peakQE = 0.304 else: peakQE = 0.32 if wpdQE: # this is taken straight from the WPD document QEscale = peakQE / 0.304 QEvals = [ 0.0, 0.0, 0.5, 3.1, 9.8, 17.5, 23.2, 26.5, 28.1, 28.1, 29.1, 30.1, 30.4, 30.1, 29.9, 29.3, 28.6, 27.5, 26.5, 25.0, 23.2, 21.1, 19.6, 18.5, 17.2, 15.4, 12.1, 9.3, 7.2, 6.2, 4.6, 3.6, 2.8, 2.1, 1.3, 0.8, 0.5, 0.3, 0.0, 0.0 ] QEvals = numpy.array(QEvals) * 0.01 * QEscale return I3CLSimFunctionFromTable(260. * I3Units.nanometer, 10. * I3Units.nanometer, QEvals) else: # this is the version we used before the WPD document QEscale = peakQE QEvals = [0.00, 0.87, 1.00, 0.94, 0.78, 0.49, 0.24, 0.09, 0.02, 0.00] QEvals = numpy.array(QEvals) * QEscale return I3CLSimFunctionFromTable(250. * I3Units.nanometer, 50. * I3Units.nanometer, QEvals)
def GetWOMAcceptance(active_fraction=1.): """ :param active_fraction: the fraction of the head-on geometric area that is photosensitive (i.e. the ratio of the area of the wavelength-shifting) tube to the area of the housing. """ # probability that Cherenkov photon directly incident on the wavelength- # shifting paint is absorbed, and that the shifted photon is captured in # the plexiglass tube. # Pers. comm., D. Hebecker, April 2016 capture_efficiency = [0.0, 0.34587, 0.45655, 0.48452, 0.46706, 0.47998, 0.48761, 0.48948, 0.49017, 0.4905, 0.49127, 0.49325, 0.4966, 0.49651, 0.4857, 0.40011, 0.15273, 0.00779, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] # KM3NeT PMT QE, weighted with the emission spectrum of the wavelength # shifter recapture_efficiency = 0.2403 return I3CLSimFunctionFromTable(245*I3Units.nanometer, 10*I3Units.nanometer, [a*recapture_efficiency*active_fraction for a in capture_efficiency])
def GetIceCubeDOMAcceptance(domRadius=0.16510 * I3Units.m, efficiency=1.0, highQE=False): """ this is taken from photonics/lib/efficiency.h: Adopted into photonics Juli 1 2007 /Johan. Interpolation of data from Kotoyo Hoshina <*****@*****.**>: Jan 15 K.Hoshina : Found a wrong value at 260nm whicn I didn't offer in July 1st 2007. Copied a value from 270nm. 0.0002027029 -> 0.0000064522 ROMEO wavelength effective area This is the table of photo-electron acceptance of the IceCube PMT after through the glass+gel + PMT photocathode, as a function of wavelength. It corresponds to a 0p.e. threshold. The injection angle (off-axis angle) is 0deg. The acceptances are calculated by: acceptance = NPEs generated by photo-cathode (0P.E threshold)\ / Nphotons_inject_to_1m^2 """ dom2007a_eff_area = [ 0.0000064522, 0.0000064522, 0.0000064522, 0.0000064522, 0.0000021980, 0.0001339040, 0.0005556810, 0.0016953000, 0.0035997000, 0.0061340900, 0.0074592700, 0.0090579800, 0.0099246700, 0.0105769000, 0.0110961000, 0.0114214000, 0.0114425000, 0.0111527000, 0.0108086000, 0.0104458000, 0.0099763100, 0.0093102500, 0.0087516600, 0.0083225800, 0.0079767200, 0.0075625100, 0.0066377000, 0.0053335800, 0.0043789400, 0.0037583500, 0.0033279800, 0.0029212500, 0.0025334900, 0.0021115400, 0.0017363300, 0.0013552700, 0.0010546600, 0.0007201020, 0.0004843820, 0.0002911110, 0.0001782310, 0.0001144300, 0.0000509155 ] dom2007a_eff_area = numpy.array( dom2007a_eff_area ) * I3Units.meter2 # apply units (this is an effective area) domArea = math.pi * domRadius**2. dom2007a_efficiency = efficiency * (dom2007a_eff_area / domArea) if highQE: wv, rde = numpy.loadtxt( expandvars( '$I3_BUILD/ice-models/resources/models/wavelength/wv.rde')).T dom2007a_efficiency *= numpy.interp( 260 + 10 * numpy.arange(len(dom2007a_efficiency)), wv, rde) domEfficiency = I3CLSimFunctionFromTable(260. * I3Units.nanometer, 10. * I3Units.nanometer, dom2007a_efficiency) return domEfficiency
def GetIceCubeFlasherSpectrum(spectrumType = I3CLSimFlasherPulse.FlasherPulseType.LED405nm): if spectrumType in [I3CLSimFlasherPulse.FlasherPulseType.SC1, I3CLSimFlasherPulse.FlasherPulseType.SC2]: # special handling for Standard Candles: # these currently have single-wavelength spectra spectrum = I3CLSimFunctionDeltaPeak(337.*I3Units.nanometer) return spectrum else: data = GetIceCubeFlasherSpectrumData(spectrumType) spectrum = I3CLSimFunctionFromTable(data[0], data[1]) return spectrum
def GetAntaresOMGelAbsorptionLength(): """ A function to return the absorption length the gel of an ANTARES OM Note: The file hit-ini_optic.f has three different datasets for this absorption length! However in the file hit.f it always is initialized with the same (gel_id=1). Thus this one is implemented here. """ # Data copied from the km3 file hit-ini_optic.f # GEL WACKER (default) al_gel_default_reverse = [ 100.81, # at 610 nm 99.94, # at 600 nm 99.89, 96.90, 96.42, 94.36, 89.09, 90.10, 86.95, 85.88, 84.49, 81.08, 78.18, 76.48, 74.55, 72.31, 68.05, 66.91, 64.48, 62.53, 59.38, 56.64, 53.29, 48.96, 45.71, 41.88, 37.14, 30.49, 23.08, 15.60, 8.00, 0.00 # at 300 nm ] # Apply units al_gel_default_reverse = [(i * I3Units.cm) for i in al_gel_default_reverse] al_gel_default_reverse.reverse() # reverse the list (in-place) return I3CLSimFunctionFromTable(300. * I3Units.nanometer, 10. * I3Units.nanometer, al_gel_default_reverse)
def GetAntaresOMGlassAbsorptionLength(): """ A function to return the absoprtion length of the glass sphere of an ANTARES OM """ # Data copied from the km3 file hit-ini_optic.f # as measured by Pavel # the last 3 bins contain measurements from Saclay (6/3/98) al_glass_reverse = [ 148.37, # at 610 nm 142.87, # at 600 nm 135.64, 134.58, 138.27, 142.40, 147.16, 151.80, 150.88, 145.68, 139.70, 126.55, 118.86, 113.90, 116.08, 109.23, 81.63, 65.66, 77.30, 73.02, 81.25, 128.04, 61.84, 19.23, 27.21, 18.09, 8.41, 3.92, 1.82, 0.84, 0.39, 0.17 # at 300 nm ] # Apply units al_glass_reverse = [(i * I3Units.cm) for i in al_glass_reverse] al_glass_reverse.reverse() # reverse the list (in-place) return I3CLSimFunctionFromTable(300. * I3Units.nanometer, 10. * I3Units.nanometer, al_glass_reverse)
def MakeAntaresMediumProperties(): m = I3CLSimMediumProperties(mediumDensity=1.039 * I3Units.g / I3Units.cm3, layersNum=1, layersZStart=-310. * I3Units.m, layersHeight=2500. * I3Units.m, rockZCoordinate=-310. * I3Units.m, airZCoordinate=2500. * I3Units.m - 310. * I3Units.m) antaresScatModel = GetAntaresScatteringCosAngleDistribution() m.SetScatteringCosAngleDistribution(antaresScatModel) # no ice/water anisotropy. all of these three are no-ops m.SetDirectionalAbsorptionLengthCorrection(I3CLSimScalarFieldConstant(1.)) m.SetPreScatterDirectionTransform(I3CLSimVectorTransformConstant()) m.SetPostScatterDirectionTransform(I3CLSimVectorTransformConstant()) # no "ice" tilt m.SetIceTiltZShift(I3CLSimScalarFieldConstant(0.)) antaresScattering = I3CLSimFunctionScatLenPartic( volumeConcentrationSmallParticles=0.0075 * I3Units.perMillion, volumeConcentrationLargeParticles=0.0075 * I3Units.perMillion) antaresPhaseRefIndex = I3CLSimFunctionRefIndexQuanFry( pressure=215.82225 * I3Units.bar, temperature=13.1, salinity=38.44 * I3Units.perThousand) # these are for ANTARES (mix of Smith&Baker water and Antares site measurements) # absorption lengths starting from 290nm in 10nm increments absLenTable = [ 4.65116279, 7.1942446, 9.17431193, 10.57082452, 12.62626263, 14.08450704, 15.89825119, 18.93939394, 21.14164905, 24.09638554, 27.54820937, 30.76923077, 34.36426117, 39.21568627, 42.19409283, 45.87155963, 50., 52.35602094, 54.94505495, 54.94505495, 51.02040816, 38.91050584, 28.01120448, 20.96436059, 19.72386588, 17.92114695, 15.67398119, 14.12429379, 12.51564456, 9.25925926, 6.36942675, 4.09836066, 3.46020761 ] antaresAbsorption = I3CLSimFunctionFromTable(290. * I3Units.nanometer, 10. * I3Units.nanometer, absLenTable) m.SetAbsorptionLength(0, antaresAbsorption) m.SetScatteringLength(0, antaresScattering) m.SetPhaseRefractiveIndex(0, antaresPhaseRefIndex) return m
def GetAntaresOMQuantumEfficiency(): """ A function to return the quantum efficiency as instance of I3CLSimFunctionFromTable """ # Data copied from the km3 file hit-ini_optic.f # BB5912 from Hamamatsu q_eff_0 = 0.01 q_eff_reverse = [ 1.988, # at 610 nm 2.714, # at 600 nm 3.496, 4.347, 5.166, 6.004, 6.885, 8.105, 10.13, 13.03, 15.29, 16.37, 17.11, 17.86, 18.95, 20.22, 21.26, 22.10, 22.65, 23.07, 23.14, 23.34, 22.95, 22.95, 22.74, 23.48, 22.59, 20.61, 17.68, 13.18, 7.443, 2.526 # at 300 nm ] q_eff_reverse = [(q_eff_0 * i) for i in q_eff_reverse] q_eff_reverse.reverse() # reverse the list (in-place) return I3CLSimFunctionFromTable(300. * I3Units.nanometer, 10. * I3Units.nanometer, q_eff_reverse)
def GetAntaresOMAcceptance(domRadius=0.2159 * I3Units.m): # 17 inch diameter """ The main function to return the effective area of the Antares OM """ # Load the constants glass_width = GetAntaresOMGlassThickness() gel_width = GetAntaresOMGelThickness() pmt_collection_efficiency = GetAntaresPMTCollectionEfficiency() pmt_diameter = GetAntaresPMTDiameter() # Geometrical area of the om profile pmt_area = math.pi * (pmt_diameter / 2.)**2 #is im square meters om_area = math.pi * domRadius**2. # Get the tables from above q_eff = GetAntaresOMQuantumEfficiency() abs_glass = GetAntaresOMGlassAbsorptionLength() abs_gel = GetAntaresOMGelAbsorptionLength() # Each of the tables above has 32 bins in the wavelength range of 300nm - 610nm, # where the exact value is at the beginning of each bin, means # value of bin 0 belongs to 300nm # value of bin 1 belongs to 310nm # value of bin 31 belongs to 610nm # Now combine them om_eff_area = [ 0. ] # use a single entry at 290nm to have the same range as other functions(wlen) for wavelength in range(300, 611, 10): this_abs_glass = abs_glass.GetValue(wavelength * I3Units.nanometer) this_abs_gel = abs_gel.GetValue(wavelength * I3Units.nanometer) if (this_abs_glass <= 0.) or (this_abs_gel <= 0.): current_om_eff_area = 0. else: current_om_eff_area = pmt_area * \ pmt_collection_efficiency * \ q_eff.GetValue(wavelength*I3Units.nanometer) * \ math.exp( -( glass_width / this_abs_glass ) ) * \ math.exp( -( gel_width / this_abs_gel ) ) om_eff_area.append(current_om_eff_area) return I3CLSimFunctionFromTable(290. * I3Units.nanometer, 10. * I3Units.nanometer, numpy.array(om_eff_area) / om_area)
def MakeIceCubeMediumPropertiesPhotonics(tableFile, detectorCenterDepth = 1948.07*I3Units.m): ### read ice information from a photonics-compatible table file = open(tableFile, 'r') rawtable = file.readlines() file.close() del file # get rid of comments and split lines at whitespace parsedtable = [line.split() for line in rawtable if line.lstrip()[0] != "#"] nlayer_lines = [line for line in parsedtable if line[0].upper()=="NLAYER"] nwvl_lines = [line for line in parsedtable if line[0].upper()=="NWVL"] if len(nlayer_lines) == 0: raise RuntimeError("There is no \"NLAYER\" entry in your ice table!") if len(nlayer_lines) > 1: raise RuntimeError("There is more than one \"NLAYER\" entry in your ice table!") if len(nwvl_lines) == 0: raise RuntimeError("There is no \"NWVL\" entry in your ice table!") if len(nwvl_lines) > 1: raise RuntimeError("There is more than one \"NWVL\" entry in your ice table!") nLayers = int(nlayer_lines[0][1]) nWavelengths = int(nwvl_lines[0][1]) startWavelength = float(nwvl_lines[0][2])*I3Units.nanometer stepWavelength = float(nwvl_lines[0][3])*I3Units.nanometer startWavelength += stepWavelength/2. # make a list of wavelengths wavelengths = numpy.linspace(start=startWavelength, stop=startWavelength+float(nWavelengths)*stepWavelength, num=nWavelengths, endpoint=False) print("The table file {0} has {1} layers and {2} wavelengths, starting from {3}ns in {4}ns steps".format(tableFile, nLayers, nWavelengths, startWavelength/I3Units.nanometer, stepWavelength/I3Units.nanometer)) # replace parsedtable with a version without NLAYER and NWVL entries parsedtable = [line for line in parsedtable if ((line[0].upper()!="NLAYER") and (line[0].upper()!="NWVL"))] if len(parsedtable) != nLayers*6: raise RuntimeError("Expected {0}*6={1} lines [not counting NLAYER and NWVL] in the icetable file, found {2}".format(nLayers, nLayers*6, len(parsedtable))) if parsedtable[0][0].upper() != "LAYER": raise RuntimeError("Layer definitions should start with the LAYER keyword (reading {0})".format(tableFile)) # parse the lines layers = [] layerdict = dict() for line in parsedtable: keyword = line[0].upper() if keyword == "LAYER": if len(layerdict) != 0: layers.append(layerdict) #re-set layerdict layerdict = dict() else: # some other keyword if keyword in layerdict: raise RuntimeError("Keyword {0} is used twice for one layer (reading {1})".format(keyword, tableFile)) # optional units unit = 1. # no units by default if keyword == "LAYER": unit = I3Units.meter # coordinates if keyword == "ABS": unit = 1./I3Units.meter # absorption coefficient if keyword == "SCAT": unit = 1./I3Units.meter # scattering coefficient layerdict[keyword] = [float(number)*unit for number in line[1:]] # last layer if len(layerdict) != 0: layers.append(layerdict) if len(layers) < 1: raise RuntimeError("At least one layers is requried (reading {0})".format(tableFile)) layerHeight = abs(layers[0]['LAYER'][1] - layers[0]['LAYER'][0]) print("layer height is {0}m".format(layerHeight/I3Units.m)) # sort layers into dict by bottom z coordinate and check some assumptions layersByZ = dict() for layer in layers: layerBottom = layer['LAYER'][0] layerTop = layer['LAYER'][1] if layerBottom > layerTop: print("a layer is upside down. compensating. (reading {0})".format(tableFile)) dummy = layerBottom layerBottom = layerTop layerTop = dummy # check layer height if abs((layerTop-layerBottom) - layerHeight) > 0.0001: raise RuntimeError("Differing layer heights while reading {0}. Expected {1}m, got {2}m.".format(tableFile, layerHeight, layerTop-layerBottom)) layersByZ[layerBottom] = layer layers = [] endZ = None for dummy, layer in sorted(layersByZ.items()): startZ = layer['LAYER'][0] if (endZ is not None) and (abs(endZ-startZ) > 0.0001): raise RuntimeError("Your layers have holes. previous layer ends at {0}m, next one starts at {1}m".format(endZ/I3Units.m, startZ/I3Units.m)) endZ = layer['LAYER'][1] layers.append(layer) # check even more assumptions meanCos=None for layer in layers: if meanCos is None: meanCos = layer['COS'][0] for cosVal in layer['COS']: if abs(cosVal-meanCos) > 0.0001: raise RuntimeError("only a constant mean cosine is supported by clsim (expected {0}, got {1})".format(meanCos, cosVal)) if len(layer['COS']) != len(wavelengths): raise RuntimeError("Expected {0} COS values, got {1}".format(len(wavelengths), len(layer['COS']))) if len(layer['ABS']) != len(wavelengths): raise RuntimeError("Expected {0} ABS values, got {1}".format(len(wavelengths), len(layer['ABS']))) if len(layer['SCAT']) != len(wavelengths): raise RuntimeError("Expected {0} SCAT values, got {1}".format(len(wavelengths), len(layer['SCAT']))) if len(layer['N_GROUP']) != len(wavelengths): raise RuntimeError("Expected {0} N_GROUP values, got {1}".format(len(wavelengths), len(layer['N_GROUP']))) if len(layer['N_PHASE']) != len(wavelengths): raise RuntimeError("Expected {0} N_PHASE values, got {1}".format(len(wavelengths), len(layer['N_PHASE']))) for i in range(len(wavelengths)): if abs(layer['N_GROUP'][i]-layers[0]['N_GROUP'][i]) > 0.0001: raise RuntimeError("N_GROUP may not be different for different layers in this version of clsim!") if abs(layer['N_PHASE'][i]-layers[0]['N_PHASE'][i]) > 0.0001: raise RuntimeError("N_PHASE may not be different for different layers in this version of clsim!") #print "start", layer['LAYER'][0], "end", layer['LAYER'][1] ##### start making the medium property object m = I3CLSimMediumProperties(mediumDensity=0.9216*I3Units.g/I3Units.cm3, layersNum=len(layers), layersZStart=layers[0]['LAYER'][0], layersHeight=layerHeight, rockZCoordinate=-870.*I3Units.m, # TODO: inbetween: from 1740 upwards: less dense ice (factor 0.825) airZCoordinate=1940.*I3Units.m) iceCubeScatModel = I3CLSimRandomValueHenyeyGreenstein(meanCosine=meanCos) m.SetScatteringCosAngleDistribution(iceCubeScatModel) # no ice/water anisotropy. all of these three are no-ops m.SetDirectionalAbsorptionLengthCorrection(I3CLSimScalarFieldConstant(1.)) m.SetPreScatterDirectionTransform(I3CLSimVectorTransformConstant()) m.SetPostScatterDirectionTransform(I3CLSimVectorTransformConstant()) # no ice tilt m.SetIceTiltZShift(I3CLSimScalarFieldConstant(0.)) phaseRefIndex = I3CLSimFunctionFromTable(startWavelength, stepWavelength, layers[0]['N_PHASE']) groupRefIndex = I3CLSimFunctionFromTable(startWavelength, stepWavelength, layers[0]['N_GROUP']) #phaseRefIndex = I3CLSimFunctionRefIndexIceCube(mode="phase") #groupRefIndex = I3CLSimFunctionRefIndexIceCube(mode="group") for i in range(len(layers)): #print "layer {0}: depth at bottom is {1} (z_bottom={2}), b_400={3}".format(i, depthAtBottomOfLayer[i], layerZStart[i], b_400[i]) m.SetPhaseRefractiveIndex(i, phaseRefIndex) m.SetGroupRefractiveIndexOverride(i, groupRefIndex) absLenTable = [1./absCoeff for absCoeff in layers[i]['ABS']] absLen = I3CLSimFunctionFromTable(startWavelength, stepWavelength, absLenTable, storeDataAsHalfPrecision=True) m.SetAbsorptionLength(i, absLen) scatLenTable = [(1./scatCoeff)*(1.-meanCos) for scatCoeff in layers[i]['SCAT']] scatLen = I3CLSimFunctionFromTable(startWavelength, stepWavelength, scatLenTable, storeDataAsHalfPrecision=True) m.SetScatteringLength(i, scatLen) return m
def GetDEggAcceptance(active_fraction=1.): """ :param active_fraction: the fraction of the head-on geometric area that is photosensitive (i.e. the ratio of the photocathode area to the geometric area) """ # Combined efficiency for D-Egg glass (10 mm), high-UV transparency gel # (5 mm), and Hamamatsu R5912-100 at the center of the photocathode # Pers. comm., Lu Lu, April 2016 logging.log_warn("DeprecationWarning: The numbers here are outdated! Also this functionality is now part of the 'mDOM-WOM-simulation' project. Please check it out!") center_efficiency = [0.0, 0.0, 0.0, 0.0005, 0.0093, 0.058, 0.1473, 0.2358, 0.2904, 0.3139, 0.3237, 0.3336, 0.339, 0.3373, 0.3292, 0.3195, 0.3087, 0.3017, 0.2873, 0.2717, 0.2532, 0.2305, 0.2119, 0.1962, 0.1832, 0.1708, 0.1523, 0.1227, 0.0928, 0.0728, 0.0597, 0.0494, 0.0404, 0.0318, 0.0241, 0.0174, 0.0118, 0.0076, 0.0047, 0.0027, 0.0, 0.0, 0.0, 0.0] # Hamamatsu quotes a minimum photocathode diameter of 190 mm. Pending a real # 2D average of the capture efficiency, approximate with 90% of the center # efficiency times the minimum photocathode area. active_fraction *= 0.9*(190./300.)**2 return I3CLSimFunctionFromTable(250*I3Units.nanometer, 10*I3Units.nanometer, [a*active_fraction for a in center_efficiency])
def GetKM3NeTDOMAcceptance(domRadius=(17. / 2.) * 0.0254 * I3Units.m, peakQE=None, wpdQE=False, withWinstonCone=False): # 17 inch diameter """ The main function to return the effective area of the KM3NeT DOM """ # the multiPMT simulation expects photons on a sphere of 17" diameter. # The result of this function is supposed to be used for emission # spectrum biassing only, so only allow 17" spheres just to be safe. if domRadius != (17. / 2.) * 0.0254 * I3Units.m: raise RuntimeError( "GetKM3NeTDOMAcceptance is currently only valid for a diameter of 17inch." ) # Load the constants glass_width = GetKM3NeTOMGlassThickness() gel_width = GetKM3NeTOMGelThickness() pmt_collection_efficiency = GetKM3NeTPMTCollectionEfficiency() # Get the tables from above q_eff = GetKM3NeTOMQuantumEfficiency(peakQE=peakQE, wpdQE=wpdQE) abs_glass = GetKM3NeTOMGlassAbsorptionLength() abs_gel = GetKM3NeTOMGelAbsorptionLength() if withWinstonCone: coneScaler = 2.0 # correction factor at cos(theta)=1 else: coneScaler = 1. # Each of the tables above has 32 bins in the wavelength range of 300nm - 610nm, # where the exact value is at the beginning of each bin, means # value of bin 0 belongs to 300nm # value of bin 1 belongs to 310nm # value of bin 31 belongs to 610nm # Now combine them om_eff_area = [ 0. ] # use a single entry at 290nm to have the same range as other functions(wlen) for wavelength in range(300, 611, 10): this_abs_glass = abs_glass.GetValue(wavelength * I3Units.nanometer) this_abs_gel = abs_gel.GetValue(wavelength * I3Units.nanometer) if (this_abs_glass <= 0.) or (this_abs_gel <= 0.): current_om_eff_area = 0. else: current_om_eff_area = pmt_collection_efficiency * \ q_eff.GetValue(wavelength*I3Units.nanometer) * \ coneScaler # do not include these two (they assume a fixed photon path length through glass which # currently is not the smallest possible length.. #math.exp( -( glass_width / this_abs_glass ) ) * \ #math.exp( -( gel_width / this_abs_gel ) ) om_eff_area.append(current_om_eff_area) return I3CLSimFunctionFromTable(290. * I3Units.nanometer, 10. * I3Units.nanometer, numpy.array(om_eff_area))