00001
00002
00003 import sys
00004 import re
00005 import optparse
00006 from pprint import pprint
00007 import array
00008 import ROOT
00009 import math
00010
00011 sepRE = re.compile (r'[\s,;:]+')
00012 nonSpaceRE = re.compile (r'\S')
00013
00014
00015
00016
00017
00018
00019
00020 class LumiInfo (object):
00021
00022 lastSingleXingRun = 136175
00023 lumiSectionLength = 23.310779
00024
00025 def __init__ (self, line):
00026 self.totInstLum = 0.
00027 self.aveInstLum = 0.
00028 self.numXings = 0
00029 self.instLums = []
00030 self.events = []
00031 self.xingInfo = False
00032 self.badXingInfo = False
00033 pieces = sepRE.split (line.strip())
00034 size = len (pieces)
00035 if size % 2:
00036 raise RuntimeError, "Odd number of pieces"
00037 if size < 4:
00038 raise RuntimeError, "Not enough pieces"
00039 try:
00040 self.run = int (pieces[0])
00041 self.lumi = int (pieces[1])
00042 self.delivered = float (pieces[2])
00043 self.recorded = float (pieces[3])
00044 except:
00045 raise RuntimeError, "Pieces not right format"
00046 if size > 4:
00047 try:
00048 for xing, lum in zip (pieces[4::2],pieces[5::2]):
00049 xing = int (xing)
00050 lum = float (lum)
00051 self.instLums.append( (xing, lum) )
00052 self.totInstLum += lum
00053 self.numXings += 1
00054 except:
00055 raise RuntimeError, "Inst Lumi Info malformed"
00056 self.aveInstLum = self.totInstLum / (self.numXings)
00057 self.xingInfo = True
00058 self.key = (self.run, self.lumi)
00059 self.keyString = self.key.__str__()
00060
00061
00062 def fixXingInfo (self):
00063 if self.numXings:
00064
00065
00066 raise RuntimeError, "This event %s already has Xing information" \
00067 % self.keyString
00068 if self.run > LumiInfo.lastSingleXingRun:
00069
00070
00071 self.badXingInfo = True
00072 return False
00073 self.numXings = 1
00074 xing = 1
00075 self.aveInstLum = self.totInstLum = lum = \
00076 self.delivered / LumiInfo.lumiSectionLength
00077 self.instLums.append( (xing, lum) )
00078 self.xingInfo = True
00079 return True
00080
00081
00082 def deadtime (self):
00083 if not self.delivered:
00084 return 1
00085 return 1 - (self.recorded / self.delivered)
00086
00087
00088 def __str__ (self):
00089 return "%6d, %4d: %6.1f (%4.1f%%) %4.2f (%3d)" % \
00090 (self.run,
00091 self.lumi,
00092 self.delivered,
00093 self.deadtime(),
00094 self.totInstLum,
00095 self.numXings)
00096
00097
00098
00099
00100
00101
00102
00103
00104 class LumiInfoCont (dict):
00105
00106 def __init__ (self, filename, **kwargs):
00107 print "loading luminosity information from '%s'." % filename
00108 source = open (filename, 'r')
00109 self.minMaxKeys = ['totInstLum', 'aveInstLum', 'numXings',
00110 'delivered', 'recorded']
00111 self._min = {}
00112 self._max = {}
00113 self.totalRecLum = 0.
00114 self.xingInfo = False
00115 self.allowNoXing = kwargs.get ('ignore')
00116 self.noWarnings = kwargs.get ('noWarnings')
00117 self.minRun = 0
00118 self.maxRun = 0
00119 self.minIntLum = 0
00120 self.maxIntLum = 0
00121
00122 for key in self.minMaxKeys:
00123 self._min[key] = -1
00124 self._max[key] = 0
00125 for line in source:
00126 try:
00127 lumi = LumiInfo (line)
00128 except:
00129 continue
00130 self[lumi.key] = lumi
00131 self.totalRecLum += lumi.recorded
00132 if not self.xingInfo and lumi.xingInfo:
00133 self.xingInfo = True
00134 if lumi.xingInfo:
00135
00136 if not self.xingInfo:
00137 print "huh?"
00138 for key in self.minMaxKeys:
00139 val = getattr (lumi, key)
00140 if val < self._min[key] or self._min[key] < 0:
00141 self._min[key] = val
00142 if val > self._max[key] or not self._max[key]:
00143 self._max[key] = val
00144 source.close()
00145
00146
00147
00148
00149
00150 self.invunits = 'nb'
00151 lumFactor = 1e3
00152 if self.totalRecLum > 1e9:
00153 lumFactor = 1e9
00154 self.invunits = 'fb'
00155 elif self.totalRecLum > 1e6:
00156 lumFactor = 1e6
00157 self.invunits = 'pb'
00158
00159
00160 self.totalRecLum /= lumFactor
00161 for lumis in self.values():
00162 lumis.delivered /= lumFactor
00163 lumis.recorded /= lumFactor
00164
00165
00166 self._integrateContainer()
00167
00168
00169
00170 def __str__ (self):
00171 retval = 'run, lum del ( dt ) inst (#xng)\n'
00172 for key, value in sorted (self.iteritems()):
00173 retval += "%s\n" % value
00174 return retval
00175
00176
00177 def min (self, key):
00178 return self._min[key]
00179
00180
00181 def max (self, key):
00182 return self._max[key]
00183
00184
00185 def keys (self):
00186 return sorted (dict.keys (self))
00187
00188
00189 def iteritems (self):
00190 return sorted (dict.iteritems (self))
00191
00192
00193 def _integrateContainer (self):
00194
00195 total = 0.
00196 for key, lumi in self.iteritems():
00197 total += lumi.recorded
00198 lumi.totalRecorded = total
00199 lumi.fracRecorded = total / self.totalRecLum
00200
00201 if not self.xingInfo:
00202
00203 return
00204 xingKeyList = []
00205 maxAveInstLum = 0.
00206 for key, lumi in self.iteritems():
00207 if not lumi.xingInfo and not lumi.fixXingInfo():
00208 if not self.noWarnings:
00209 print "Do not have lumi xing info for %s" % lumi.keyString
00210 if not self.allowNoXing:
00211 print "Setting no Xing info flag"
00212 self.xingInfo = False
00213 return
00214 continue
00215 xingKeyList.append( (lumi.aveInstLum, key) )
00216 if lumi.aveInstLum > maxAveInstLum:
00217 maxAveInstLum = lumi.aveInstLum
00218 xingKeyList.sort()
00219 total = 0.
00220 for tup in xingKeyList:
00221 lumi = self[tup[1]]
00222 total += lumi.recorded
00223 lumi.totalAXILrecorded = total
00224 lumi.fracAXILrecorded = total / self.totalRecLum
00225 lumi.fracAXIL = lumi.aveInstLum / maxAveInstLum
00226
00227
00228
00229
00230
00231
00232
00233
00234 def loadEvents (filename, cont, options):
00235 eventsDict = {}
00236 print "loading events from '%s'" % filename
00237 events = open (filename, 'r')
00238 runIndex, lumiIndex, eventIndex, weightIndex = 0, 1, 2, 3
00239 if options.relOrder:
00240 lumiIndex, eventIndex = 2,1
00241 minPieces = 3
00242 totalWeight = 0.
00243 if options.weights:
00244 minPieces = 4
00245 for line in events:
00246 pieces = sepRE.split (line.strip())
00247 if len (pieces) < minPieces:
00248 if nonSpaceRE.search (line):
00249 print "skipping", line
00250 continue
00251 try:
00252 run, lumi, event = int( pieces[runIndex] ), \
00253 int( pieces[lumiIndex] ), \
00254 int( pieces[eventIndex] )
00255 except:
00256 continue
00257 key = (run, lumi)
00258 if not cont.has_key (key):
00259 if options.ignore:
00260 print "Warning, %s is not found in the lumi information" \
00261 % key.__str__()
00262 continue
00263 else:
00264 raise RuntimeError, "%s is not found in lumi information. Use '--ignoreNoLumiEvents' option to ignore these events and continue." \
00265 % key.__str__()
00266 if options.edfMode != 'time' and not cont[key].xingInfo:
00267 if options.ignore:
00268 print "Warning, %s does not have Xing information" \
00269 % key.__str__()
00270 continue
00271 else:
00272 raise RuntimeError, "%s does not have Xing information. Use '--ignoreNoLumiEvents' option to ignore these events and continue." \
00273 % key.__str__()
00274 if options.weights:
00275 weight = float (pieces[weightIndex])
00276 else:
00277 weight = 1
00278 eventsDict.setdefault( key, []).append( (event, weight) )
00279 totalWeight += weight
00280 events.close()
00281 return eventsDict, totalWeight
00282
00283
00284 def makeEDFplot (lumiCont, eventsDict, totalWeight, outputFile, options):
00285
00286 xVals = [0]
00287 yVals = [0]
00288 expectedVals = [0]
00289 predVals = [0]
00290 weight = 0
00291 expectedChunks = []
00292
00293
00294
00295 if 'time' == options.edfMode:
00296
00297 if lumiCont.minRun or lumiCont.minIntLum:
00298 xVals = []
00299 yVals = []
00300 expectedVals = []
00301 predVals = []
00302
00303 for key, eventList in sorted( eventsDict.iteritems() ):
00304 usePoints = True
00305
00306 if lumiCont.minRun and lumiCont.minRun > key[0] or \
00307 lumiCont.maxRun and lumiCont.maxRun < key[0]:
00308 usePoints = False
00309 for event in eventList:
00310 weight += event[1]
00311 if not usePoints:
00312 continue
00313 factor = weight / totalWeight
00314 try:
00315 intLum = lumiCont[key].totalRecorded
00316 except:
00317 raise RuntimeError, "key %s not found in lumi information" \
00318 % key.__str__()
00319 if lumiCont.minIntLum and lumiCont.minIntLum > intLum or \
00320 lumiCont.maxIntLum and lumiCont.maxIntLum < intLum:
00321 continue
00322 lumFrac = intLum / lumiCont.totalRecLum
00323 xVals.append( lumiCont[key].totalRecorded)
00324 yVals.append (factor)
00325 expectedVals.append (lumFrac)
00326 predVals.append (lumFrac * options.pred)
00327
00328 if not lumiCont.maxRun and not lumiCont.maxIntLum:
00329 xVals.append (lumiCont.totalRecLum)
00330 yVals.append (1)
00331 expectedVals.append (1)
00332 predVals.append (options.pred)
00333
00334
00335
00336 if options.resetExpected:
00337 slope = (yVals[-1] - yVals[0]) / (xVals[-1] - xVals[0])
00338 print "slope", slope
00339 for index, old in enumerate (expectedVals):
00340 expectedVals[index] = yVals[0] + \
00341 slope * (xVals[index] - xVals[0])
00342
00343
00344
00345 if options.breakExpectedIntLum:
00346 breakExpectedIntLum = []
00347 for chunk in options.breakExpectedIntLum:
00348 pieces = sepRE.split (chunk)
00349 try:
00350 for piece in pieces:
00351 breakExpectedIntLum.append( float(piece) )
00352 except:
00353 raise RuntimeError, "'%s' from '%s' is not a valid float" \
00354 % (piece, chunk)
00355 breakExpectedIntLum.sort()
00356 boundaries = []
00357 breakIndex = 0
00358 done = False
00359 for index, xPos in enumerate (xVals):
00360 if xPos > breakExpectedIntLum[breakIndex]:
00361 boundaries.append (index)
00362 while breakIndex < len (breakExpectedIntLum):
00363 breakIndex += 1
00364 if breakIndex >= len (breakExpectedIntLum):
00365 done = True
00366 break
00367
00368
00369
00370 if xPos <= breakExpectedIntLum[breakIndex]:
00371 break
00372 if done:
00373 break
00374
00375 if not boundaries:
00376 raise RuntimeError, "No values of 'breakExpectedIntLum' are in current range."
00377
00378 if boundaries[0]:
00379 boundaries.insert (0, 0)
00380
00381
00382 if boundaries[-1] != len (xVals) - 1:
00383 boundaries.append( len (xVals) - 1 )
00384 rangeList = zip (boundaries, boundaries[1:])
00385 for thisRange in rangeList:
00386 upper = thisRange[1]
00387 lower = thisRange[0]
00388 slope = (yVals[upper] - yVals[lower]) / \
00389 (xVals[upper] - xVals[lower])
00390 print "slope", slope
00391
00392 pairList = []
00393 for index in range (lower, upper + 1):
00394 newExpected = yVals[lower] + \
00395 slope * (xVals[index] - xVals[lower])
00396 pairList.append( (xVals[index], newExpected) )
00397 expectedVals[index] = newExpected
00398 expectedChunks.append (pairList)
00399
00400
00401
00402 elif 'instLum' == options.edfMode or 'instIntLum' == options.edfMode:
00403 eventTupList = []
00404 if not lumiCont.xingInfo:
00405 raise RuntimeError, "Luminosity Xing information missing."
00406 for key, eventList in sorted( eventsDict.iteritems() ):
00407 try:
00408 lumi = lumiCont[key]
00409 instLum = lumi.aveInstLum
00410 fracAXIL = lumi.fracAXILrecorded
00411 totalAXIL = lumi.totalAXILrecorded
00412 except:
00413 raise RuntimeError, "key %s not found in lumi information" \
00414 % key.__str__()
00415 for event in eventList:
00416 eventTupList.append( (instLum, fracAXIL, totalAXIL, key,
00417 event[0], event[1], ) )
00418 eventTupList.sort()
00419 for eventTup in eventTupList:
00420 weight += eventTup[5]
00421 factor = weight / totalWeight
00422 if 'instLum' == options.edfMode:
00423 xVals.append (eventTup[0])
00424 else:
00425 xVals.append (eventTup[2])
00426 yVals.append (factor)
00427 expectedVals.append (eventTup[1])
00428 predVals.append (eventTup[1] * options.pred)
00429 else:
00430 raise RuntimeError, "It looks like Charles screwed up if you are seeing this."
00431
00432 size = len (xVals)
00433 step = int (math.sqrt(size) / 2 + 1)
00434 if options.printValues:
00435 for index in range (size):
00436 print "%8f %8f %8f" % (xVals[index], yVals[index], expectedVals[index]),
00437 if index > step:
00438 denom = xVals[index] - xVals[index - step]
00439 numer = yVals[index] - yVals[index - step]
00440 if denom:
00441 print " %8f" % (numer / denom),
00442 if 0 == index % step:
00443 print " **",
00444
00445 print
00446 print
00447
00448 xArray = array.array ('d', xVals)
00449 yArray = array.array ('d', yVals)
00450 expected = array.array ('d', expectedVals)
00451 graph = ROOT.TGraph( size, xArray, yArray)
00452 graph.SetTitle (options.title)
00453 graph.SetMarkerStyle (20)
00454 expectedGraph = ROOT.TGraph( size, xArray, expected)
00455 expectedGraph.SetLineColor (ROOT.kRed)
00456 expectedGraph.SetLineWidth (3)
00457 if options.noDataPoints:
00458 expectedGraph.SetLineStyle (2)
00459
00460
00461 if options.weights:
00462 print "average weight per event:", weight / ( size - 1)
00463 maxDistance = ROOT.TMath.KolmogorovTest (size, yArray,
00464 size, expected,
00465 "M")
00466 prob = ROOT.TMath.KolmogorovProb( maxDistance * math.sqrt( size ) )
00467
00468
00469 ROOT.gROOT.SetStyle('Plain')
00470 ROOT.gROOT.SetBatch()
00471 c1 = ROOT.TCanvas()
00472 graph.GetXaxis().SetRangeUser (min (xVals), max (xVals))
00473 minValue = min (min(yVals), min(expected))
00474 if options.pred:
00475 minValue = min (minValue, min (predVals))
00476 graph.GetYaxis().SetRangeUser (minValue,
00477 max (max(yVals), max(expected), max(predVals)))
00478 graph.SetLineWidth (3)
00479 if options.noDataPoints:
00480 graph.Draw ("AL")
00481 else:
00482 graph.Draw ("ALP")
00483 if 'instLum' == options.edfMode:
00484 graph.GetXaxis().SetTitle ("Average Xing Inst. Luminosity (1/ub/s)")
00485 graph.GetXaxis().SetRangeUser (0., lumiCont.max('aveInstLum'))
00486 else:
00487 if 'instIntLum' == options.edfMode:
00488 graph.GetXaxis().SetTitle ("Integrated Luminosity - Inst. Lum. Ordered (1/%s)" \
00489 % lumiCont.invunits)
00490 else:
00491 graph.GetXaxis().SetTitle ("Integrated Luminosity (1/%s)" \
00492 % lumiCont.invunits)
00493 graph.GetYaxis().SetTitle ("Fraction of Events Seen")
00494 expectedGraphs = []
00495 if expectedChunks:
00496 for index, chunk in enumerate (expectedChunks):
00497 expectedXarray = array.array ('d', [item[0] for item in chunk])
00498 expectedYarray = array.array ('d', [item[1] for item in chunk])
00499 expectedGraph = ROOT.TGraph( len(chunk),
00500 expectedXarray,
00501 expectedYarray )
00502 expectedGraph.SetLineWidth (3)
00503 if options.noDataPoints:
00504 expectedGraph.SetLineStyle (2)
00505 if index % 2:
00506 expectedGraph.SetLineColor (ROOT.kBlue)
00507 else:
00508 expectedGraph.SetLineColor (ROOT.kRed)
00509 expectedGraph.Draw("L")
00510 expectedGraphs.append (expectedGraph)
00511 exptectedGraph = expectedGraphs[0]
00512 else:
00513 expectedGraph.Draw ("L")
00514 green = 0
00515 if options.pred:
00516 predArray = array.array ('d', predVals)
00517 green = ROOT.TGraph (size, xArray, predArray)
00518 green.SetLineWidth (3)
00519 green.SetLineColor (8)
00520 green.Draw ('l')
00521 legend = ROOT.TLegend(0.15, 0.65, 0.50, 0.85)
00522 legend.SetFillStyle (0)
00523 legend.SetLineColor(ROOT.kWhite)
00524 observed = 'Observed'
00525 if options.weights:
00526 observed += ' (weighted)'
00527 legend.AddEntry(graph, observed,"PL")
00528 if options.resetExpected:
00529 legend.AddEntry(expectedGraph, "Expected from partial yield","L")
00530 else:
00531 legend.AddEntry(expectedGraph, "Expected from total yield","L")
00532 if options.pred:
00533 legend.AddEntry(green, options.predLabel,"L")
00534 legend.AddEntry("","D_{stat}=%.3f, N=%d" % (maxDistance, size),"")
00535 legend.AddEntry("","P_{KS}=%.3f" % prob,"")
00536 legend.Draw()
00537
00538
00539 c1.Print (outputFile)
00540
00541
00542
00543
00544
00545
00546
00547
00548
00549
00550 if __name__ == '__main__':
00551
00552
00553
00554 allowedEDF = ['time', 'instLum', 'instIntLum']
00555 parser = optparse.OptionParser ("Usage: %prog [options] lumi.csv events.txt output.png", description='Script for generating EDF curves. See https://twiki.cern.ch/twiki/bin/viewauth/CMS/SWGuideGenerateEDF for more details.')
00556 plotGroup = optparse.OptionGroup (parser, "Plot Options")
00557 rangeGroup = optparse.OptionGroup (parser, "Range Options")
00558 inputGroup = optparse.OptionGroup (parser, "Input Options")
00559 modeGroup = optparse.OptionGroup (parser, "Mode Options")
00560 plotGroup.add_option ('--title', dest='title', type='string',
00561 default = 'Empirical Distribution Function',
00562 help = 'title of plot (default %default)')
00563 plotGroup.add_option ('--predicted', dest='pred', type='float',
00564 default = 0,
00565 help = 'factor by which predicted curve is greater than observed')
00566 plotGroup.add_option ('--predLabel', dest='predLabel', type='string',
00567 default = 'Predicted',
00568 help = 'label of predicted in legend')
00569 plotGroup.add_option ('--noDataPoints', dest='noDataPoints',
00570 action='store_true',
00571 help="Draw lines but no points for data")
00572 rangeGroup.add_option ('--minRun', dest='minRun', type='int', default=0,
00573 help='Minimum run number to consider')
00574 rangeGroup.add_option ('--maxRun', dest='maxRun', type='int', default=0,
00575 help='Maximum run number to consider')
00576 rangeGroup.add_option ('--minIntLum', dest='minIntLum', type='float', default=0,
00577 help='Minimum integrated luminosity to consider')
00578 rangeGroup.add_option ('--maxIntLum', dest='maxIntLum', type='float', default=0,
00579 help='Maximum integrated luminosity to consider')
00580 rangeGroup.add_option ('--resetExpected', dest='resetExpected',
00581 action='store_true',
00582 help='Reset expected from total yield to highest point considered')
00583 rangeGroup.add_option ('--breakExpectedIntLum', dest='breakExpectedIntLum',
00584 type='string', action='append', default=[],
00585 help='Break expected curve into pieces at integrated luminosity boundaries')
00586 inputGroup.add_option ('--ignoreNoLumiEvents', dest='ignore',
00587 action='store_true',
00588 help = 'Ignore (with a warning) events that do not have a lumi section')
00589 inputGroup.add_option ('--noWarnings', dest='noWarnings',
00590 action='store_true',
00591 help = 'Do not print warnings about missing luminosity information')
00592 inputGroup.add_option ('--runEventLumi', dest='relOrder',
00593 action='store_true',
00594 help = 'Parse event list assuming Run, Event #, Lumi# order')
00595 inputGroup.add_option ('--weights', dest='weights', action='store_true',
00596 help = 'Read fourth column as a weight')
00597 modeGroup.add_option ('--print', dest='printValues', action='store_true',
00598 help = 'Print X and Y values of EDF plot')
00599 modeGroup.add_option ('--runsWithLumis', dest='runsWithLumis',
00600 type='string',action='append', default=[],
00601 help='Print out run and lumi sections corresponding to integrated luminosities provided and then exits')
00602 modeGroup.add_option ('--edfMode', dest='edfMode', type='string',
00603 default='time',
00604 help="EDF Mode %s (default '%%default')" % allowedEDF)
00605 parser.add_option_group (plotGroup)
00606 parser.add_option_group (rangeGroup)
00607 parser.add_option_group (inputGroup)
00608 parser.add_option_group (modeGroup)
00609 (options, args) = parser.parse_args()
00610
00611 if options.edfMode not in allowedEDF:
00612 raise RuntimeError, "edfMode (currently '%s') must be one of %s" \
00613 % (options.edfMode, allowedEDF)
00614
00615 if len (args) != 3 and not (options.runsWithLumis and len(args) >= 1):
00616 raise RuntimeError, "Must provide lumi.csv, events.txt, and output.png"
00617
00618
00619
00620
00621
00622 cont = LumiInfoCont (args[0], **options.__dict__)
00623 cont.minRun = options.minRun
00624 cont.maxRun = options.maxRun
00625 cont.minIntLum = options.minIntLum
00626 cont.maxIntLum = options.maxIntLum
00627
00628
00629
00630
00631
00632 if options.runsWithLumis:
00633 recLumis = []
00634 for line in options.runsWithLumis:
00635 pieces = sepRE.split (line)
00636 for piece in pieces:
00637 try:
00638 recLumValue = float (piece)
00639 except:
00640 raise RuntimeError, "'%s' in '%s' is not a float" % \
00641 (piece, line)
00642 if recLumValue <= 0:
00643 raise RuntimeError, "You must provide positive values for -runsWithLumis ('%f' given)" % recLumValue
00644 recLumis.append (recLumValue)
00645 if not recLumis:
00646 raise RuntimeError, "What did Charles do now?"
00647 recLumis.sort()
00648 recLumIndex = 0
00649 recLumValue = recLumis [recLumIndex]
00650 prevRecLumi = 0.
00651 done = False
00652 for key, lumi in cont.iteritems():
00653 if prevRecLumi >= recLumValue and recLumValue < lumi.totalRecorded:
00654
00655 print "%s contains total recorded lumi %f" % \
00656 (key.__str__(), recLumValue)
00657 while True:
00658 recLumIndex += 1
00659 if recLumIndex == len (recLumis):
00660 done = True
00661 break
00662 recLumValue = recLumis [recLumIndex]
00663 if prevRecLumi >= recLumValue and recLumValue < lumi.totalRecorded:
00664
00665 print "%s contains total recorded lumi %f" % \
00666 (key.__str__(), recLumValue)
00667 else:
00668 break
00669 if done:
00670 break
00671 prevRecLumi = lumi.totalRecorded
00672 if recLumIndex < len (recLumis):
00673 print "Theses lumis not found: %s" % recLumis[recLumIndex:]
00674 sys.exit()
00675
00676
00677
00678
00679 if options.edfMode != 'time' and not cont.xingInfo:
00680 raise RuntimeError, "'%s' does not have Xing info" % args[0]
00681 eventsDict, totalWeight = loadEvents (args[1], cont, options)
00682 makeEDFplot (cont, eventsDict, totalWeight, args[2], options)