def get_scan_arrays_brm(self, inputHistograms, scan, bunch, scanPlane, algos):
        '''
        Returns an array for a single bunch in a single 
        scan.  Array contains mu values versus separation 
        values and their associated errors.
        '''

        muVdm       = l.dict_map(algos, [{} for i in range(len(algos))]) 
        muErr       = l.dict_map(algos, [{} for i in range(len(algos))]) 
        delta       = []
        deltaErr    = []

        preSeparation   = 0.
        dipTime         = 0.
        orbit           = 11246

        for row in self._scanData[int(scan)-1]:

            if row['BUNCHES'] == '' or row['IPSCAN'] != '5' or row['SCAN_FLAG'] == 0: continue

            dipTime     = r.TDatime(row['DT']).Convert(r.kTRUE)
            separation  = float(row['SEP'])
            plane       = row['PLANE']
            time        = dipTime - self._offsets['PLT']

            #print time, dipTime, row['DT']

            if plane is not scanPlane or separation == 0.: continue

            ### Beam parameters ###
            delta.append(separation)

            ### GET RATES ###
            prob = {}

            prob['OR']     = inputHistograms['OR'].GetBinContent(inputHistograms['OR'].FindBin(time*1e3))/orbit
            prob['XOR1']   = inputHistograms['XOR1'].GetBinContent(inputHistograms['XOR1'].FindBin(time*1e3))/orbit
            prob['XOR2']   = inputHistograms['XOR2'].GetBinContent(inputHistograms['XOR2'].FindBin(time*1e3))/orbit
            prob['AND']    = inputHistograms['AND'].GetBinContent(inputHistograms['AND'].FindBin(time*1e3))/orbit
            prob['0']      = 1 - prob['OR'] 

            if prob['OR'] == 0: continue

            #print '{}, {}'.format(prob['OR'], time)

            ### MU-CORRECTIONS ###
            mu = {}
            mu['OR']        = -1*r.TMath.log(prob['0'])
            mu['XOR1']      = r.TMath.log(1 + prob['XOR1']/prob['0'])
            mu['XOR2']      = r.TMath.log(1 + prob['XOR2']/prob['0'])
            mu['AND']       = -1*r.TMath.log(prob['0']*(1 + prob['XOR1']/prob['0'])*(1 + prob['XOR2']/prob['0']))
            mu['AND_ALT']   = -1*r.TMath.log(1 - prob['AND'])


            if bunch is not 'Total':
                beamTime    = int((dipTime - self._offsets['intensity'])/60.)

                #print beamTime, dipTime, self._offsets['intensity']

                intensity1  = 1. #float(self._bunchData[0][beamTime][int(bunch)])*1e-11
                intensity2  = 1. #float(self._bunchData[1][beamTime][int(bunch)])*1e-11
                IxI = intensity1*intensity2

            for algo in algos:
                if prob[algo] == 0: continue

                if separation != preSeparation:
                    muVdm[algo][separation] = 0
                    muErr[algo][separation] = 0

                error = self.calculate_sigma_mu(prob, 11246., algo)

                muVdm[algo][separation] += (mu[algo]/IxI)/pow(error/IxI, 2)
                muErr[algo][separation] += pow(IxI/error, 2)

            preSeparation = separation


        # Prepare arrays with scan data for use in fits

        delta = sorted(list(set(delta)))

        for algo in algos:
            for separation in delta:
                muVdm[algo][separation] = muVdm[algo][separation]/muErr[algo][separation]    
                muErr[algo][separation] = math.sqrt(1/muErr[algo][separation])
                deltaErr.append(1e-7)

        return (muVdm, muErr, delta, deltaErr)
    def get_scan_arrays_lumi(self, inputHistograms, scan, bunch, scanPlane, algos):
        '''
        Uses CSV files provided by lumi group.  The files contain
        times for the beginning and end of each scan separation.
        Returns an array for a single bunch in a single scan.  
        Array contains mu values versus separation values and
        their associated errors.
        '''

        muVdm       = l.dict_map(algos, [{} for i in range(len(algos))]) 
        muErr       = l.dict_map(algos, [{} for i in range(len(algos))]) 
        delta       = []
        deltaErr    = []

        preSeparation   = 0.
        dipTime         = 0.
        orbit           = 11246.

        for row in self._scanData[int(scan)-1]:

            time_i      = int(row['TIME_I']) - self._offsets['PLT']
            time_f      = int(row['TIME_F']) - self._offsets['PLT']
            separation  = float(row['SEP'])
            plane       = row['PLANE']


            if plane is not scanPlane or separation == 0.: continue

            ### Beam parameters ###
            delta.append(separation)

            for time in range(time_i, time_f, 2):

                ### PROBABILITIES ###
                prob = {}

                prob['OR']     = inputHistograms['OR'].GetBinContent(inputHistograms['OR'].FindBin(time*1e3))/(2*orbit)
                prob['XOR1']   = inputHistograms['XOR1'].GetBinContent(inputHistograms['XOR1'].FindBin(time*1e3))/(2*orbit)
                prob['XOR2']   = inputHistograms['XOR2'].GetBinContent(inputHistograms['XOR2'].FindBin(time*1e3))/(2*orbit)
                prob['AND']    = inputHistograms['AND'].GetBinContent(inputHistograms['AND'].FindBin(time*1e3))/(2*orbit)
                prob['0']      = 1 - prob['OR'] 

                if prob['OR'] == 0: continue

                #print '{}, {}'.format(prob['OR'], time)

                ### MU-CORRECTIONS ###
                mu = {}
                mu['OR']        = -1*r.TMath.log(prob['0'])
                mu['XOR1']      = r.TMath.log(1 + prob['XOR1']/prob['0'])
                mu['XOR2']      = r.TMath.log(1 + prob['XOR2']/prob['0'])
                mu['AND']       = -1*r.TMath.log(prob['0']*(1 + prob['XOR1']/prob['0'])*(1 + prob['XOR2']/prob['0']))
                mu['AND_ALT']   = -1*r.TMath.log(1 - prob['AND'])


                if bunch is not 'Total':
                    iRow = int((int(row['TIME_I']) + time - self._offsets['intensity'])/60.)

                    #print iRow, time, int(row['TIME_I']), self._offsets['intensity']

                    intensity1  = float(self._bunchData[0][iRow][int(bunch)])*1e-11
                    intensity2  = float(self._bunchData[1][iRow][int(bunch)])*1e-11
                    IxI = intensity1*intensity2

                for algo in algos:
                    if prob[algo] == 0: continue

                    if separation != preSeparation:
                        muVdm[algo][separation] = 0
                        muErr[algo][separation] = 0

                    error = self.calculate_sigma_mu(prob, 11246., algo)

                    muVdm[algo][separation] += (mu[algo]/IxI)/pow(error/IxI, 2)
                    muErr[algo][separation] += pow(IxI/error, 2)


        # Prepare arrays with scan data for use in fits

        delta = sorted(list(set(delta)))

        for algo in algos:
            for separation in delta:
                muVdm[algo][separation] = muVdm[algo][separation]/muErr[algo][separation]    
                muErr[algo][separation] = math.sqrt(1/muErr[algo][separation])
                deltaErr.append(1e-7)

        return (muVdm, muErr, delta, deltaErr)
    def multi_scan(self, scans, bcids, pltData, doCorrFit, savePath):
        '''
        Analyzes multiple scans
        '''
    
        scanResults     = []
        scanResults2D   = []
        for scan in scans:
            fitResults      = l.dict_map(['OR', 'AND', 'XOR1', 'XOR2'], [{}, {}, {}, {}])
            fitResults2D    = l.dict_map(['OR', 'AND', 'XOR1', 'XOR2'], [{}, {}, {}, {}])
            resultFile      = open(savePath+'/fill'+str(self._fill)+'/scan'+scan+'_fitSummary.txt', 'w')


            for bcid in bcids:
                bunch = str(bcid)

                histograms = {
                    'OR':pltData.GetDirectory('BCID_'+bunch).Get('p1_RateVsTime_OR'), 
                    'AND': pltData.GetDirectory('BCID_'+bunch).Get('p1_RateVsTime_AND'), 
                    'XOR1': pltData.GetDirectory('BCID_'+bunch).Get('p1_RateVsTime_XOR1'), 
                    'XOR2': pltData.GetDirectory('BCID_'+bunch).Get('p1_RateVsTime_XOR2')
                }

                fitResult   = {'X':[], 'Y':[]}
                fitGraphs   = {}
                mu2D        = {'X':[], 'Y':[], 'Z':[]}
                mu2DErr     = {'X':[], 'Y':[], 'Z':[]}

                for plane in ['X', 'Y']:
                    if self._dataFormat == 'brm':
                        fitInput = self.get_scan_arrays_brm(histograms, scan, bunch, plane, self._algos)
                    elif self._dataFormat == 'lumi':
                        fitInput = self.get_scan_arrays_lumi(histograms, scan, bunch, plane, self._algos)

                    muOr  = (
                        [value for (key, value) in sorted(zip(fitInput[0]['OR'].keys(), fitInput[0]['OR'].values()))],
                        [value for (key, value) in sorted(zip(fitInput[1]['OR'].keys(), fitInput[1]['OR'].values()))]
                    )

                    vdmGraph = r.TGraphErrors(len(fitInput[2]), array('f', fitInput[2]), array('f', muOr[0]), array('f', fitInput[3]), array('f', muOr[1]))
                    vdmGraph.SetTitle(plane+'-scan BCID '+bunch+' (BCM1F);#Delta '+plane+' (mm);')
                    fitResult[plane] = t.Fit(vdmGraph, 'doubleGaussian', True, self._fill, '1', savePath+'/fill'+str(self._fill)+'/scan'+scan+'/vdmScan_1D_OR_BCM1F_BCID-'+bunch+'_'+plane)

                    # Collect data for correlated fits
                    fitGraphs[plane] = vdmGraph

                    if plane == 'X':
                        mu2D['X'].extend(fitInput[2])
                        mu2DErr['X'].extend(fitInput[3])
                        mu2D['Y'].extend([0. for n in range(len(fitInput[2]))])
                        mu2DErr['Y'].extend([0. for n in range(len(fitInput[2]))])
                    elif plane == 'Y':
                        mu2D['X'].extend([0. for n in range(len(fitInput[2]))])
                        mu2DErr['X'].extend([0. for n in range(len(fitInput[2]))])
                        mu2D['Y'].extend(fitInput[2])
                        mu2DErr['Y'].extend(fitInput[3])

                    mu2D['Z'].extend(muOr[0])
                    mu2DErr['Z'].extend(muOr[1])


                graph2D = r.TGraph2DErrors(len(mu2D['X']), \
                                    array('d', mu2D['X']), array('d', mu2D['Y']), array('d', mu2D['Z']),\
                                    array('d', mu2DErr['X']), array('d', mu2DErr['Y']), array('d', mu2DErr['Z']))

                fitResult2D = t.Fit2D(graph2D, fitGraphs['X'], fitGraphs['Y'], True, self._fill, fitResult['X'], fitResult['Y'], savePath+'/fill'+str(self._fill)+'/scan'+scan+'/vdmScan_2D_OR_BCM1F_BCID-'+bunch)
                graph2D.Clear()

                sigmaEff = r.TMath.Pi()*fitResult['X'][0]*fitResult['Y'][0]*(fitResult['X'][2] + fitResult['Y'][2])/1e30
                sigmaErr = (r.TMath.Pi()/1e30)*math.sqrt((pow(fitResult['X'][0]*fitResult['Y'][1], 2) \
                            + pow(fitResult['X'][1]*fitResult['Y'][0], 2))*pow(fitResult['X'][2] + fitResult['Y'][2], 2) \
                            + pow(fitResult['X'][0]*fitResult['X'][1], 2)*(fitResult['X'][3]*fitResult['X'][3] + fitResult['Y'][3]*fitResult['Y'][3]))

                fitResults['OR'][bunch]     = (fitResult['X'], fitResult['Y'], (sigmaEff*1e30, sigmaErr*1e30))
                fitResults2D['OR'][bunch]   = (fitResult2D, (sigmaEff*1e30, sigmaErr*1e30))

            subprocess.call('pdftk ' + savePath + '/fill' + str(self._fill) + '/scan' + scan + '/vdmScan_1D*.pdf output ' + savePath + '/fill' + str(self._fill) + '/scan' + scan + '_1D.pdf', shell = True)
            subprocess.call('pdftk ' + savePath + '/fill' + str(self._fill) + '/scan' + scan + '/vdmScan_2D*.pdf output ' + savePath + '/fill' + str(self._fill) + '/scan' + scan + '_2D.pdf', shell = True)
            l.print_vdm_tables('OR', bcids, fitResults, resultFile, False, False) 

            scanResults.append(fitResults)
            scanResults2D.append(fitResults2D)

        return scanResults, scanResults2D
    def __init__(self, timeStep = 25, nIter = 72, sourceFile= 'histograms/adcHistograms.root', doEff = False, \
            channels = [str(i) for i in range(8)], \
            pList = [0.5 for i in range(8)], \
            sList = [45 for i in range(8)], \
            gList = [(10,12.5) for i in range(8)]
            ):

        # configuration parameters
        self._sourceFile    = r.TFile(sourceFile, 'OPEN')
        self._timeStep      = timeStep
        self._nObjects      = len(channels)
        self._nIter         = nIter
        self._doEff         = doEff

        # initialize generators, counters
        self._rndGen        = r.TRandom3(0)
        self._counter       = 0.

        # Specify the list of outcomes that are being tested (diamond, algo, ?)
        self._channels       = channels 
        self._algos         = ['OR', 'XOR+', 'XOR-', 'AND'] 

        # Channel status booleans
        self._isLive        = l.dict_map(self._channels, [True for i in range(len(channels))]) 
        self._isTriggered   = l.dict_map(self._channels, [[False, False] for i in range(len(channels))]) #[<hit>, <recorded hit>]
        self._isOvershoot   = l.dict_map(self._channels, [False for i in range(len(channels))]) 

        # Saves pulse characteristics for evolving (for now, just TOT)
        self._pulseStatus   = l.dict_map(self._channels, [[0, 0, 0] for i in range(len(channels))]) 

        # Input parameters for detector modelling
        self._fillPattern   = [True for i in range(nIter)]       # list of bools saying whether bunch is colliding or not
        self._probabilities = l.dict_map(self._channels, pList)  # diamond-by-diamond probabilities 
        self._gate          = l.dict_map(self._channels, gList)  # gate width and offset w.r.t. center of hit distribution  
        self._overshoot     = l.dict_map(self._channels, sList)  # saturationn parameters
        #self._tosParams     = l.dict_map(self._channels, osList)

        # Spectra for modelling detector effects
        self._pulseSpectrum  = l.dict_map(self._channels, [])     # 2D hist for producing pulse height and tot 
        self._osSpectrum     = l.dict_map(self._channels, [r.TF1('f_osSpectrum', 'exp([0]*(x - [1]))', -5000, 0) for i in range(len(channels))])
        self._tosSpectrum    = l.dict_map(self._channels, [r.TF1('f_tosSpectrum', '[0] + [1]*atan([2]*(x - [3]))', 0, 2000) for i in range(len(channels))])
        self._bunchSpectrum  = r.TF1('f_bunchSpectrum', 'gaus', 0, 25) # for modelling distribution of colliding hits in time per bunch 
        self._albedoSpectrum = r.TF1('f_albedoSpectrum', 'pol0', 25, 50) # for modelling distribution of albedo hits in time per bunch 

        # Outputs
        self._hits  = l.dict_map(self._channels, [[False for i in range(self._nIter)] for j in range(self._nObjects)])
        self._coins = l.dict_map(self._algos, [[False for i in range(self._nIter)] for j in range(len(self._algos))])