-
megglest authored
only a desired run, timestamp, and mu profile provided by Data Preparation New file configLumi_run305000.py as requested in ATLASSIM-3522
megglest authoredonly a desired run, timestamp, and mu profile provided by Data Preparation New file configLumi_run305000.py as requested in ATLASSIM-3522
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
RunDepProfileGenerator.py 8.51 KiB
#!/usr/bin/env python
#file RunDepProfileGenerator.py
#Mu profile generator. Given mu histogram in ROOT file from Data-Preparation, scale to desired number of events, and create configLumi job option.
import sys,argparse,os,time
import ROOT
import math
from AthenaCommon.Logging import logging
log = logging.getLogger("RunDepProfileGenerator")
log.setLevel(logging.DEBUG)
parser = argparse.ArgumentParser(description='Generate configLumi option from mu profile.')
parser.add_argument('-i','--input-file-name',help='name of ROOT file containing unscaled mu histogram',dest='in_file')
parser.add_argument('-n','--histogram-name',help='optional name of histogram to generate profile for if multiple exist in IN_FILE',dest='in_hist')
parser.add_argument('-e','--external-dictionary',help='unscaled input dictionary given as {mu:events}',dest='ex_dict')
parser.add_argument('-r','--run-number',help='run number used in default output file name',dest='run_num',required=True,type=int)
parser.add_argument('-s','--start-time-stamp',help='starting timestamp',dest='start_tstamp',required=True,type=int)
parser.add_argument('-o','--output-file-name',help='custom output file name',dest='out_file')
parser.add_argument('-c','--scale',help='total number of events by which to scale the input histogram/dictionary',dest='total_events',required=True,type=int)
#Following RunDepTaskMaker.py for output format
formatLine="'run':{run}, 'lb':{lb}, 'starttstamp':{starttstamp}, 'dt':{dt:.3f}, 'evts':_evts({evts:.0f}), 'mu':{mu:.3f}, 'force_new':False"
scaled_integral = 0
#Check total number of events in a dictionary
def integrate(input_dict):
total = 0
for val in input_dict.values():
total += val
return total
#Scales the given data desired total number of events passed as argument to option --scale
def scaleDict(input_dict,scale):
scaled_dict = dict()
global scaled_integral
skipped = 0
integral = integrate(input_dict)
for key in input_dict.keys():
new_val = round((input_dict[key]*scale)/integral)
scaled_dict[key] = new_val
#This condition checks for zero event elements getting stored in dictionary
if new_val == 0.0:
skipped += 1
#User will be warned in log of how many lumiblocks went unpopulated, and excluded from being written by dressSkeleton to the output file
log.warning("Skipped %i lumiblocks for containing less than one event.",skipped)
scaled_integral = integrate(scaled_dict)
if scaled_integral != scale:
log.warning("Final distribution has {} total events! Does not match with {} events requested.".format(scaled_integral,scale))
return scaled_dict
#Create scaled dictionary from histogram with:
# key = mu value at ceneter of bin,
# value = number of events in that bin
def extractDict(hist,scale):
data = dict()
nbins = hist.GetNbinsX()
for i in range(1,nbins+1):
data[hist.GetBinCenter(i)] = hist.GetBinContent(i)
return scaleDict(data,scale)
#Generates the configuration preInclude file for transform jobs
# rewritten from RunDepTaskMaker.py
#Parses skeleton file skeletonName and replaces the lines between #<!-- and #-->
#skeletonName is static, set in this script to RunDependentSimData/share/OverrideRunLBLumiDigitConfig.py
def dressSkeleton(skeletonName,outName,jobIterator):
from AthenaCommon.Include import FindFile,optionsPath
sname = FindFile(skeletonName, optionsPath, os.R_OK)
if not sname: raise IOError("Input skeleton file {} cannot be read.".format(skeletonName))
s = open(sname,'r')
outfile = open(outName,'w')
outfile.write('#'*20)
outfile.write('\n## File {}: autogenerated configuration file from command\n'.format(outName))
outfile.write('##' + ' '.join(sys.argv) + '\n')
outfile.write('## Created on {}\n'.format(time.asctime()))
outfile.write('#'*20+'\n')
incomment = False
dumpedAlready = False
for line in s:
if (line.find("#<!--") == 0): incomment = True
if (line.find("#-->") == 0): incomment = False
if not incomment:
outfile.write(line)
elif not dumpedAlready:
for j in jobIterator:
if j['evts'] != 0.0:
outfile.write(" {"+formatLine.format(**j)+"},\n") #Skips all zero event lumiblocks
pass
dumpedAlready = True
pass
pass
pass
def main():
args = parser.parse_args()
#Check that the user provided either a .root file or a dictionary
if len(sys.argv) == 0 or not args.in_file and not args.ex_dict:
parser.print_help()
exit(1)
#If no output file name privided, generate default: "configLumi_runRUN_NUM.py"
outFName = args.out_file
if not args.out_file:
outFName = 'configLumi_run{}.py'.format(args.run_num)
#Use first histogram in file to generate dictionary, if no name provided
hist = None
inFile = None
scaled_dict = dict()
global scaled_integral
if args.in_file: #If file provided, use it
inFile = ROOT.TFile(args.in_file)
if (not args.in_hist) and inFile: #Use first histogram
if inFile.GetListOfKeys().GetSize() > 1:
log.warning('Multiple objects found in {}. Only converting first histogram.'.format(args.in_file))
for x in range(0,inFile.GetListOfKeys().GetSize()): #Loop works even if only one element in file
if inFile.Get(inFile.GetListOfKeys()[x].GetName()).ClassName() == 'TH1F':
hist = inFile.Get(inFile.GetListOfKeys()[x].GetName())
break
else:
continue
if not hist and not args.ex_dict:
print "No histogram found in {} and no dictionary provided. Exiting!".format(args.in_file)
exit(1)
scaled_dict = extractDict(hist,args.total_events) #This function collects data from histogram and scales to the input
elif (args.in_hist and inFile):
hist = inFile.Get(args.in_hist)
scaled_dict = extractDict(hist,args.total_events)
#If a file with histogram was not provided, then scaled_dict should contain zero items at this point.
#Checking that a command line dictionary was provided, and scaled_dict not populated will keep the dictionary
# from being over written by EX_DICT, unless the scaled dictionary contains only mu values with zero scaled events
if args.ex_dict and (len(scaled_dict) == 0):
scaled_dict = scaleDict(eval(str(args.ex_dict)),args.total_events)
if scaled_integral == 0.0:
log.warning("All lumiblocks contain zero events after scaling! No luminosity in selected range.")
else:
log.info('Preparing a RunDMC task configuration object for {} total events'.format(args.total_events))
average_mu = sum([ pair[0]*pair[1] for pair in scaled_dict.items() if (pair[0] > 0) and (pair[1] > 0) ])
average_mu /= scaled_integral
log.info('Average pileup in this task is {}'.format(average_mu))
#Now place mu and events into list of lumiblock info called JobMaker, to be written into the configLumi file by dressSkeleton()
JobMaker = []
#Iterate through items in the dictionary and add to a list in the order they were added to dictionary, should be with increasing lb
#Also offsets the timestamp proportional to first nonzero lb.
#scaled_dict.items() are (mu,evts) tuples.
#Python sorts according to keys, so this loop will iterate over mu values, which is the order they were inserted
# This sorting is unnecessary if using >=python3.6 since dicts in 3.6 are apparently ordered by insertion order.
#Uses a counter variable i to calculate the lb starting at 1, and displacement from starting timestamp
i = 0
for item in sorted(scaled_dict.items()):
JobMaker.append( {'run':args.run_num,
'lb':i+1,
'starttstamp':args.start_tstamp+(i*60),
'dt':0.00,
'evts':item[1],
'mu':item[0]} )
i += 1
l = len(JobMaker)
log.info('There are {} lumiblocks in this task.'.format(l))
if l > 10:
log.info('Displaying first and last 5 lumiblocks:')
for j in JobMaker[:5]+JobMaker[-5:]: print " ",j
else:
log.info('Displaying all lumiblocks')
for j in JobMaker: print " ",j
allLB = iter(JobMaker)
skeletonName = "RunDependentSimData/OverrideRunLBLumiDigitConfig.py"
dressSkeleton(skeletonName,outFName,allLB)
if __name__=="__main__":
main()