示例#1
0
class LumiDumper:
    def __init__(self):

        # Control output level
        self.verbose = True

        # Channel to chose (preferred by default)
        self.lumiChan = 0

        # Run list
        self.runList = []

        # Luminosity tag
        self.lumiTag = 'OflLumi-7TeV-002'

        # Use online instead
        self.online = False

        # Instantiate the LumiDBHandler, so we can cleanup all COOL connections in the destructor
        self.dbHandler = LumiDBHandler()

        # Output file (default stdout)
        self.outFile = None

        # Output directory (default none - pwd)
        self.outDir = None

        # Write stable beams only
        self.checkStable = True

        # Predefine data readers
        self.lumi = None
        self.fill = None
        self.ardy = None
        self.lhc = None
        self.lblb = None

    # Explicitly clean up our DB connections
    def __del__(self):
        self.dbHandler.closeAllDB()

    # Called in command-line mode
    def execute(self):

        # Handle command-line switches
        self.parseOpts()

        # Process each run in the runlist
        for runnum in self.runList:

            # Read all COOL data
            self.readData(runnum)

            self.printData(runnum)

    def parseOpts(self):

        parser = OptionParser(usage="usage: %prog [options]",
                              add_help_option=False)

        parser.add_option("-?",
                          "--usage",
                          action="store_true",
                          default=False,
                          dest="help",
                          help="show this help message and exit")

        parser.add_option("-v",
                          "--verbose",
                          action="store_true",
                          default=self.verbose,
                          dest="verbose",
                          help="turn on verbose output")

        parser.add_option(
            "-r",
            "--run",
            dest="runlist",
            metavar="RUN",
            help="Specific run, range, or comma-separated list of both")

        parser.add_option("--channel",
                          dest='chan',
                          metavar='N',
                          default=self.lumiChan,
                          help='specify luminosity channel (default: %d)' %
                          self.lumiChan)

        parser.add_option("--lumiTag",
                          dest='lumitag',
                          metavar='TAG',
                          default=self.lumiTag,
                          help='specify luminosity tag (default: %s)' %
                          self.lumiTag)

        parser.add_option("--online",
                          action='store_true',
                          default=self.online,
                          dest='online',
                          help='use online luminosity (default: use offline)')

        parser.add_option("--outDir",
                          dest='outdir',
                          metavar='DIR',
                          default=self.outDir,
                          help='change default output directory')

        parser.add_option(
            "--noStable",
            action="store_false",
            default=self.checkStable,
            dest="checkstable",
            help="write non-stable beam luminosity (default: stable only)")

        (options, args) = parser.parse_args()

        if options.help:
            parser.print_help()
            sys.exit()

        if options.verbose: self.verbose = options.verbose

        self.lumiChan = int(options.chan)
        self.lumiTag = options.lumitag
        self.online = options.online
        self.outDir = options.outdir
        self.checkStable = options.checkstable

        # Parse run list
        if options.runlist != None:

            # Clear the old one
            self.runList = []

            # Can be comma-separated list of run ranges
            runlist = options.runlist.split(',')
            if len(runlist) == 0:
                print 'Invalid run list specified!'
                sys.exit()

            # Go through and check for run ranges
            for runstr in runlist:
                subrunlist = runstr.split('-')

                if len(subrunlist) == 1:  # Single run
                    self.runList.append(int(subrunlist[0]))

                elif len(subrunlist) == 2:  # Range of runs
                    for runnum in range(int(subrunlist[0]),
                                        int(subrunlist[1]) + 1):
                        self.runList.append(runnum)

                else:  # Too many parameters
                    print 'Invalid run list segment found:', runstr
                    sys.exit()

            self.runList.sort()
            if self.verbose:
                print 'Finished parsing run list:',
                for runnum in self.runList:
                    print runnum,
                print

    def readData(self, runnum):

        # Luminosity folder
        if self.verbose: print 'Reading lumi for run %d' % runnum
        if self.lumi == None:
            if self.online:
                self.lumi = CoolDataReader('COOLONL_TRIGGER/COMP200',
                                           '/TRIGGER/LUMI/LBLESTONL')
            else:
                self.lumi = CoolDataReader('COOLOFL_TRIGGER/COMP200',
                                           '/TRIGGER/OFLLUMI/LBLESTOFL')
                self.lumi.setTag(self.lumiTag)

            self.lumi.setChannelId(self.lumiChan)

        self.lumi.setIOVRangeFromRun(runnum)
        self.lumi.readData()

        if self.verbose:
            print 'Read %d Luminosity records' % len(self.lumi.data)

        # Load stable beam flag (
        if self.verbose: print 'Reading ATLAS ready flag'
        if self.ardy == None:
            self.ardy = LumiDBCache('COOLONL_TDAQ/COMP200',
                                    '/TDAQ/RunCtrl/DataTakingMode')

        self.ardy.reader.setIOVRangeFromRun(runnum)
        self.ardy.reader.readData()

        if self.verbose:
            print 'Read %d ATLAS ready records' % len(self.ardy.reader.data)

        # Load LBLB data (needed to convert to time)
        if self.verbose: print 'Reading LBLB data'
        if self.lblb == None:
            self.lblb = CoolDataReader('COOLONL_TRIGGER/COMP200',
                                       '/TRIGGER/LUMI/LBLB')

        self.lblb.setIOVRangeFromRun(runnum)
        self.lblb.readData()

        if self.verbose:
            print 'Read %d LBLB records' % len(self.lblb.data)
            if len(self.lblb.data) > 0:
                print 'First LB %d/%d' % (self.lblb.data[0].since() >> 32,
                                          self.lblb.data[0].since()
                                          & 0xFFFFFFFF)
                print 'Last  LB %d/%d' % (self.lblb.data[-1].since() >> 32,
                                          self.lblb.data[-1].since()
                                          & 0xFFFFFFFF)

        # Now figure out starting/ending time
        if len(self.lblb.data) < 1:
            print 'No LBLB data found!'
            return  # Nothing to work with

        tlo = self.lblb.data[0].payload()['StartTime']
        thi = self.lblb.data[-1].payload()['EndTime']

        # Fillparams (slow)
        if self.verbose: print 'Reading Fillparams'
        if self.fill == None:
            self.fill = LumiDBCache('COOLONL_TDAQ/COMP200',
                                    '/TDAQ/OLC/LHC/FILLPARAMS')

        self.fill.reader.setIOVRange(tlo, thi)
        self.fill.reader.readData()

        if self.verbose:
            print 'Read %d FILLPARAMS records' % len(self.fill.reader.data)

        # LHC information (for stable beams)
        if self.verbose: print 'Reading LHC information'
        if self.lhc == None:
            self.lhc = LumiDBCache('COOLOFL_DCS/COMP200', '/LHC/DCS/FILLSTATE')

        self.lhc.reader.setIOVRange(tlo, thi)
        self.lhc.reader.readData()

        if self.verbose:
            print 'Read %d LHC records' % len(self.lhc.reader.data)

    def printData(self, runnum):

        # Only proceed if we have actual lumi data
        if len(self.lumi.data) == 0:
            return

        f = None

        # Make time map
        lblbMap = dict()
        for obj in self.lblb.data:
            lblbMap[obj.since()] = (obj.payload()['StartTime'],
                                    obj.payload()['EndTime'])

        # OK, now we want to go through the luminosity records and match the other data
        for obj in self.lumi.data:

            run = obj.since() >> 32
            lb = obj.since() & 0xFFFFFFFF
            startTime = lblbMap.get(obj.since(), (0., 0.))[0]
            endTime = lblbMap.get(obj.since(), (0., 0.))[1]

            lumi = obj.payload()['LBAvInstLumi']
            mu = obj.payload()['LBAvEvtsPerBX']

            payload = self.fill.getPayload(startTime)
            if payload == None:
                ncol = 0
            else:
                ncol = payload['LuminousBunches']

            payload = self.lhc.getPayload(startTime)
            if payload == None:
                stable = 0
            else:
                stable = payload['StableBeams']

            payload = self.ardy.getPayload(obj.since())
            if self.ardy == None:
                ready = 0
            else:
                ready = payload['ReadyForPhysics']

            if self.checkStable and not stable: continue

            # Open file if not open already
            if f == None:

                # Open
                if self.outDir != None:
                    self.outFile = '%s/run%d.txt' % (self.outDir, runnum)
                else:
                    self.outFile = 'run%d.txt' % runnum

                print 'Writing file %s' % self.outFile
                f = open(self.outFile, 'w')

            # OK, print it out
            print >> f, run, lb, startTime / 1.E9, endTime / 1.E9, lumi, mu, ncol, ready

        # Close file
        if f != None:
            f.close()
