416 `nominal` AnalysisObject (nominal object, which gives us the central values
417 The type is a dict of np.arrays() encoding information equivalent
418 to a Scatter2D, Histo1D, TGraphAsymmErrors or TH1D. See module description
419 for more information about this format. TODO: dedicated class for this?)
420 `formula` String (The formula with which to combine the components)
421 `componentsMap` dict(filenames, AnalysisObjects) (this is a map between the
422 file which the component corresponds to and the AnalysisObject which it
424 `level` Int [optional] (keeps track of how deep in the recursion we have gone)
425 `verbose` Int [optional] (0 or 1, whether or not to print(a lot of debug messages) )
426 `return` AnalysisObject
428 Resolves the formula iteratively, using the AnalysisObjects listed in the
429 componentsMap. resolveFormula supports the following functions:
431 `Central(arg1)`: Use the central value of arg1, and set errors to 0
433 `DownUpNominal(arg0, arg1, arg2)`: Produce a new AO where ydn is taken from
434 the central value ('y') of arg0, yup is taken from the central value of arg1,
435 and y is taken from the central value of arg2.
437 `Envelope(arg1,..., argN)`: Take the min/max envelope of the arguments as the
438 up/down errors of the resulting AO. The central value is taken from 'nominal'.
440 `Inverse(arg1)`: Take the inverse of the AO, so y--> 1/y, and the errors are
441 propagated as yerr --> |yerr/y **2|
443 `Product(arg1,..., argN)`: Take the product of the arguments of the function.
444 The 'y' value is the product of the 'y' values of the arguments, the errors
445 are propagated according the relative errors in quadrature.
448 `Average(arg1,..., argN)`: Take the product of the arguments of the function.
449 The 'y' value is the avergae of the 'y' values of the arguments, the errors
450 are taken from the first argument.
452 `QuadSum(arg1,.., argN)`: Sum in quadrature of the errors of the arguments,
453 and central value taken from nominal AO.
455 `Value(arg1)`: Used to scan an AO and its errors by the float specified in arg1.
457 `CopyError(arg1, arg2)`: Copy the up/down errors.
462 y, yup, ydn =
'y',
'yup',
'ydn'
463 if 'z' in nominal.keys():
464 y, yup, ydn =
'z',
'zup',
'zdn'
465 for i
in range(level): prefix +=
"-->"
466 if (verbose):
print(prefix +
" processing " + formula)
470 if ")" not in formula:
474 for k, v
in componentsMap.items():
475 componentName = k.split(
"/")[-1].
replace(
".yoda",
"").
replace(
".root",
"")
479 if componentName.split(
":")[0] ==
safeFileName(formula):
483 print(
"[ERROR] could not find a match for " + formula)
485 for k, v
in componentsMap.items():
486 print(k.split(
"/")[-1])
490 if (verbose):
print(prefix +
"--> result:" + result)
498 operation = formula.partition(
"(")[0]
499 operand = formula.partition(
"(")[-1].rpartition(
")")[0]
505 if operation ==
"Value":
506 arguments = [float(operand)]
512 argumentNames += [argument]
514 if (verbose):
print(prefix +
"argument of" + operation +
": " + arg)
522 if operation ==
"QuadSum":
524 for argument
in arguments:
525 if result
is None: result = copy.deepcopy(argument)
527 if not (np.allclose(nominal[y], argument[y])):
528 print(
"[ERROR] trying to take the quadratic sum of uncertainties which don't have the same central value!")
529 print(
"If you are using an alternative PDF directly int his function, please take the difference with the nominal and use that instead!")
531 result[yup] = nominal[y] + np.sqrt((argument[yup] - nominal[y]) ** 2 + (result[yup] - nominal[y]) ** 2)
532 result[ydn] = nominal[y] - np.sqrt((argument[ydn] - nominal[y]) ** 2 + (result[ydn] - nominal[y]) ** 2)
535 elif operation ==
"Central":
536 result = copy.deepcopy(arguments[0])
537 result[yup] = result[y]
538 result[ydn] = result[y]
543 elif operation ==
"DownUpNominal":
544 if len(arguments) != 3:
545 print(
"[ERROR] DownUpNominal() must take exactly three arguments, found " + len(arguments))
547 result = copy.deepcopy(arguments[2])
548 result[yup] = copy.deepcopy(arguments[1][y])
549 result[ydn] = copy.deepcopy(arguments[0][y])
554 elif operation ==
"Product":
556 for arg
in arguments:
557 if result
is None: result = copy.deepcopy(arg)
559 centralProduct = result[y] * arg[y]
560 upError = centralProduct * np.sqrt((
safeDiv(result[y] - result[yup], result[y])) ** 2 + (
safeDiv(arg[y] - arg[yup], arg[y])) ** 2)
561 dnError = centralProduct * np.sqrt((
safeDiv(result[y] - result[ydn], result[y])) ** 2 + (
safeDiv(arg[y] - arg[ydn], arg[y])) ** 2)
562 result[y] = centralProduct
563 result[yup] = centralProduct + upError
564 result[ydn] = centralProduct - dnError
565 elif operation ==
"Average":
568 for arg
in arguments:
569 if result
is None: result = copy.deepcopy(arg)
571 result[y] = result[y] + arg[y]
572 result[y] = result[y] / N
576 elif operation ==
"Envelope":
578 for i
in range(len(arguments)):
579 argumentDict[argumentNames[i]] = arguments[i]
581 result = copy.deepcopy(nominal)
585 elif operation ==
"EnvelopeIgnoreErrors":
587 for i
in range(len(arguments)):
588 argumentDict[argumentNames[i]] = arguments[i]
590 result = copy.deepcopy(nominal)
597 elif operation ==
"Inverse":
598 if len(arguments) != 1:
599 print(
"[ERROR] Inverse() must take exactly 1 argument, found " + len(arguments))
602 result = copy.deepcopy(arg)
603 result[y] =
safeDiv(np.ones(len(arg[y])), arg[y])
604 result[yup] = result[y] + np.abs(
safeDiv(arg[y] - arg[yup], arg[y] ** 2))
605 result[ydn] = result[y] - np.abs(
safeDiv(arg[y] - arg[ydn], arg[y] ** 2))
607 elif operation ==
"Value":
608 if len(arguments) != 1:
609 print(
"[ERROR] Value() must take exactly 1 argument, found " + len(arguments))
612 result = copy.deepcopy(nominal)
613 result[y] = arg * np.ones(len(result[y]))
614 result[yup] = arg * np.zeros(len(result[y]))
615 result[ydn] = arg * np.zeros(len(result[y]))
620 print(
"[ERROR] this operation" + operation +
" is not yet supported! Please implement it in resolveFormula() before continuing")
836 `filename` String (path to file which is to be read)
837 `regexFilter` String [optional] (AOs whose names match regex are processed)
838 `regexVeto` String [optional](AOs whose names match regex are NOT processed)
839 `return` dict{String, AnalysisObject}
841 Open a ROOT file, read the contents and return them as AnalysisObjects.
842 Control which AOs to select/reject using the optional regexFilter/regexVeto
844 Only supports TH1D and TGraphAsymmErrors types for now. TODO Support other types.
847 filename = filename.split(
":")
848 rootfile = r.TFile.Open(filename[0])
852 if len(filename) > 1:
853 if '/' in filename[1]:
854 path = filename[1].
split(
"/")[0]
855 tfd = rootfile.Get(path)
856 for aoName
in tfd.GetListOfKeys():
857 if debug:
print(
"DEBUG open ao:", aoName)
859 aoName = aoName.GetName()
861 aoName = path +
"/" + aoName.GetName()
862 if debug:
print(
"DEBUG modified name for ao:", aoName)
864 if debug:
print(
"DEBUG regex filte =%s, veto=%s" % (regexFilter, regexVeto))
865 if (regexFilter
is not None):
866 if debug:
print(
"DEBUG re.findall(regexFilter, aoName))", re.findall(regexFilter, aoName))
867 if (aoName
not in re.findall(regexFilter, aoName)):
868 if debug:
print(
"DEBUG ao is not in finall: continue")
870 if (regexVeto
is not None):
871 if debug:
print(
"DEBUG re.findall(regexVeto, aoName))", re.findall(regexVeto, aoName))
872 if (aoName
in re.findall(regexVeto, aoName)):
873 if debug:
print(
"DEBUG ao is in finall: continue")
877 if debug:
print(
"DEBUG start AONAME replacement, filename", filename)
878 if len(filename) > 1:
879 rep = filename[1].
replace(
"!AONAME",
"")
880 filt = filename[1].
replace(
"!AONAME",
".*")
881 capt = filename[1].
replace(
"!AONAME",
"(.*)")
882 if debug:
print(
"DEBUG AONAME replacement. rep=", rep,
"filt=", filt,
"capt=", capt)
883 if debug:
print(
"DEBUG AONAME re.findall(filt, aoName)", re.findall(filt, aoName))
884 if (aoName
not in re.findall(filt, aoName)):
885 if debug:
print(
"DEBUG AONAME not in re.findall(filt, aoName): continue")
888 newAoName = re.findall(capt, aoName)[0]
890 newAoName = aoName.replace(rep,
"")
891 ao = rootfile.Get(aoName)
895 aoResult[
'name'] = aoName
896 aoResult[
'path'] = filename[0] +
'/' + aoName
898 binRange = range(1, ao.GetNbinsX() + 1)
899 aoResult[
'info'] =
'Was_a_TH1'
900 aoResult[
'bw'] = np.array([ao.GetBinWidth(b)
for b
in binRange])
901 aoResult[
'y'] = np.array([ao.GetBinContent(b)
for b
in binRange])
902 stat = np.array([ao.GetBinError(b)
for b
in binRange])
903 aoResult[
'yup'] = (aoResult[
'y'] + stat)
904 aoResult[
'ydn'] = (aoResult[
'y'] - stat)
905 aoResult[
'x'] = np.array([ao.GetBinCenter(b)
for b
in binRange])
906 aoResult[
'xup'] = np.array([ao.GetBinLowEdge(b + 1)
for b
in binRange])
907 aoResult[
'xdn'] = np.array([ao.GetBinLowEdge(b)
for b
in binRange])
908 aoResult[
'nbins'] = len(binRange)
910 xbinRange = range(1, ao.GetNbinsX() + 1)
911 ybinRange = range(1, ao.GetNbinsY() + 1)
912 aoResult[
'info'] =
'Was_a_TH2'
913 aoResult[
'xbw'] = np.array([ao.GetXaxis().GetBinWidth(b)
for b
in xbinRange
for c
in ybinRange])
914 aoResult[
'ybw'] = np.array([ao.GetYaxis().GetBinWidth(c)
for b
in xbinRange
for c
in ybinRange])
915 aoResult[
'z'] = np.array([ao.GetBinContent(b, c)
for b
in xbinRange
for c
in ybinRange])
916 stat = np.array([ao.GetBinError(b, c)
for b
in xbinRange
for c
in ybinRange])
917 aoResult[
'zup'] = (aoResult[
'z'] + stat)
918 aoResult[
'zdn'] = (aoResult[
'z'] - stat)
919 aoResult[
'x'] = np.array([ao.GetXaxis().GetBinCenter(b)
for b
in xbinRange
for c
in ybinRange])
920 aoResult[
'xup'] = np.array([ao.GetXaxis().GetBinLowEdge(b + 1)
for b
in xbinRange
for c
in ybinRange])
921 aoResult[
'xdn'] = np.array([ao.GetXaxis().GetBinLowEdge(b)
for b
in xbinRange
for c
in ybinRange])
922 aoResult[
'y'] = np.array([ao.GetYaxis().GetBinCenter(c)
for b
in xbinRange
for c
in ybinRange])
923 aoResult[
'yup'] = np.array([ao.GetYaxis().GetBinLowEdge(c + 1)
for b
in xbinRange
for c
in ybinRange])
924 aoResult[
'ydn'] = np.array([ao.GetYaxis().GetBinLowEdge(c)
for b
in xbinRange
for c
in ybinRange])
925 aoResult[
'nbinsx'] = len(xbinRange)
926 aoResult[
'nbinsy'] = len(ybinRange)
927 elif type(r.TGraphAsymmErrors()) ==
type(ao):
928 binRange = range(0, ao.GetN())
929 aoResult[
'info'] =
'Was_a_TG'
930 aoResult[
'bw'] = np.array([ao.GetErrorXlow(b) + ao.GetErrorXhigh(b)
for b
in binRange])
931 aoResult[
'y'] = np.array([ao.GetY()[b]
for b
in binRange])
932 aoResult[
'yup'] = np.array([ao.GetY()[b] + ao.GetErrorYhigh(b)
for b
in binRange])
933 aoResult[
'ydn'] = np.array([ao.GetY()[b] - ao.GetErrorYlow(b)
for b
in binRange])
934 aoResult[
'x'] = np.array([ao.GetX()[b]
for b
in binRange])
935 aoResult[
'xup'] = np.array([ao.GetX()[b] + ao.GetErrorXhigh(b)
for b
in binRange])
936 aoResult[
'xdn'] = np.array([ao.GetX()[b] - ao.GetErrorXlow(b)
for b
in binRange])
937 aoResult[
'nbins'] = len(binRange)
939 aoResult[
'tree'] = ao.GetTree()
942 result[aoName] = aoResult
944 print(
"[ERROR] no analysis objects passed your filter, or your input files are empty of TH1 or TGraph objects. Exiting")
1181 `wName` String (Name of the systemtic weight to combine)
1182 `wInfo` dict in output format of the readDatabase.getWeight() tool.
1183 See https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/PmgSystTool
1185 `regexFilter` String [optional] (AOs whose names match regex are processed)
1186 `regexVeto` String [optional] (AOs whose names match regex are NOT processed)
1189 Produce and write a YODA file for the systematic uncertainty wName by
1190 combining the weights listed in wInfo['weights'], according to the procedure
1191 specified in wInfo['combination'] for each AO.
1193 The following values of wInfo['combination'] are suppored: stat, lhapdf,
1194 replicas, alphas, envelope, hessian, and customFunction (in this case, the
1195 formula given in wInfo['function'] is evaluated).
1198 if wInfo[
'combination'] ==
'lhapdf':
1199 if (
'nominal_pdf' not in wInfo.keys())
or (len(wInfo[
'weights']) == 1):
1202 wInfo[
'combination'] =
'stat'
1204 nominalPDF = int(wInfo[
'nominal_pdf'])
1206 pset = lhapdf.getPDFSet(pdfSetName)
1210 nominalHists =
readFromFile(wInfo[
'nominal'], regexFilter, regexVeto)
1211 variationHists_Var_AO = {}
1213 for var
in wInfo[
'weights']:
1214 if "Weight" in var:
continue
1215 if ":" in var
and 'yoda' in var:
1216 variationHists_Var_AO[var] =
readFromFile(var, regexFilter, regexVeto)
1218 variationHists_Var_AO[var] =
readFromFile(var, regexFilter, regexVeto)
1220 for var, aoDict
in variationHists_Var_AO.items():
1221 for ao, info
in aoDict.items():
1222 if ao
not in variationHists.keys():
1223 variationHists[ao] = {}
1224 variationHists[ao][var] = info
1227 outputHists = copy.deepcopy(nominalHists)
1228 for ao
in sorted(nominalHists.keys()):
1229 noms = nominalHists[ao]
1230 y, yup, ydn =
'y',
'yup',
'ydn'
1231 if 'z' in noms.keys():
1232 y, yup, ydn =
'z',
'zup',
'zdn'
1233 variations = variationHists[ao]
1234 systCentral = np.array(noms)
1235 systUp = np.array(noms)
1236 systDn = np.array(noms)
1240 if wInfo[
'type'] ==
'Nominal' or wInfo[
'combination'] ==
'stat':
1242 elif wInfo[
'combination'] ==
'lhapdf':
1244 if res
is None:
continue
1245 systCentral, systDn, systUp = res
1246 elif wInfo[
'combination'] ==
'replicas':
1248 elif wInfo[
'combination'] ==
'envelope':
1250 elif wInfo[
'combination'] ==
'alphaS':
1252 elif wInfo[
'combination'] ==
'hessian':
1254 elif wInfo[
'combination'] ==
'norm':
1256 elif wInfo[
'combination'] ==
'customFunction':
1259 print(
"[ERROR] combination type:" + wInfo[
'combination'] +
"is not yet supported... skipping this systematic uncertainty:" + wName)
1263 systCentral = (systCentral)
1265 outputHists[ao][y] = systCentral
1266 outputHists[ao][yup] = systUp
1267 outputHists[ao][ydn] = systDn
1271 print(
"[INFO] done combining this systematic: " + wName +
" using method: " + repr(wInfo[
'combination']))
1272 if wInfo[
'combination'] ==
'customFunction':
1273 print(
"[INFO] customFunction = " + repr(wInfo[
'function']))
1462 `mergedSystDict` dict{String, String} (format is as follows {N!label:filename},
1463 where N is the order in which to plot the variations, labels is what to put on
1464 the legend, and filename is the name of the YODA/ROOT file to use as input)
1465 `outdir` String (the output dir for the plots)
1466 `nominalName` String (which entry of mergedSystDict is the nominal? One of the
1467 entries in mergedSystDict should have this as a label)
1468 `ratioZoom` [Float, Float] or None (if not None, use this factor as the low/high limits of the
1470 `regexFilter` String [optional] (AOs whose names match regex are processed)
1471 `regexVeto` String [optional](AOs whose names match regex are NOT processed)
1472 `label` String [optional](additional label to add to the plots name)
1473 `plotInfo` String [optional](path to plot info file (will try to find it dynamically if not specified)
1474 `return` dict of output plots
1476 This is a generic macro which will make the output plots of the AOs contained
1477 in the files listed in mergedSystDict.
1480 r.gStyle.SetOptStat(00000)
1484 RIVET_ANALYSIS_PATH = os.environ.get(
"RIVET_ANALYSIS_PATH")
1485 os.system(
"mkdir -p %s" % (outdir))
1489 fileNames = [
"%s:'%s'" % (k, v)
for k, v
in sorted(mergedSystDict.items())]
1496 colorList = [r.kMagenta, r.kRed, r.kOrange, r.kYellow, r.kGreen, r.kBlue,
1497 r.kViolet, r.kPink, r.kSpring, r.kTeal, r.kCyan,
1505 nominalForRatio =
None
1506 for fn
in fileNames:
1507 if nominalName
in fn:
1508 nominalForRatio = copy.deepcopy(
readFromFile(fn.split(
":")[0]))
1509 if nominalForRatio
is None:
1510 print(
"WARNING, could not identify nominal ! exit()")
1514 for fn
in fileNames:
1516 aosThisFN =
readFromFile(fn.split(
":")[0], regexFilter, regexVeto)
1517 for aoName
in aosThisFN.keys():
1518 if "superAO" in aoName:
continue
1519 aoNameNoRef = aoName.replace(
"/REF",
"")
1520 ao = aosThisFN[aoName]
1521 if aoName
not in aos.keys():
1522 aos[aoNameNoRef] = {}
1523 aos_ratio[aoNameNoRef] = {}
1526 setYErrorsToZero =
False
1528 setYErrorsToZero =
True
1532 if aoName
in nominalForRatio.keys():
1533 nfr = nominalForRatio[aoName]
1534 elif "/REF" + aoName
in nominalForRatio.keys():
1535 nfr = nominalForRatio[
"/REF" + aoName]
1537 aos_ratio[aoNameNoRef][fn] =
arrayDictToTGraph(ao,
False, setYErrorsToZero, nfr)
1540 dataDir = os.environ[
"SYSTTOOLSPATH"] +
"/data/"
1541 rivetAnalysis = aoName.split(
"/")[1]
if len(aoName.split(
"/")) > 1
else ""
1542 pathInRivetEnv =
"%s/Rivet/%s.yoda" % (RIVET_ANALYSIS_PATH, rivetAnalysis)
1545 if fnIndex == len(fileNames) - 1
and 'Data' not in nominalName:
1548 if os.path.isfile(pathInRivetEnv):
1549 refDataPath = pathInRivetEnv
1550 elif os.path.isfile(
"%s.yoda" % rivetAnalysis):
1551 refDataPath =
"%s.yoda" % rivetAnalysis
1552 elif os.path.isfile(
"%s/%s.yoda" % (dataDir, rivetAnalysis)):
1553 refDataPath =
"%s/%s.yoda" % (dataDir, rivetAnalysis)
1559 theAOs =
readFromFile(refDataPath, regexFilter, regexVeto)
1561 if "/REF" + aoNameNoRef
in theAOs.keys()
or aoNameNoRef
in theAOs.keys():
1562 if "/REF" + aoNameNoRef
in theAOs.keys():
1563 theAO = theAOs[
"/REF" + aoNameNoRef]
1564 elif aoNameNoRef
in theAOs.keys():
1565 theAO = theAOs[aoNameNoRef]
1575 print(
"[WARNING] could not find AO with name " + aoNameNoRef +
" or " +
"REF" + aoNameNoRef +
" in REF file " + refDataPath)
1576 print(
" Perhaps due to a version mis-match where the AO names were changed... skip this data file for now")
1578 if plotInfo
is not None:
1579 plotInfoDict[aoNameNoRef] =
getPlotInfo(aoNameNoRef, plotInfo)
1580 elif os.path.isfile(pathInRivetEnv.replace(
"yoda",
"plot")):
1581 plotInfoDict[aoNameNoRef] =
getPlotInfo(aoNameNoRef, pathInRivetEnv.replace(
"yoda",
"plot"))
1582 elif os.path.isfile(
"%s.plot" % rivetAnalysis):
1583 plotInfoDict[aoNameNoRef] =
getPlotInfo(aoNameNoRef,
"%s.plot" % rivetAnalysis)
1584 elif os.path.isfile(
"%s/%s.plot" % (dataDir, rivetAnalysis)):
1585 plotInfoDict[aoNameNoRef] =
getPlotInfo(aoNameNoRef,
"%s/%s.plot" % (dataDir, rivetAnalysis))
1588 for k, v
in plotInfoDict.items():
1592 for aoName
in aos.keys():
1593 if 'RAW' in aoName:
continue
1596 for aoName
in aoNames:
1597 print(
"[INFO] processing ao %d/%d: %s " % (aoNames.index(aoName), len(aoNames), aoName))
1599 aoNameSafe = aoName.replace(
"/",
"__")
1602 nFilesToProcess = len(fileNames)
1603 leg = r.TLegend(0.17, 0.9 - 0.1 * ((2 + nFilesToProcess) / 2), 0.88, 0.80)
1611 latexes.append([11, 17, 0., 0.15, 0.905,
"#scale[1.2]{#bf{#it{ATLAS}}} Internal"])
1612 latexes.append([33, 15, 90., 0.005, 0.35,
safeRootLatex(
"Ratio to %s" % nominalName)])
1613 latexes.append([31, 15, 0., 0.9, 0.905, aoName])
1619 tc = r.TCanvas(aoNameSafe, aoNameSafe, 500, 500)
1620 pad1 = r.TPad(
"pad1",
"pad1", 0.0, 0.30, 1.0, 0.97)
1621 pad2 = r.TPad(
"pad2",
"pad2", 0.0, 0.05, 1.0, 0.35)
1622 r.SetOwnership(pad1,
False)
1623 r.SetOwnership(pad2,
False)
1624 pad1.SetLeftMargin(0.15)
1625 pad2.SetLeftMargin(0.15)
1626 pad1.SetBottomMargin(0.09)
1627 pad2.SetTopMargin(0.0)
1628 pad2.SetBottomMargin(0.15)
1637 rivetAnalysis = aoName.split(
"/")[1]
if len(aoName.split(
"/")) > 1
else ""
1638 averageErrorSize = {}
1639 for fn
in fileNames:
1643 fileNamesToProcess = []
1644 for k, v
in sorted(averageErrorSize.items(), key=
lambda x: x[1], reverse=
True):
1645 fileNamesToProcess += [k]
1647 if rivetAnalysis
in aos[aoName].keys():
1648 fileNamesToProcess += [rivetAnalysis]
1652 for fn
in fileNamesToProcess:
1658 if ":" in fn
and '[Data]' not in fn:
1660 legendName =
"%s" % fn.split(
":")[1].
replace(
"'",
"")
1663 legendName =
"[Data]"
1665 legendName +=
" MC Stat."
1667 aos[aoName][fn].SetLineStyle(2)
1668 aos_ratio[aoName][fn].SetLineStyle(2)
1669 if (
"PDF" in fn)
and (
'Nominal' not in fn):
1671 if len(re.findall(
r'PDF\d+', fn)) > 0:
1672 pdfstring = re.findall(
r'PDF\d+', fn)[0].
replace(
"PDF",
"")
1673 if len(pdfstring) > 2:
1675 legendName +=
" " + pdfsetName.partition(
"_")[0]
1676 legendName = (legendName[:20] +
'...')
if len(legendName) > 20
else legendName
1677 if 'Total Uncertainty' in fn:
1678 aos[aoName][fn].SetFillStyle(3004)
1679 aos[aoName][fn].SetFillColorAlpha(r.kBlack, 0.5)
1680 aos[aoName][fn].SetLineColor(r.kBlack)
1681 aos_ratio[aoName][fn].SetFillStyle(3004)
1682 aos_ratio[aoName][fn].SetFillColorAlpha(r.kBlack, 0.5)
1683 aos_ratio[aoName][fn].SetLineColor(r.kBlack)
1684 elif legendName !=
"[Data]":
1685 aos[aoName][fn].SetFillColorAlpha(colorList[fnIndex], 0.5)
1686 aos[aoName][fn].SetLineColor(colorList[fnIndex])
1687 aos_ratio[aoName][fn].SetFillColorAlpha(colorList[fnIndex], 0.5)
1688 aos_ratio[aoName][fn].SetLineColor(colorList[fnIndex])
1690 aos[aoName][fn].SetMarkerSize(1)
1691 aos[aoName][fn].SetMarkerStyle(20)
1692 aos[aoName][fn].SetLineWidth(3)
1693 aos_ratio[aoName][fn].SetMarkerSize(1)
1694 aos_ratio[aoName][fn].SetMarkerStyle(20)
1695 aos_ratio[aoName][fn].SetLineWidth(3)
1700 oldRatioZoom = ratioZoom
1702 if aoName
not in plotInfoDict.keys():
1703 plotInfoDict[aoName] = {}
1704 if 'LogY' not in plotInfoDict[aoName].keys():
1705 plotInfoDict[aoName][
'LogY'] = 0
1708 pad1.SetLogy(int(plotInfoDict[aoName][
'LogY']))
1709 if 'LogX' not in plotInfoDict[aoName].keys():
1710 plotInfoDict[aoName][
'LogX'] = 0
1714 pad1.SetLogx(int(plotInfoDict[aoName][
'LogX']))
1715 pad2.SetLogx(int(plotInfoDict[aoName][
'LogX']))
1719 XandYMinAndMax = [
None,
None,
None,
None]
1720 XandYMinAndMax_ratio = [
None,
None,
None,
None]
1722 if "XMin" in plotInfoDict[aoName].keys():
1723 XandYMinAndMax[0] = plotInfoDict[aoName][
'XMin']
1724 XandYMinAndMax_ratio[0] = plotInfoDict[aoName][
'XMin']
1726 if "XMax" in plotInfoDict[aoName].keys():
1727 XandYMinAndMax[1] = plotInfoDict[aoName][
'XMax']
1728 XandYMinAndMax_ratio[1] = plotInfoDict[aoName][
'XMax']
1730 if "YMin" in plotInfoDict[aoName].keys():
1731 XandYMinAndMax[2] = plotInfoDict[aoName][
'YMin']
1732 if "YMax" in plotInfoDict[aoName].keys():
1733 XandYMinAndMax[3] = plotInfoDict[aoName][
'YMax']
1735 if "RatioPlotYMin" in plotInfoDict[aoName].keys():
1736 if ratioZoom
is None: ratioZoom = [0., 2.]
1737 ratioZoom[0] = float(plotInfoDict[aoName][
'RatioPlotYMin'])
1738 if "RatioPlotYMax" in plotInfoDict[aoName].keys():
1739 if ratioZoom
is None: ratioZoom = [0., 2.]
1740 ratioZoom[1] = float(plotInfoDict[aoName][
'RatioPlotYMax'])
1742 dummyHisto =
makeDummyHisto(aos[aoName][fn], int(plotInfoDict[aoName][
'LogY']), XandYMinAndMax)
1743 dummyHisto_ratio =
makeDummyHisto(aos_ratio[aoName][fn],
False, XandYMinAndMax, ratioZoom,
True)
1744 ratioZoom = oldRatioZoom
1748 dummyHisto_ratio.Draw(
"")
1749 dummyHisto.GetYaxis().SetLabelFont(43)
1750 dummyHisto.GetYaxis().SetLabelSize(15)
1751 dummyHisto.GetXaxis().SetLabelFont(43)
1752 dummyHisto.GetXaxis().SetLabelSize(0)
1753 dummyHisto_ratio.GetXaxis().SetLabelFont(43)
1754 dummyHisto_ratio.GetXaxis().SetLabelSize(15)
1755 dummyHisto_ratio.GetYaxis().SetLabelFont(43)
1756 dummyHisto_ratio.GetYaxis().SetLabelSize(15)
1759 for k, v
in plotInfoDict[aoName].items():
1760 if k ==
'XTwosidedTicks':
1761 pad1.SetTickx(int(v))
1762 pad2.SetTickx(int(v))
1763 if k ==
'YTwosidedTicks':
1764 pad1.SetTicky(int(v))
1765 pad2.SetTicky(int(v))
1766 if k ==
'XCustomMajorTicks':
1767 for i
in range(1, dummyHisto.GetNbinsX() + 1):
1768 dummyHisto.GetXaxis().SetBinLabel(i,
"")
1769 dummyHisto_ratio.GetXaxis().SetBinLabel(i,
safeRootLatex(v.split()[2 * i - 1]))
1782 aos[aoName][fn].Draw(
"pe")
1784 aos_ratio[aoName][fn].Draw(
"pe")
1785 leg.AddEntry(aos[aoName][fn], legendName,
"pe")
1786 elif aos[aoName][fn].GetN() > 1:
1787 aos[aoName][fn].Draw(
"l3")
1789 aos_ratio[aoName][fn].Draw(
"l3")
1790 leg.AddEntry(aos[aoName][fn], legendName,
"lf")
1792 aos[aoName][fn].Draw(
"e")
1793 aos[aoName][fn].Draw(
"e2")
1795 aos_ratio[aoName][fn].Draw(
"e")
1796 aos_ratio[aoName][fn].Draw(
"e2")
1797 leg.AddEntry(aos[aoName][fn], legendName,
"lf")
1802 aos[aoName][fn].Draw(
"pe")
1804 aos_ratio[aoName][fn].Draw(
"pe")
1805 leg.AddEntry(aos[aoName][fn], legendName,
"pe")
1810 if aos[aoName][fn].GetN() > 1:
1811 aos[aoName][fn].Draw(
"l3")
1813 aos_ratio[aoName][fn].Draw(
"l3")
1815 aos[aoName][fn].Draw(
"e")
1816 aos[aoName][fn].Draw(
"e2")
1818 aos_ratio[aoName][fn].Draw(
"e")
1819 aos_ratio[aoName][fn].Draw(
"e2")
1821 if 'altPDF' in fn: legendMarker =
"l"
1822 leg.AddEntry(aos[aoName][fn], legendName, legendMarker)
1828 for latex
in latexes:
1829 lat.SetTextAlign(latex[0])
1830 lat.SetTextSize(latex[1])
1831 lat.SetTextAngle(latex[2])
1832 lat.DrawLatex(latex[3], latex[4], latex[5])
1835 tc.Print(
"%s/%s%s.pdf" % (outdir, label, aoNameSafe))
1836 tc.Print(
"%s/%s%s.png" % (outdir, label, aoNameSafe))
1837 outputPlots += [
"%s/%s%s.pdf" % (outdir, label, aoNameSafe)]
1966def combineAllVariations(weightList, indir, outdir, regexFilter=None, regexVeto=None, combinationRecipe=None, returnOnlyVariationsInComination=True, schema="!INDIR/!INFILE.yoda:!AONAME[!WEIGHTNAME]
", orderedWeights=None, inFile=None, customWeightNameReplacements=None):
1968 `weightList` dict in output format of the readDatabase.getWeight() tool.
1969 See https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/PmgSystTool
1971 `indir` String (input dir, where to find samples for each weight (merged
1972 across jobs/DSIDs if needed)
1973 `outdir` String (output dir, where to write the output files where the
1974 individual weights are combined into systematic variations)
1975 `regexFilter` String [optional] (AOs whose names match regex are processed)
1976 `regexVeto` String [optional](AOs whose names match regex are NOT processed)
1977 `combinationRecipe` String [optional] (if you want to use a specific
1978 combination recipe, specify it here. If None, then the code will try to
1979 auto-determine the correct recipe from the $SYSTTOOLSPATH/data/Syst_Database.yaml.
1980 supports String:String where the first part is the yaml file containing the recipe)
1981 `returnOnlyVariationsInComination` Bool [optional] (by default the code will return
1982 only a list of files for variations which take part in the combination. Set to False
1983 to return all possible variations)
1984 `schema` String [optional] (tells the code how the naming convention for histograms is
1985 structured within the input file.)
1986 `schema` List[weight names] [optional] (if the `!WEIGHTINDEX!` keyword is used in `schema`
1987 this option is needed so the code can convert from weight name to index)
1988 `inFile` String [optional] (needed if the `!INFILE!` keyword is used in `schema`)
1989 `customWeightNameReplacements` OrderedDict of custom replacements to sequentially apply
1990 to weight names, or txt file with one replacement per line, separated by "-->"
1991 `return` dict{String, String} (format is as follows {N!label:filename},
1992 where N is the order in which to plot the variations, labels is what to put on
1993 the legend, and filename is the name of the YODA/ROOT file to use as input)
1995 Using the information provided by the readDatabase tool (see
1996 https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/PmgSystTool),
1997 combine individual YODA/ROOT files for each Matrix Element weights into
1998 individual YODA/ROOT files for each systematic uncertainty.
2000 os.system(
"mkdir -p %s" % outdir)
2003 weightsToProcess = []
2007 averageErrorSize = {}
2008 weightList = copy.deepcopy(weightList)
2013 for k, v
in weightList.items():
2015 isRoot =
False if 'root' not in schema
else 'True'
2016 for oldWeight
in v[
'weights']:
2018 fileName = schema.replace(
"!NSFWEIGHTNAME", oldWeight)
2019 fileName = fileName.replace(
"!WEIGHTNAME",
safeFileName(oldWeight, removeExtension=
True))
2020 fileName = fileName.replace(
"!CRWEIGHTNAME",
customReplacements(oldWeight, removeExtension=
True, customReps=customWeightNameReplacements))
2021 fileName = fileName.replace(
"!INDIR", indir)
2022 if orderedWeights
is not None: fileName = fileName.replace(
"!WEIGHTINDEX",
"%d" % orderedWeights.index(oldWeight))
2023 if inFile
is not None:
2024 fileName = fileName.replace(
"!INFILE", inFile)
2025 fileName = fileName.replace(
".root.root",
".root")
2026 fileName = fileName.replace(
".yoda.yoda",
".yoda")
2027 if(os.path.isfile(fileName.split(
":")[0])):
2028 weights += [fileName]
2029 fOut[oldWeight] = fileName
2031 print(
"[ERROR] couldn't find file of name " + fileName +
" with either .root or .yoda extension!")
2033 v[
'weights'] = weights
2034 if v[
'type'] ==
'Nominal':
2035 NominalFile = weights[0]
2036 v[
'nominal'] = NominalFile
2037 weightsToProcess += [k]
2038 elif v[
'combination'] ==
'none':
pass
2040 fileName = schema.replace(
"!NSFWEIGHTNAME", v[
'nominal'])
2041 fileName = fileName.replace(
"!WEIGHTNAME",
safeFileName(v[
'nominal'], removeExtension=
True))
2042 fileName = fileName.replace(
"!CRWEIGHTNAME",
customReplacements(oldWeight, removeExtension=
True, customReps=customWeightNameReplacements))
2043 fileName = fileName.replace(
"!INDIR", indir)
2044 if orderedWeights
is not None: fileName = fileName.replace(
"!WEIGHTINDEX",
"%d" % orderedWeights.index(v[
'nominal']))
2045 if inFile
is not None:
2046 fileName = fileName.replace(
"!INFILE", inFile)
2047 fileName = fileName.replace(
".root.root",
".root")
2048 fileName = fileName.replace(
".yoda.yoda",
".yoda")
2049 if(os.path.isfile(fileName.split(
":")[0])):
2050 v[
'nominal'] = fileName
2052 print(
"[ERROR] couldn't find file of name " + fileName +
" with either .root or .yoda extension!")
2054 weightsToProcess += [k]
2055 allWeights += weights
2058 combinationRecipeFile, combinationRecipeName =
None, combinationRecipe
2059 if combinationRecipe:
2060 if ":" in combinationRecipe:
2061 combinationRecipeFile, combinationRecipeName = combinationRecipeName.split(
":")
2062 recipe, recipeDetails =
getCombinationRecipe(weightList.keys(), combinationRecipeFile, combinationRecipeName)
2063 if recipe
is not None: formula = recipeDetails[
'combination']
2068 for syst
in weightsToProcess:
2069 if isRoot: fOut[syst] =
'%s/%s.root' % (outdir, syst)
2070 else: fOut[syst] =
'%s/%s.yoda' % (outdir, syst)
2071 labels[syst] = weightList[syst][
'type']
2073 print(
"Doing %s with %d weights: %s" % (syst, len(weightList[syst][
'weights']), repr(weightList[syst])))
2074 combineVariation(syst, weightList[syst], fOut[syst], regexFilter, regexVeto)
2076 os.system(
"ls %s" % fOut[syst])
2081 if recipe
is not None:
2082 if 'user_defined_components' in recipeDetails.keys():
2083 for syst, details
in recipeDetails[
'user_defined_components'].items():
2084 if isRoot: fOut[syst] =
'%s/%s.root' % (outdir, syst)
2085 else: fOut[syst] =
'%s/%s.yoda' % (outdir, syst)
2086 labels[syst] = details[
'type']
2089 combinationInfo = {}
2090 combinationInfo[
'type'] = details[
'type']
2091 combinationInfo[
'nominal'] = NominalFile
2093 combinationInfo[
'combination'] =
"customFunction"
2094 combinationInfo[
'function'] = details[
'combination']
2097 combineVariation(syst, combinationInfo, fOut[syst], regexFilter, regexVeto)
2099 os.system(
"ls %s" % fOut[syst])
2102 print(
"[INFO] combining uncertainties using the following recipe:" + recipe)
2103 print(
"[INFO] which uses formula: " + formula)
2106 combinationInfo = {}
2107 combinationInfo[
'type'] =
'All'
2108 combinationInfo[
'nominal'] = NominalFile
2109 combinationInfo[
'weights'] = fOut.values()
2110 combinationInfo[
'combination'] =
"customFunction"
2111 combinationInfo[
'function'] = formula
2114 fOut[
'all'] =
'%s/all.yoda' % outdir
2115 if isRoot: fOut[
'all'] =
'%s/all.root' % outdir
2116 else: fOut[
'all'] =
'%s/all.yoda' % outdir
2117 combineVariation(
'All', combinationInfo, fOut[
'all'], regexFilter, regexVeto)
2119 os.system(
"ls %s" % fOut[syst])
2123 result = OrderedDict()
2125 formulaComponents = averageErrorSize.keys()
2127 for k, v
in sorted(averageErrorSize.items(), key=
lambda x: x[1], reverse=
True):
2129 result[fOut[k]] =
"[Total Uncertainty]"
2132 for component
in formulaComponents:
2133 if component == k: isComponent =
True
2134 if (
not isComponent)
and returnOnlyVariationsInComination:
continue
2135 result[fOut[k]] =
'[%s]' % labels[k]
2355 This module can also be run as a standalone executable.
2356 For info about the options try:
2357 systematicsTool.py -h
2359 This is useful for unpacking large GRID job outputs, as it will do all the
2360 book-keeping, untarring of tarballs, merging across jobs, across subsamples,
2361 across samples weighted by DSID, and then finally processing the systematics
2362 and plotting them. Multithreading available.
2364 parser = optparse.OptionParser(usage=
"%prog [options]")
2365 parser.add_option(
"-d",
"--directory", help=
"Directory containing the rucio downloads.", dest=
"directory", default=
"samples")
2366 parser.add_option(
"-p",
"--process", help=
"Name of process to merge. Will only apply merge to directories containing this name. Also accepts the format regex:label, where the label is used to name the output directory, and the regex to select which directories to merge. Accepts comma separated list of regex:label pairs", dest=
"process", default=
"Zee")
2367 parser.add_option(
"-g",
"--generator", help=
"Name of generator to merge. Will only apply merge to directories containing this name. Also accepts the format regex:label, where the label is used to name the output directory, and the regex to select which directories to merge. Accepts comma separated list of regex:label pairs", dest=
"generator", default=
"Sherpa")
2368 parser.add_option(
"--force", help=
"By default, if a subsample has already been untarred and merged, it is not done again. This option forces the script to re-untar and re-merge. Slow, unless you use a lot of threads...", dest=
"force", default=
False)
2369 parser.add_option(
"--plotsDir", help=
"Where to save the plots to? Default is <generator>_<process>_<label>/plots", dest=
"plotsDir", default=
None)
2370 parser.add_option(
"-l",
"--label", help=
"An additional label for the output directory of the merge and as a prefix to the plots", dest=
"label", default=
"")
2371 parser.add_option(
"-f",
"--filter", help=
"Only process AOs matching this regex", dest=
"filter", default=
".*")
2372 parser.add_option(
"-e",
"--exclude", help=
"Do NOT process AOs matching this regex", dest=
"exclude", default=
None)
2373 parser.add_option(
"-j",
"--nThreads", help=
"Multithread some of the merging steps", dest=
"nThreads", default=4)
2374 parser.add_option(
"--noSyst", help=
"Ignore systematics, only merge/process/draw the nominal weight", dest=
"noSyst", default=
False, action=
"store_true")
2375 parser.add_option(
"--notTGZ", help=
"Use if your input files were just yoda/root files not tarballs", dest=
"notTGZ", default=
False, action=
"store_true")
2376 parser.add_option(
"--uxs",
"--userXS", help=
"When looking up cross-sections for each DSID, check the specified file first before defaulting to CVMFS values", dest=
"userXS", default=
None)
2377 parser.add_option(
"--nw",
"--nominalWeight", help=
"Bypass the DSID_weights database, and use the specified name as the nominal. Only for us with --noSyst", dest=
"nominalWeight", default=
"")
2378 parser.add_option(
"--sp",
"--skipPlots", help=
"Do the merging of files, but just skip the plotting at the end", dest=
"skipPlots", default=
False, action=
"store_true")
2379 parser.add_option(
"--normalize", help=
"Normalize any output plots to unit integral", dest=
"normalize", default=
False, action=
"store_true")
2380 parser.add_option(
"--r2n",
"--rivet2Normalised", help=
"Are your histograms normalised by the sum of weights in each variation, as is done typically for Rivet2 analyses? specify 1/0 for True/False", dest=
"rivetNormalised", default=
False)
2381 parser.add_option(
"--sw",
"--sumwHistName", help=
"Name of the TH1 that stores the sum-of-weights in the case of root file", dest=
"sumwHistName", default=
"__EVTCOUNT")
2382 parser.add_option(
"--cr",
"--customReplacements", help=
"A .txt file specifying which replacements to apply to weight names, one per line, in the format `<old string>--><new string>` (without quotes), eg ` -->` to replace spaces with nothing, and `.-->_` to replace dots with underscores. One replacement per line which will be applied sequentially in the order listed in the file.", dest=
"customReplacements", default=
None)
2383 parser.add_option(
"--schema", help=
"Tells the tool how to access the analysis objects from the inputs provided. Format is PathToFile:ObjectName. For example, if you have one YODA file per MC Weight, and each histogram is named the same in each input file, you schema would be !INDIR/!WEIGHTNAME.yoda:!AONAME. If you instead store in a single root file all the variations, where the weight name is appended to the histo name, your schema would be !INDIR/!INFILE.yoda:!AONAME[!WEIGHTNAME]. The available identifiers are: !INDIR (path to input dir), !INFILE (name of input file), !AONAME (analysis object name), !WEIGHTNAME (the MC weight name, assuming that safeFileName was applied to changes spaces to underscores etc...), !NSFWEIGHTNAME (assuming no weight name replacements at all), or !CRWEIGHTNAME (using custom repalcements, in this case please use option --customReplacements or --cr to specify them)", dest=
"schema", default=
'!INDIR/!INFILE.yoda:!AONAME[!WEIGHTNAME]')
2384 parser.add_option(
"--plotInfo", help=
"A rivet-style .plot file to tell the tool how to format your output plots. See data/ExclusivePlusInclusive.plot as an example. Also works for plots make from root files!", dest=
"plotInfo", default=
None)
2385 (opts, args) = parser.parse_args()
2387 r.gErrorIgnoreLevel = r. kError
2390 for process
in opts.process.split(
","):
2392 tokens = process.split(
":")
2393 processRegex = tokens[0]
2396 processRegex = process
2400 for generator
in opts.generator.split(
","):
2401 if ":" in generator:
2402 tokens = generator.split(
":")
2403 generatorRegex = tokens[0]
2404 generator = tokens[1]
2406 generatorRegex = generator
2408 if opts.rivetNormalised
is not None:
2409 opts.rivetNormalised = bool(int(opts.rivetNormalised))
2412 if opts.rivetNormalised
is True:
2413 if "--schema" not in sys.argv:
2414 opts.schema =
"!INDIR/!WEIGHTNAME.yoda:!AONAME"
2416 rootOrYoda =
'root' if '.root' in opts.schema
else 'yoda'
2417 if "WEIGHT" in opts.schema.split(
":")[0]:
2418 structure =
"OneFilePerVariation"
2420 structure =
"AllVariationsInFile"
2423 thisFilter =
'.*%s.*%s.*' % (generatorRegex, processRegex)
2426 if opts.plotsDir
is None:
2427 opts.plotsDir =
"%s_%s_%s/plots" % (process, generator, opts.label)
2428 os.system(
"mkdir -p %s" % opts.plotsDir)
2432 jobsMergedFiles = {}
2433 print(
"================================================================================")
2434 print(
"[INFO] Processing files which match this regex " + thisFilter)
2435 print(
"================================================================================")
2440 print(
"-----------------------------------------------------------------------")
2441 print(
"[INFO] Untar Grid outputs ")
2442 print(
"------------------------------------------------------------------------")
2445 pool = ThreadPool(int(opts.nThreads))
2449 samplesToProcess = []
2450 workingDir = opts.directory
2451 if '/afs/' not in workingDir: workingDir = pwd +
"/" + opts.directory
2453 for sample
in os.listdir(workingDir):
2455 fulldir = workingDir +
"/" + sample
2456 if not re.search(thisFilter, sample):
continue
2457 if not re.search(
'user.', sample):
continue
2459 if not os.path.isfile(fulldir):
2460 samplesToProcess += [fulldir]
2461 for fulldir
in samplesToProcess:
2462 threadInputs.append([fulldir, opts.force])
2463 untarredFiles[fulldir] = {}
2464 if len(threadInputs) == 0:
2465 print(
"[ERROR] no directories in %s matches the requested regex! exit !" % workingDir)
2468 def threadJobFunction(inputs):
2471 results = pool.map(threadJobFunction, threadInputs)
2474 for jobResult
in results:
2475 for res
in jobResult:
2476 if structure ==
"OneFilePerVariation":
2477 untarredFiles[res[0]].setdefault(res[1], []).append(res[2])
2479 untarredFiles[res[0]].setdefault(
'AllVariations', []).append(res[2])
2483 print(
"------------------------------------------------------------------------")
2484 print(
"[INFO] Merging across jobs for each DSID")
2485 print(
"------------------------------------------------------------------------")
2487 for fulldir
in sorted(untarredFiles.keys()):
2488 print(
"[INFO] Merging files for jobs of " + os.path.basename(fulldir))
2490 infiles = untarredFiles[fulldir]
2491 nFileTypes = len(infiles.keys())
2493 print(
"[ERROR] 0 file types found in inputs! Exit!")
2494 print(untarredFiles)
2500 def threadJobFunction(inputs):
2501 mergeInChunks(inputs[0], inputs[1], progressDict=inputs[2], rootOrYoda=rootOrYoda)
2504 pool = ThreadPool(int(opts.nThreads))
2508 progressDict[
'N'] = int(opts.nThreads)
2509 for fn, paths
in infiles.items():
2512 progressDict[basename] =
'pend'
2513 outfn =
"%s/merged/%s.%s" % (fulldir, basename, rootOrYoda)
2514 sampleName = os.path.basename(fulldir)
2515 threadInputs.append([outfn, paths, progressDict])
2516 jobsMergedFiles.setdefault(basename, {})[sampleName] = outfn
2517 results = pool.map(threadJobFunction, threadInputs)
2520 print(
'\r[INFO] Done Merging %s' % os.path.basename(fulldir))
2524 print(
"------------------------------------------------------------------------")
2525 print(
"[INFO] Get available MEweights from DSID database and make sure they are consistent!")
2526 print(
"------------------------------------------------------------------------")
2530 def threadJobFunction(inputs):
2534 dsid = re.findall(
r"\.[0-9]{6,6}\.", fulldir)[0].
replace(
".",
"")
2535 print(
"\r[INFO] Looking up XS and MEWeights for DSID = " + repr(dsid))
2536 if rootOrYoda ==
'yoda' and getXSFromYODA(jobsMergedFiles.values()[0][os.path.basename(fulldir)]) != 1.0:
2537 print(
"\r[INFO] Histograms in YODA file of DSID =%s are already scaled by xsec. Set xsec to 1.0 to avoid double scaling!" % dsid)
2538 xsDict[dsid] = 1.0, 0, 0
2540 xsDict[dsid] =
getXS(int(dsid), userFile=opts.userXS)
2541 availableWeights =
None
2542 nominalWeight =
None
2544 if availableWeights
is None:
2545 availableWeights = rDB.getWeights(dsid)[0]
2546 for k, v
in availableWeights.items():
2547 if v[
'type'] ==
'Nominal':
2548 nominalWeight = v[
'nominal']
2551 nominalWeight = jobsMergedFiles.keys()[0]
2552 if len(opts.nominalWeight) > 0: nominalWeight = opts.nominalWeight
2553 availableWeights = {
'_Nominal':
2554 {
'nominal_pdf':
'-1',
'type':
'Nominal',
'nominal':
'%s' % nominalWeight,
2555 'weights': [
'%s' % nominalWeight],
'combination':
'none'
2558 if xsDict[dsid][2] > 0:
2559 availableWeights[
"Normalization"] = {
'nominal_pdf':
'-1',
'type':
'Normalization',
'nominal': nominalWeight,
'weights': [nominalWeight],
'combination':
'norm',
"value": xsDict[dsid][2]}
2561 return [availableWeights, dsid]
2563 pool = ThreadPool(int(opts.nThreads))
2565 for fulldir
in sorted(untarredFiles.keys()):
2566 threadInputs.append([fulldir, opts.noSyst, xsDict])
2568 results = pool.map(threadJobFunction, threadInputs)
2573 print(
"[INFO] Consistentcy Checks:")
2574 availableWeights =
None
2576 for dsid, xs
in xsDict.items():
2577 if (xsDict[dsid][0] == -1):
2578 print(
"\r[INFO] ERROR: could not find Xs for DSID =%s. Exit." % (str(dsid)))
2581 print(
"\r[INFO] DSID =%s has XS =%.6f pb (kFactor =%.2f, normUnvertainty =%.2f)" % (str(dsid), xsDict[dsid][0], xsDict[dsid][1], xsDict[dsid][2]) +
" and found ME weights")
2583 if availableWeights
is None:
2584 availableWeights = res[0]
2585 print(
"[INFO] DSID = " + res[1] +
" has weights" + repr(availableWeights.keys()))
2587 elif(sorted(availableWeights.keys()) == sorted(res[0].keys())):
2588 print(
"[INFO] DSID = " + res[1] +
" has weights consistent with the above")
2590 print(
"[ERROR] DSID = " + res[1] +
" has weights NOT consistent with the above!!")
2593 for k, v
in availableWeights.items():
2596 for w
in v[
'weights']:
2597 if w
in uniqueWeights:
continue
2598 else: uniqueWeights += [w]
2600 if (len(uniqueWeights) == 0
and not opts.noSyst):
2601 print(
"[ERROR] Your sample DISD = " + res[1] +
" has no weights... so it might be missing from the database!")
2602 print(
"[ERROR] Please contact lcorpe@cern.ch to get your sample added...")
2603 print(
"[ERROR] In the meantime, we will exit since no combination recipe is availale... you can still run with --noSyst")
2608 print(
"------------------------------------------------------------------------")
2609 print(
"[INFO] Calculating on the fly corrections to XSs for each systematic")
2610 print(
"------------------------------------------------------------------------")
2612 if opts.rivetNormalised
is None:
2613 if rootOrYoda ==
'yoda': opts.rivetNormalised =
True
2614 else: opts.rivetNormalised =
True
2616 allFilesToMerge = {}
2618 def threadJobFunction(inputs):
2620 progressDict = inputs[4]
2624 return systName, res
2626 pool = ThreadPool(int(opts.nThreads))
2630 applyKFactorCorrection =
None
2631 progressDict[
'N'] = int(opts.nThreads)
2632 for syst
in uniqueWeights:
2633 if structure ==
"AllVariationsInFile":
2634 sample = jobsMergedFiles.values()[0]
2637 systSampleFiles = []
2640 for sampleName, path
in sample.items():
2641 dsid = re.findall(
"[0-9]{6,6}", sampleName)[0].
replace(
".",
"")
2642 xsList += [xsDict[dsid][0]]
2643 if applyKFactorCorrection
is None: applyKFactorCorrection = xsDict[dsid][1]
2644 elif applyKFactorCorrection != xsDict[dsid][1]:
2645 print(
"[ERROR], cannot merge samples with inconsistent k-factors !")
2649 systSampleFiles += [path]
2650 if structure ==
"AllVariationsInFile":
2651 nomSampleFiles += [jobsMergedFiles.values()[0][sampleName]]
2653 nomSampleFiles += [jobsMergedFiles[
safeFileName(nominalWeight[
'nominal'])][sampleName]]
2655 if structure ==
"AllVariationsInFile":
2656 threadInputs += [[syst, xsList, systSampleFiles, nomSampleFiles, progressDict, opts.rivetNormalised, applyKFactorCorrection,
safeFileName(syst),
safeFileName(nominalWeight[
'nominal'])]]
2658 threadInputs += [[syst, xsList, systSampleFiles, nomSampleFiles, progressDict, opts.rivetNormalised, applyKFactorCorrection,
"",
""]]
2659 progressDict[syst] =
'pend'
2661 results = pool.map(threadJobFunction, threadInputs)
2665 for jobResult
in results:
2667 for systFile, weight
in jobResult[1]:
2668 allFilesToMerge.setdefault(syst, []).append(
" %s:%f" % (systFile, weight))
2671 print(
"------------------------------------------------------------------------")
2672 print(
"[INFO] Merging files across DSIDs for each systematic")
2673 print(
"------------------------------------------------------------------------")
2675 def threadJobFunction(inputs):
2678 generator = inputs[1]
2681 os.system(
"mkdir -p %s_%s_%s/merged_sample" % (process, generator, opts.label))
2682 if rootOrYoda ==
'root':
2684 if structure ==
"AllVariationsInFile":
2685 mergedFile =
"%s_%s_%s/merged_sample/%s.%s" % (process, generator, opts.label,
safeFileName(s), rootOrYoda)
2688 command =
"haddw -f %s -r %s %s %s &> out.txt " % (thisFilter, thisFindAndReplace, mergedFile,
" ".join(allFilesToMerge[
safeFileName(s)]))
2689 if debug:
print(command)
2692 if structure ==
"OneFilePerVariation":
2693 mergedFile =
"%s_%s_%s/merged_sample/%s.%s" % (process, generator, opts.label,
safeFileName(s), rootOrYoda)
2694 command =
"haddw %s %s &> out.txt " % (mergedFile,
" ".join(allFilesToMerge[s]))
2697 mergedFile =
"%s_%s_%s/merged_sample/%s.%s" % (process, generator, opts.label,
safeFileName(s), rootOrYoda)
2698 if structure ==
"AllVariationsInFile":
2700 command =
r"yodamerge_tmp.py --add -o %s %s -m r'.*\[%s\].*' &> out.txt " % (mergedFile,
" ".join(allFilesToMerge[
safeFileName(s)]),
rivetINameReplacements(s, escapePlus=
True))
2708 command =
"yodascale -c '.* %fx' -i %s &> out.txt \n" % (float(sampleToMerge[1]), mergedFile)
2709 if debug:
print(command)
2713 command =
r"yodamerge_tmp.py --add -o %s %s -M .*\\\[.*\\\].* &> out.txt " % (mergedFile,
" ".join(allFilesToMerge[
safeFileName(s)]))
2714 if debug:
print(command)
2718 command =
"yodascale -c '.* %fx' -i %s &> out.txt \n" % (float(sampleToMerge[1]), mergedFile)
2719 if debug:
print(command)
2721 if structure ==
"OneFilePerVariation":
2722 if len(allFilesToMerge[s]) == 1:
2723 sampleToMerge = allFilesToMerge[s][0].
split(
":")
2724 command =
"cp %s %s ; yodascale -c '.* %fx' -i %s &> out.txt \n" % (sampleToMerge[0], mergedFile, float(sampleToMerge[1]), mergedFile)
2726 command =
"yodamerge_tmp.py --add -o %s %s &> out.txt " % (mergedFile,
" ".join(allFilesToMerge[s]))
2727 if debug:
print(command)
2732 if rootOrYoda ==
'root': nThreads = 1
2733 else: nThreads = int(opts.nThreads)
2734 pool = ThreadPool(nThreads)
2737 progressDict[
'N'] = nThreads
2738 if (structure ==
"AllVariationsInFile"):
2739 for i, s
in enumerate(uniqueWeights):
2740 progressDict[s] =
'pend'
2741 threadInputs.append([s, generator, process, progressDict])
2742 elif (structure ==
"OneFilePerVariation"):
2743 for i, s
in enumerate(allFilesToMerge.keys()):
2744 progressDict[s] =
'pend'
2745 threadInputs.append([s, generator, process, progressDict])
2747 results = pool.map(threadJobFunction, threadInputs)
2753 print(
"------------------------------------------------------------------------")
2754 print(
"[INFO] Combining variations into named sources of uncertainty")
2755 print(
"------------------------------------------------------------------------")
2757 mergedSystDict =
combineAllVariations(availableWeights,
"%s_%s_%s/merged_sample" % (process, generator, opts.label),
"%s_%s_%s/merged_final/." % (process, generator, opts.label), opts.filter, opts.exclude, schema=
"!INDIR/!WEIGHTNAME.%s:!AONAME" % rootOrYoda, customWeightNameReplacements=opts.customReplacements)
2760 print(
"-----------------------------------------------")
2761 print(
"[INFO] Produce plots of the systematic uncertainties (with bands)")
2762 print(
"-----------------------------------------------")
2764 if not opts.skipPlots
and rootOrYoda ==
"yoda":
2765 plots =
makeSystematicsPlotsWithRIVET(mergedSystDict, opts.plotsDir,
"Nominal", label=opts.label, plotInfo=opts.plotInfo, normalize=opts.normalize)
2767 print(
"[INFO] printed: " + p)