class OflLumiMaker:
    def __init__(self):

        # Control output level
        self.verbose = False

        # Output root file name
        self.fileName = 'scan.root'

        # Input file with turn information (from Nitesh)
        self.turnFile = None

        # Location of scan data
        self.tdaqDB = 'COOLONL_TDAQ/COMP200'
        self.tdaqMonpDB = 'COOLONL_TDAQ/MONP200'

        self.monp = False  # Use MONP or COMP DB

        self.scanFolder = '/TDAQ/OLC/LHC/SCANDATA'  # vdM Scan parameter data
        self.beamFolder = '/TDAQ/OLC/LHC/BEAMPOSITION'
        self.fillFolder = '/TDAQ/OLC/LHC/FILLPARAMS'
        self.lbdataFolder = '/TDAQ/OLC/LHC/LBDATA'
        self.bunchFolder = '/TDAQ/OLC/LHC/BUNCHDATA'
        self.bunchLumiFolder = '/TDAQ/OLC/BUNCHLUMIS'
        self.lumiFolder = '/TDAQ/OLC/LUMINOSITY'

        # Calendar time range to look for vdM scan data
        self.startTime = '2010-10-01:07:00:00/UTC'
        self.endTime = '2010-10-01:14:00:00/UTC'

        # Time as COOL IOV
        self.startTimeIOV = None
        self.endTimeIOV = None

        self.startLBIOV = None
        self.endLBIOV = None

        # Pointers to CoolDataReader return objects
        self.lhcData = None
        self.lumiData = None
        self.scanData = None
        self.lbdataData = None
        self.bunchdataData = None

        self.ions = False

        # Object to store BCID information
        self.maskObj = BCIDMask()

        # Object to store the bunch group definition
        self.bgObj = BunchGroup()

        # Channels to store
        self.lumiChanList = [
            101, 102, 103, 104, 105, 106, 201, 202, 206, 207, 211, 212, 216,
            217, 221, 222, 226, 1001, 1002, 1004, 1011, 1012, 1014
        ]

    # Called in command-line mode
    def execute(self):

        # Handle command-line switches
        self.parseOpts()

        # Find the scan data
        self.findScanData()

        # Write the data to ntuple
        #nt = ScanNtupleHandler()
        #nt.fileName = self.fileName
        #nt.ions = self.ions

        #nt.open()
        #nt.init()
        #nt.fill(self) # Must pass self reference to get access to data
        #nt.close()

        nt = NtupleHandler()
        nt.chan2011 = False
        nt.chan2012 = True
        nt.fibers = False
        nt.fileName = self.fileName

        nt.open()
        nt.init()

        nfilled = 0

        # Sort raw data and save by [iov][algo]
        rawDict = dict()
        for obj in self.bunchLumi.data:

            channel = obj.channelId()
            iov = obj.since()

            if iov not in rawDict:
                rawDict[iov] = dict()
            rawDict[iov][channel] = obj

        # PMT current data
        pmtDict = dict()
        fibDict = dict()  # Fiber currents
        for obj in self.pmtReader.data:
            if obj.channelId() == 0:
                pmtDict[obj.since()] = obj
            elif obj.channelId() == 1:
                fibDict[obj.since()] = obj
            else:
                print 'Found channel %d in pmtReader!' % obj.channel()

        # Bunch current data
        currDict = dict()
        for obj in self.bunchData.data:
            currDict[obj.since()] = obj

        # Loop over scandata entries
        for plbobj in self.scanData.data:

            iov = plbobj.since()
            iovString = timeString(iov)

            runlb = plbobj.payload()['RunLB']
            run = plbobj.payload()['RunLB'] >> 32
            lb = plbobj.payload()['RunLB'] & 0xFFFFFFFF
            dtime = (plbobj.until() - plbobj.since()) / 1E9

            print '%s (%6d/%3d)  t=%5.2fs  Acq:%5.2f d=%6.3fmm IP:%2d Plane:%2d' % (
                iovString, run, lb, dtime, plbobj.payload()['AcquisitionFlag'],
                plbobj.payload()['NominalSeparation'],
                plbobj.payload()['ScanningIP'],
                plbobj.payload()['ScanInPlane']),

            # Check if beams are moving at IP1
            if run == 0 and plbobj.payload(
            )['ScanningIP'] == 1 and plbobj.payload()['AcquisitionFlag'] < 1.:
                print " --> moving"
                continue
            print

            nt.fillLBData(plbobj)
            nt.lbDataStruct.fStable = True  # Ensure true

            if iov not in rawDict:
                print "Can't find time %s in raw lumi!" % iovString
                continue

            nt.clearBCIDData()

            # Make sure BCID mask is valid
            if not self.updateBCIDMask(iov):
                print "Couldn't find valid BCID mask data for IOV %s!" % iov
                continue

            # Make sure BunchGroup is valid
            #if run > 0 and not self.updateBunchGroup(runlb):
            #    print "Couldn't find valid BunchGroup data for %s (%d/%d)!" % (iov, run, lb)

            if iov in pmtDict:
                nt.lbDataStruct.fPmtA = pmtDict[iov].payload()['CurrentSideA']
                nt.lbDataStruct.fPmtC = pmtDict[iov].payload()['CurrentSideC']
                # print 'Found Run %d LB %d PMTA %f PMTC %f' % (run, lb, nt.lbDataStruct.fPmtA, nt.lbDataStruct.fPmtC)

            if iov in fibDict:
                nt.lbDataStruct.fFibA = fibDict[iov].payload()['CurrentSideA']
                nt.lbDataStruct.fFibC = fibDict[iov].payload()['CurrentSideC']
                # print 'Found Run %d LB %d FIBA %f FIBC %f' % (run, lb, nt.lbDataStruct.fFibA, nt.lbDataStruct.fFibC)

            if iov in currDict:
                nt.fillCurrentData(currDict[iov], self.maskObj)

            for chan in self.lumiChanList:

                if chan not in rawDict[iov]:
                    print "Can't find channel", chan, "in", iovString
                    continue

                rawobj = rawDict[iov][chan]

                if chan != rawobj.channelId():
                    print 'Channel', chan, '!=', rawobj.channelId(), '!'
                    continue

                # Get raw lumi
                normValue = rawobj.payload()['AverageRawInstLum']
                blobValue = rawobj.payload()['BunchRawInstLum']
                bcidVec, rawLumi = unpackBCIDValues(blobValue,
                                                    self.maskObj.coll,
                                                    normValue)

                # dict to hold BCID lumi keyed by BCID
                for i in range(len(rawLumi)):

                    # Check if this is in our bunch group (only really need to fill this once)
                    bcid = bcidVec[i]

                    # Protect against any weird values
                    if bcid >= nt.nBCID:
                        print 'BCID %d found >= %d!' % (bcid, nt.nBCID)
                        continue

                    #if bcid in self.bgObj.bg:
                    #    nt.bcidStruct.fStatus[bcid] = 1
                    #else:
                    #    nt.bcidStruct.fStatus[bcid] = 0

                    # Now need to save bcid, mu, and calibLumi
                    lraw = rawLumi[i]
                    muval = 0.  # Uncalibrated
                    nt.fillBCIDData(chan, bcid, lraw, muval)

                # End of loop over BCIDs

            # End of loop over channels
            nt.tree.Fill()

        # End of loop over IOVs
        nt.close()

    def parseOpts(self):

        parser = OptionParser(usage="usage: %prog [options]",
                              add_help_option=False)

        parser.add_option("-?",
                          "--usage",
                          action="store_true",
                          default=False,
                          dest="help",
                          help="show this help message and exit")

        parser.add_option("-v",
                          "--verbose",
                          action="store_true",
                          default=self.verbose,
                          dest="verbose",
                          help="turn on verbose output")

        parser.add_option("-o",
                          "--output",
                          dest="outfile",
                          metavar="FILE",
                          default=self.fileName,
                          help="specify output ROOT file name - default: " +
                          self.fileName)

        parser.add_option("--startTime",
                          dest="starttime",
                          metavar="TIME",
                          help="set starting time (YYYY-MM-DD:HH:MM:SS)")

        parser.add_option("--endTime",
                          dest="endtime",
                          metavar="TIME",
                          help="set ending time (YYYY-MM-DD:HH:MM:SS)")

        parser.add_option(
            "--ions",
            dest="ions",
            default=self.ions,
            action="store_true",
            help="Use Heavy Ion variable list (not used currently!)")

        parser.add_option(
            "--monp",
            dest="monp",
            default=self.monp,
            action="store_true",
            help=
            "Use MONP200 rather than COMP200 DB for per-bunch lumi - default: "
            + str(self.monp))

        (options, args) = parser.parse_args()

        if options.help:
            parser.print_help()
            sys.exit()

        self.verbose = options.verbose
        self.ions = options.ions
        self.monp = options.monp

        # Parse times
        if options.starttime != None:
            self.startTime = options.starttime
        if options.endtime != None:
            self.endTime = options.endtime

        self.fileName = options.outfile

    def findScanData(self):
        print 'vdMScanMaker.findScanData() called'

        # Based on the (text) starting and ending times, find the available scan entries in COOL

        # First, convert time strings to COOL IOV times
        self.startTimeIOV = timeVal(self.startTime)
        if self.startTimeIOV == None:
            print 'OflLumiMaker.findScanData - Error converting start time', self.startTime, '!'
            sys.exit()

        self.endTimeIOV = timeVal(self.endTime)
        if self.endTimeIOV == None:
            print 'OflLumiMaker.findScanData - Error converting end time', self.endTime, '!'
            sys.exit()

        # Load the scan data
        self.scanData = CoolDataReader(self.tdaqDB, self.scanFolder)
        self.scanData.setIOVRange(self.startTimeIOV, self.endTimeIOV)
        if not self.scanData.readData():
            print 'OflLumiMaker.findScanData - No scan data found in range', self.startTime, 'to', self.endTime, '!'
            sys.exit()

        # for obj in self.scanData.data:
        #     run = obj.payload()['RunLB'] >> 32
        #     lb = obj.payload()['RunLB'] & 0xFFFFFFFF
        #     print '%s (%6d/%3d)  t=%5.2fs  Acq:%5.2f d=%6.3fmm IP:%2d Plane:%2d' % (timeString(obj.since()), run, lb, (obj.until()-obj.since())/1E9, obj.payload()['AcquisitionFlag'], obj.payload()['NominalSeparation'], obj.payload()['ScanningIP'], obj.payload()['ScanInPlane'])

        # Load the beam positions
        # self.beamData = CoolDataReader(self.tdaqDB, self.beamFolder)
        # self.beamData.setIOVRange(self.startTimeIOV, self.endTimeIOV)
        # if not self.beamData.readData():
        #     print 'OflLumiMaker.findScanData - No beam separation data found in range', self.startTime, 'to', self.endTime,'!'
        #     sys.exit()

        # if self.verbose:
        #     for obj in self.beamData.data:
        #         run = obj.payload()['RunLB'] >> 32
        #         lb = obj.payload()['RunLB'] & 0xFFFFFFFF
        #         print run, lb, obj.payload()['B1_PositionAtIP_H'], obj.payload()['B1_PositionAtIP_V'], obj.payload()['B2_PositionAtIP_H'], obj.payload()['B2_PositionAtIP_V']

        # Load the fill parameters
        self.fillData = CoolDataReader(self.tdaqDB, self.fillFolder)
        self.fillData.setIOVRange(self.startTimeIOV, self.endTimeIOV)
        if not self.fillData.readData():
            print 'OflLumiMaker.findScanData - No fill parameters data found in range', self.startTime, 'to', self.endTime, '!'
            sys.exit()

        if self.verbose:
            for obj in self.fillData.data:
                print obj.payload()['Beam1Bunches'], obj.payload(
                )['Beam2Bunches'], obj.payload()['LuminousBunches']

        # Load the lbdata parameters
        self.lbdataData = CoolDataReader(self.tdaqDB, self.lbdataFolder)
        self.lbdataData.setIOVRange(self.startTimeIOV, self.endTimeIOV)
        if not self.lbdataData.readData():
            print 'OflLumiMaker.findScanData - No LBDATA data found in range', self.startTime, 'to', self.endTime, '!'
            sys.exit()

        if self.verbose:
            for obj in self.lbdataData.data:
                print 'LBDATA', obj.channelId(), obj.payload(
                )['Beam1Intensity'], obj.payload()['Beam2Intensity']

        # Load the BUNCHDATA parameters
        if self.monp:
            self.bunchData = CoolDataReader(self.tdaqMonpDB, self.bunchFolder)
        else:
            self.bunchData = CoolDataReader(self.tdaqDB, self.bunchFolder)

        self.bunchData.setIOVRange(self.startTimeIOV, self.endTimeIOV)
        self.bunchData.setChannelId(1)  # 0 = BPTX, 1 = Fast BCT

        if not self.bunchData.readData():
            print 'OflLumiMaker.findScanData - No BUNCHDATA data found in range', self.startTime, 'to', self.endTime, '!'
            sys.exit()

        if self.verbose:
            for obj in self.bunchData.data:
                print 'BUNCHDATA', obj.channelId(), obj.payload(
                )['B1BunchAverage'], obj.payload()['B2BunchAverage']

        # Load the BUNCHLUMI parameters
        if self.monp:
            self.bunchLumi = CoolDataReader(self.tdaqMonpDB,
                                            self.bunchLumiFolder)
        else:
            self.bunchLumi = CoolDataReader(self.tdaqDB, self.bunchLumiFolder)

        self.bunchLumi.setIOVRange(self.startTimeIOV, self.endTimeIOV)
        if not self.bunchLumi.readData():
            print 'OflLumiMaker.findScanData - No BUNCHLUMIS data found in range', self.startTime, 'to', self.endTime, '!'
            sys.exit()

        if self.verbose:
            for obj in self.bunchLumi.data:
                print 'BUNCHLUMI', obj.channelId(), obj.payload(
                )['AverageRawInstLum']

        # Load the luminosity data
        self.lumiData = CoolDataReader(self.tdaqDB, self.lumiFolder)
        self.lumiData.setIOVRange(self.startTimeIOV, self.endTimeIOV)
        if not self.lumiData.readData():
            print 'OflLumiMaker.findScanData - No LUMINOSITY data found in range', self.startTime, 'to', self.endTime, '!'
            sys.exit()

        # Load the Lucid currents
        self.pmtReader = CoolDataReader('COOLONL_TDAQ/COMP200',
                                        '/TDAQ/OLC/LUCIDCURRENTS')
        self.pmtReader.setIOVRange(self.startTimeIOV, self.endTimeIOV)
        self.pmtReader.setChannel([0, 1])  # Load total PMT and fiber currents
        if not self.pmtReader.readData():
            print 'No PMT current data found in range', self.startTime, 'to', self.endTime, '!'

        if self.verbose:
            for obj in self.pmtReader.data:
                print 'LUCIDCURRENTS', obj.channelId(), obj.payload(
                )['CurrentSideA'], obj.payload()['CurrentSideC']

    def updateBCIDMask(self, startTime):

        if not self.maskObj.isValid(startTime):

            self.maskObj.clearValidity()

            # Find the proper mask object
            maskData = None
            for mask in self.fillData.data:
                if not mask.since() <= startTime < mask.until(): continue
                self.maskObj.setMask(mask)
                break

            if not self.maskObj.isValid(startTime):
                return False

        return True

    def updateBunchGroup(self, runlb):

        if not self.bgObj.isValid(runlb):

            self.bgObj.clearValidity()

            # Find the proper BG object
            for bg in self.bgReader.data:
                if not bg.since() <= runlb < bg.until(): continue
                self.bgObj.setBG(bg)
                break

            if not self.bgObj.isValid(runlb):
                return False

        return True
示例#3
0
class CalibNtupleMaker:
    def __init__(self):

        # Control output level
        self.verbose = False

        # Luminosity Channels
        self.lumiChanList = []

        # Luminosity Channel Map
        self.lumiChanNames = dict()

        self.lumiMapFile = 'defaultChannels.txt'

        # Stable only
        self.stableOnly = False

        # Write output ntuple
        self.ntuple = True

        # Write extra current information
        self.writeExtraCurrents = True

        # List of (integer) run numbers specified on the command line
        self.runList = []

        # Dict of (lblo, lbhi) pairs giving extent of StableBeams
        self.stableDict = dict()

        # Utlity routine for handling COOL connections
        self.dbHandler = LumiDBHandler()

        # Data readers
        self.maskReader = None
        self.lumiReader = None
        self.caliReader = None
        self.lblbReader = None
        self.currReader = None
        self.lhcReader = None
        self.bgReader = None
        self.pmtReader = None
        self.lbdataReader = None

        # Object which stores the BCID information
        self.maskObj = BCIDMask()

        # Object to store the bunch group definition
        self.bgObj = BunchGroup()

        self.currentRun = 0

        self.nBCID = 3564

        self.outdir = '.'

    # Explicitly clean up our DB connections
    def __del__(self):
        self.dbHandler.closeAllDB()

    # Called in command-line mode
    def execute(self):

        # Handle command-line switches
        self.parseOpts()

        # Fill channel list
        self.fillChannelList()

        # Process each run in self.runList
        for run in self.runList:

            # Load all data from COOL
            self.loadBCIDData(run)

            # Skip if run doesn't exist
            if len(self.lblbReader.data) == 0: continue

            # Find stable beams, output goes to self.stableTime
            self.findStable()

            if self.stableOnly and len(self.stableTime) == 0: continue

            # Open new output file and init ntuple
            if self.ntuple:
                nt = NtupleHandler()
                nt.chanMap = self.lumiChanNames

                nt.writeExtraCurrents = self.writeExtraCurrents

                nt.fileName = '%s/r%d.root' % (self.outdir, run)
                nt.open()
                nt.init()

            # Storage objects keyed by [runlb][algo]
            objDict = dict()
            runlbList = []

            # Loop over all objects and sort by runlb and algo
            for obj in self.lumiReader.data:
                channel = obj.channelId()
                runlb = obj.payload()['RunLB']
                if runlb not in objDict:
                    objDict[runlb] = dict()
                    runlbList.append(runlb)
                objDict[runlb][channel] = obj

                # if self.verbose:
                #     run = runlb >> 32
                #     lb = runlb & 0xFFFFFFFF
                #     print 'Found Run %d LB %d chan %d' % (run, lb, channel)

            # LHC Current objects keyed by [runlb]
            currDict = dict()
            for obj in self.currReader.data:
                runlb = obj.payload()['RunLB']
                if runlb not in currDict:
                    currDict[runlb] = dict()
                currDict[runlb][obj.channelId()] = obj

            #if self.writeExtraCurrents:
            lbdataDict = dict()
            for obj in self.lbdataReader.data:
                runlb = obj.payload()['RunLB']
                if runlb not in lbdataDict:
                    lbdataDict[runlb] = dict()
                lbdataDict[runlb][obj.channelId()] = obj

            # Loop over all lumi blocks and calibrate each algorithm
            runlbList.sort()
            for runlb in runlbList:

                run = runlb >> 32
                lb = runlb & 0xFFFFFFFF

                self.currentRun = run

                # Make sure bunch group is valid (keyed by run/lb)
                if not self.updateBunchGroup(runlb):
                    print "Couldn't find valid Bunch Group definition for Run %d LB %d!" % (
                        run, lb)
                    continue

                # Get integer to make average mu value below
                nBunch = len(self.bgObj.bg)
                if nBunch < 1: nBunch = 1

                # Zero all storage locations
                nt.clearBCIDData()

                first = True
                lbSum = dict()
                for chan in self.lumiChanList:

                    if chan not in objDict[runlb]:
                        print "Can't find channel", chan, "in run/lb", run, '/', lb
                        continue

                    obj = objDict[runlb][chan]

                    if chan != obj.channelId():
                        print 'Channel', chan, '!=', obj.channelId(), '!'
                        continue

                    if runlb != obj.payload()['RunLB']:
                        print 'RunLB', runlb, '!=', obj.payload()['RunLB'], '!'
                        continue

                    startTime = obj.since()
                    endTime = obj.until()
                    dtime = (endTime - startTime) / 1E9
                    valid = obj.payload()['Valid'] & 0x03

                    # Hack for lucid validity
                    if 100 < chan < 200 and valid == 1: valid = 0

                    # Check whether this is in stable beams
                    isStable = False
                    for stable in self.stableTime:
                        if not stable[0] <= startTime < stable[1]: continue
                        isStable = True
                        break

                    if self.stableOnly and not isStable: continue

                    # Clear lumi block sum counters
                    lbSum[chan] = 0.

                    # Only do this once per lumi block
                    if first:
                        first = False

                        if self.verbose:
                            print 'Found stable Run %d LB %d' % (run, lb)

                        # Fill general LB information
                        if self.ntuple:
                            nt.fillLBData(obj)
                            nt.lbDataStruct.fStable = isStable

                        # These change slowly, so checking them every run/lb is OK
                        # Make sure BCID mask is valid
                        if not self.updateBCIDMask(startTime):
                            print "Couldn't find valid BCID mask data for Run %d LB %d!" % (
                                run, lb)
                            continue

                        # Do this here so the BCID mask is up to date
                        if self.ntuple:
                            if runlb in currDict:
                                if 1 in currDict[runlb]:
                                    nt.fillCurrentData(currDict[runlb],
                                                       self.maskObj, 1)
                                if 0 in currDict[
                                        runlb] and self.writeExtraCurrents:
                                    nt.fillCurrentData(currDict[runlb],
                                                       self.maskObj, 0)

                            if runlb in lbdataDict:
                                nt.fillMoreCurrentData(lbdataDict[runlb])

                    # Now we want to start extracting bunch-by-bunch information

                    # Get raw lumi
                    normValue = obj.payload()['AverageRawInstLum']
                    blobValue = obj.payload()['BunchRawInstLum']
                    bcidVec, rawLumi = unpackBCIDValues(blobValue)

                    # dict to hold BCID lumi keyed by BCID
                    bcidLumi = dict()
                    for i in range(len(rawLumi)):

                        # Check if this is in our bunch group (only really need to fill this once)
                        bcid = bcidVec[i]

                        # Protect against any weird values
                        if bcid >= self.nBCID:
                            print 'BCID %d found >= %d!' % (bcid, self.nBCID)
                            continue

                        if bcid in self.bgObj.bg:
                            nt.bcidArray['Status'][bcid] = 1
                        else:
                            nt.bcidArray['Status'][bcid] = 0

                        # Now need to save bcid, mu, and calibLumi
                        lraw = rawLumi[i]

                        nt.fillBCIDData(chan, bcid, lraw)

                    # End loop over BCIDs

                # End of loop over channels
                nt.tree.Fill()

            # End of loop over LBs
            nt.close()

        # End of loop over runs

    # Load all data necessary to make bunch-by-bunch lumi for a single run
    def loadBCIDData(self, runnum):
        print 'MuHistMaker.loadBCIDData(%s) called' % runnum

        # Many folders are indexed by time stamp.  Load RunLB -> time conversion here
        self.loadLBLB(runnum)

        if len(self.lblbReader.data) == 0: return

        # Determine start and end time of this run
        startTime = self.lblbReader.data[0].payload()['StartTime']
        endTime = self.lblbReader.data[-1].payload()['EndTime']
        iov = (startTime, endTime)

        # Read LHC Data (for stable beams)
        self.loadLHCData(iov)

        # Read the bunch group (so we sum over the right thing)
        self.loadBGData(runnum)

        # Read BCID Data
        self.loadBCIDMask(iov)
        self.loadBCIDLumi(iov)
        self.loadBCIDCurrents(iov)
        self.loadOtherCurrents(iov)

    def loadLBLB(self, run):
        if self.verbose: print 'Loading LBLB data'

        # Instantiate new COOL data reader if not already done
        if self.lblbReader == None:
            self.lblbReader = CoolDataReader('COOLONL_TRIGGER/CONDBR2',
                                             '/TRIGGER/LUMI/LBLB')

        self.lblbReader.setIOVRangeFromRun(run)
        self.lblbReader.readData()

        if self.verbose:
            print 'Read %d LBLB records' % len(self.lblbReader.data)
            if len(self.lblbReader.data) > 0:
                print 'First LB %d/%d' % (
                    self.lblbReader.data[0].since() >> 32,
                    self.lblbReader.data[0].since() & 0xFFFFFFFF)
                print 'Last  LB %d/%d' % (
                    self.lblbReader.data[-1].since() >> 32,
                    self.lblbReader.data[-1].since() & 0xFFFFFFFF)

    def loadBGData(self, run):
        if self.verbose: print 'Loading Bunch group data'

        # Instantiate new COOL reader if not already done
        if self.bgReader == None:
            self.bgReader = CoolDataReader('COOLONL_TRIGGER/CONDBR2',
                                           '/TRIGGER/LVL1/BunchGroupContent')

        self.bgReader.setIOVRangeFromRun(run)
        self.bgReader.readData()

        if self.verbose:
            print 'Read %d bunch group records' % len(self.bgReader.data)

    def loadBCIDMask(self, iov):
        if self.verbose: print 'Loading BCID masks'

        # Instantiate new COOL data reader if not already done
        if self.maskReader == None:
            self.maskReader = CoolDataReader('COOLONL_TDAQ/CONDBR2',
                                             '/TDAQ/OLC/LHC/FILLPARAMS')

        self.maskReader.setIOVRange(iov[0], iov[1])
        self.maskReader.readData()

        if self.verbose:
            print 'Read %d BCID Mask records' % len(self.maskReader.data)

    def loadBCIDLumi(self, iov):
        if self.verbose: print 'Loading BCID luminosity values'

        # Instantiate new COOL data reader if not already done
        if self.lumiReader == None:
            # Switch at start of September
            self.lumiReader = CoolDataReader('COOLONL_TDAQ/CONDBR2',
                                             '/TDAQ/OLC/BUNCHLUMIS')

        #self.lumiReader.verbose = True
        self.lumiReader.setIOVRange(iov[0], iov[1])
        self.lumiReader.setChannel(self.lumiChanList)
        self.lumiReader.readData()
        #self.lumiReader.verbose = False

        if self.verbose:
            print 'Read %d Lumi records' % len(self.lumiReader.data)

    # Bunch currents
    def loadBCIDCurrents(self, iov):
        if self.verbose: print 'Loading Bunch Current information'

        if self.currReader == None:
            # self.currReader = CoolDataReader('COOLONL_TDAQ/MONP200', '/TDAQ/OLC/LHC/BUNCHDATA')
            self.currReader = CoolDataReader('COOLONL_TDAQ/CONDBR2',
                                             '/TDAQ/OLC/LHC/BUNCHDATA')

        self.currReader.setIOVRange(iov[0], iov[1])
        if self.writeExtraCurrents:
            self.currReader.setChannel([0, 1])  # 0 = BPTX, 1 = Fast BCT
        else:
            self.currReader.setChannelId(1)  # 0 = BPTX, 1 = Fast BCT
        self.currReader.readData()

        if self.verbose:
            print 'Read %d Current records' % len(self.currReader.data)

    def loadOtherCurrents(self, iov):
        if self.verbose: print 'Loading LBDATA Bunch Current information'

        if self.lbdataReader == None:
            self.lbdataReader = CoolDataReader('COOLONL_TDAQ/CONDBR2',
                                               '/TDAQ/OLC/LHC/LBDATA')

        self.lbdataReader.setIOVRange(iov[0], iov[1])
        self.lbdataReader.setChannel([0, 1, 2, 3])  # 0 = BPTX, 1 = Fast BCT
        self.lbdataReader.readData()

        if self.verbose:
            print 'Read %d LBDATA Current records' % len(
                self.lbdataReader.data)

    # Information about stable beams
    def loadLHCData(self, iov):
        if self.verbose: print 'Loading LHC information'

        # Instantiate new COOL data reader if not already done
        if self.lhcReader == None:
            self.lhcReader = CoolDataReader('COOLOFL_DCS/CONDBR2',
                                            '/LHC/DCS/FILLSTATE')

        self.lhcReader.setIOVRange(iov[0], iov[1])
        self.lhcReader.readData()

        if self.verbose:
            print 'Read %d LHC records' % len(self.lhcReader.data)

    # Fill the stable beam information in the OflLumiRunData object
    def findStable(self):
        if self.verbose: print 'Finding stable beam periods'

        # First, fill stable beam time periods for this run
        tlo = cool.ValidityKeyMax
        thi = cool.ValidityKeyMin
        self.stableTime = []
        for obj in self.lhcReader.data:
            if obj.payload()['StableBeams'] == 0: continue

            if tlo > thi:  # First stable beams
                tlo = obj.since()
                thi = obj.until()
            elif thi == obj.since():  # Extension of existing stable beams
                thi = obj.until()
            else:  # Not contiguous, add old to list and start again
                self.stableTime.append((tlo, thi))
                tlo = obj.since()
                thi = obj.until()

        if tlo < thi:
            self.stableTime.append((tlo, thi))

        if self.verbose:
            print 'Stable beam periods found:', self.stableTime

    def updateBCIDMask(self, startTime):

        if not self.maskObj.isValid(startTime):

            self.maskObj.clearValidity()

            # Find the proper mask object
            maskData = None
            for mask in self.maskReader.data:
                if not mask.since() <= startTime < mask.until(): continue
                self.maskObj.setMask(mask)
                break

            if not self.maskObj.isValid(startTime):
                return False

        return True

    def updateBunchGroup(self, runlb):

        if not self.bgObj.isValid(runlb):

            self.bgObj.clearValidity()

            # Find the proper BG object
            for bg in self.bgReader.data:
                if not bg.since() <= runlb < bg.until(): continue
                self.bgObj.setBG(bg)
                break

            if not self.bgObj.isValid(runlb):
                return False

        return True

    def parseOpts(self):

        parser = OptionParser(usage="usage: %prog [options]",
                              add_help_option=False)

        parser.add_option("-?",
                          "--usage",
                          action="store_true",
                          default=False,
                          dest="help",
                          help="show this help message and exit")

        parser.add_option("-v",
                          "--verbose",
                          action="store_true",
                          default=self.verbose,
                          dest="verbose",
                          help="turn on verbose output")

        parser.add_option("-r",
                          "--updateRun",
                          dest="runlist",
                          metavar="RUN",
                          help="update specific run, or comma separated list")

        parser.add_option("--noNtuple",
                          action="store_false",
                          default=self.ntuple,
                          dest='ntuple',
                          help="Don't store output ntuple (default: Do)")

        parser.add_option("-o",
                          "--outputDir",
                          dest="outdir",
                          metavar="DIR",
                          default=self.outdir,
                          help='directory for output ntuple (default: %s)' %
                          self.outdir)

        parser.add_option(
            "--noStable",
            action="store_false",
            default=self.stableOnly,
            dest="stable",
            help="turn off stable beams requirements (default: stable only)")

        parser.add_option("--channelList",
                          dest='chanlist',
                          metavar="FILE",
                          default=self.lumiMapFile,
                          help='file to read channel list from (default: %s)' %
                          self.lumiMapFile)

        (options, args) = parser.parse_args()

        if options.help:
            parser.print_help()
            sys.exit()

        self.verbose = options.verbose
        self.outdir = options.outdir
        self.stableOnly = options.stable
        self.lumiMapFile = options.chanlist

        # Parse run list
        if options.runlist != None:

            # Clear the old one
            self.runList = []

            # Can be comma-separated list of run ranges
            runlist = options.runlist.split(',')
            if len(runlist) == 0:
                print 'Invalid run list specified!'
                sys.exit()

            # Go through and check for run ranges
            for runstr in runlist:
                subrunlist = runstr.split('-')

                if len(subrunlist) == 1:  # Single run
                    self.runList.append(int(subrunlist[0]))

                elif len(subrunlist) == 2:  # Range of runs
                    for runnum in range(int(subrunlist[0]),
                                        int(subrunlist[1]) + 1):
                        self.runList.append(runnum)

                else:  # Too many parameters
                    print 'Invalid run list segment found:', runstr
                    sys.exit()

            self.runList.sort()
            if self.verbose:
                print 'Finished parsing run list:',
                for runnum in self.runList:
                    print runnum,
                print

    def fillChannelList(self):

        print 'Reading channel list from', self.lumiMapFile

        # Make sure these are empty
        self.lumiChanList = []
        self.lumiChanNames = dict()

        f = open(self.lumiMapFile)

        for line in f.readlines():

            line = string.lstrip(line)
            # Skip obvious comments
            if len(line) == 0: continue
            if line[0] == "!": continue
            if line[0] == "#": continue

            # Now parse
            tokens = line.split()
            chanID = int(tokens[0])
            chanName = tokens[1]
            print 'Found Channel %d: %s' % (chanID, chanName)
            self.lumiChanList.append(chanID)
            self.lumiChanNames[chanID] = chanName

        # End of loop over channels
        f.close()
    def execute(self):

        self.parseOpts()

        # Read offline luminosity
        lumiReader = CoolDataReader('COOLOFL_TRIGGER/COMP200',
                                    '/TRIGGER/OFLLUMI/LBLESTOFL')
        lumiReader.setChannelId(self.lumiChannel)
        lumiReader.setTag(self.lumiTag)

        # Read LAr noise bursts
        larReader = CoolDataReader('COOLOFL_LAR/COMP200',
                                   '/LAR/BadChannelsOfl/EventVeto')
        larReader.setTag('LARBadChannelsOflEventVeto-UPD4-04')

        # Time to use with LAr noise
        # lbtimeReader = CoolDataReaderCache('COOLONL_TRIGGER/COMP200', '/TRIGGER/LUMI/LBTIME')
        lblbReader = CoolDataReader('COOLONL_TRIGGER/COMP200',
                                    '/TRIGGER/LUMI/LBLB')

        # Read ATLAS ready flag
        readyReader = LumiDBCache('COOLONL_TDAQ/COMP200',
                                  '/TDAQ/RunCtrl/DataTakingMode')

        for run in self.runList:

            print
            print 'Generating run', run

            rootfile = self.outdir + '/run' + str(run) + '.root'

            # Define ntuple - will open existing file if present
            nt = TrigNtupleHandler()
            nt.fileName = rootfile
            nt.open(update=False)
            nt.initLBData()
            nt.initL1TrigData()

            # Load run information
            print 'Load trigger information'
            th = TriggerHandler()
            th.allL1Triggers = True
            th.trigList = []
            th.loadDataByRun(run)

            # Load lumi information
            print 'Load lumi information'
            lumiReader.setIOVRangeFromRun(run)
            lumiReader.readData()

            # Read ATLAS ready information
            print 'Load ATLAS ready information'
            readyReader.reader.setIOVRangeFromRun(run)
            readyReader.reader.readData()

            # Load time stamps
            print 'Load LBLB information'
            lblbReader.setIOVRangeFromRun(run)
            lblbReader.readData()
            startTime = lblbReader.data[0].payload()["StartTime"]
            endTime = lblbReader.data[-1].payload()["EndTime"]

            # Read bad LAr periods
            print 'Load LAr information'
            larReader.setIOVRange(startTime, endTime)
            larReader.readData()

            # Now make a list of bad lumi blocks
            print 'Finding bad LBs'
            badLB = set()
            for larData in larReader.data:

                if larData.payload()["EventVeto"] == 0:
                    continue

                tlo = larData.since()
                thi = larData.until()

                # Find all lumi blocks spanned by this range
                for lb in lblbReader.data:
                    if lb.payload()["EndTime"] <= tlo: continue
                    ss = lb.since()
                    if lb.payload()["StartTime"] < tlo:
                        badLB.add(ss)
                        print runLBString(ss)
                    if lb.payload()["StartTime"] < thi:
                        badLB.add(ss)
                        print runLBString(ss)
                    if lb.payload()["StartTime"] > thi: break

            # Process
            for obj in lumiReader.data:

                ss = obj.since()

                lumi = obj.payload()["LBAvInstLumi"]
                mu = obj.payload()["LBAvEvtsPerBX"]

                if ss not in th.trigL1Dict:
                    continue

                trigcount = th.trigL1Dict[ss].TBP[self.trigChan]
                dtime = th.trigL1Dict[ss].dtime
                if (dtime > 0.):
                    trigrate = trigcount / dtime
                else:
                    trigrate = 0.

                atlasReady = False
                readypay = readyReader.getPayload(obj.since())
                if readypay != None:
                    atlasReady = readypay['ReadyForPhysics']

                print runLBString(
                    ss), atlasReady, lumi, mu, trigcount, trigrate,
                if trigrate > 0.:
                    print lumi / trigrate
                else:
                    print

                # ATLAS Ready only
                if self.ready and (not atlasReady): continue

                nt.clear()
                nt.lbData.coolStartTime = th.trigL1Dict[ss].startTime
                nt.lbData.coolEndTime = th.trigL1Dict[ss].endTime
                nt.lbData.startTime = nt.lbData.coolStartTime / 1.E9
                nt.lbData.endTime = nt.lbData.coolEndTime / 1.E9
                nt.lbData.lbTime = dtime

                nt.lbData.run = obj.since() >> 32
                nt.lbData.lb = obj.since() & 0xFFFFFFFF

                nt.lbData.onlInstLum = lumi
                nt.lbData.onlEvtsPerBX = mu

                nt.lbData.ready = atlasReady
                nt.lbData.larVeto = (ss in badLB)

                # And save trigger counts
                nt.fillL1Trig(th.trigL1Dict[ss], th.trigChan)
                nt.save()

                #for chan in range(256):
                #    print chan, nt.l1TBP[chan]

            nt.close()