CMS 3D CMS Logo

genericValidation.py
Go to the documentation of this file.
1 from __future__ import print_function
2 from __future__ import absolute_import
3 from builtins import range
4 from abc import ABCMeta, abstractmethod, abstractproperty
5 import os
6 import re
7 import json
8 from . import globalDictionaries
9 from . import configTemplates
10 from .dataset import Dataset
11 from .helperFunctions import replaceByMap, addIndex, getCommandOutput2, boolfromstring, pythonboolstring
12 from .TkAlExceptions import AllInOneError
13 
14 class ValidationMetaClass(ABCMeta):
15  sets = ["mandatories", "optionals", "needpackages"]
16  dicts = ["defaults"]
17  def __new__(cls, clsname, bases, dct):
18  for setname in cls.sets:
19  if setname not in dct: dct[setname] = set()
20  dct[setname] = set.union(dct[setname], *(getattr(base, setname) for base in bases if hasattr(base, setname)))
21 
22  for dictname in cls.dicts:
23  if dictname not in dct: dct[dictname] = {}
24  for base in bases:
25  if not hasattr(base, dictname): continue
26  newdict = getattr(base, dictname)
27  for key in set(newdict) & set(dct[dictname]):
28  if newdict[key] != dct[dictname][key]:
29  raise ValueError("Inconsistent values of defaults[{}]: {}, {}".format(key, newdict[key], dct[dictname][key]))
30  dct[dictname].update(newdict)
31 
32  for setname in cls.sets: #e.g. removemandatories, used in preexistingvalidation
33  #use with caution
34  if "remove"+setname not in dct: dct["remove"+setname] = set()
35  dct["remove"+setname] = set.union(dct["remove"+setname], *(getattr(base, "remove"+setname) for base in bases if hasattr(base, "remove"+setname)))
36 
37  dct[setname] -= dct["remove"+setname]
38 
39  return super(ValidationMetaClass, cls).__new__(cls, clsname, bases, dct)
40 
41 class GenericValidation(object, metaclass=ValidationMetaClass):
42  defaultReferenceName = "DEFAULT"
43  mandatories = set()
44  defaults = {
45  "cmssw": os.environ['CMSSW_BASE'],
46  "parallelJobs": "1",
47  "jobid": "",
48  "needsproxy": "false",
49  }
50  needpackages = {"Alignment/OfflineValidation"}
51  optionals = {"jobmode"}
52 
53  def __init__(self, valName, alignment, config):
54  import random
55  self.name = valName
56  self.alignmentToValidate = alignment
57  self.general = config.getGeneral()
58  self.randomWorkdirPart = "%0i"%random.randint(1,10e9)
59  self.configFiles = []
60  self.config = config
61  self.jobid = ""
62 
63  theUpdate = config.getResultingSection(self.valType+":"+self.name,
64  defaultDict = self.defaults,
65  demandPars = self.mandatories)
66  self.general.update(theUpdate)
67  self.jobmode = self.general["jobmode"]
68  self.NJobs = int(self.general["parallelJobs"])
69  self.needsproxy = boolfromstring(self.general["needsproxy"], "needsproxy")
70 
71  # limit maximum number of parallel jobs to 40
72  # (each output file is approximately 20MB)
73  maximumNumberJobs = 40
74  if self.NJobs > maximumNumberJobs:
75  msg = ("Maximum allowed number of parallel jobs "
76  +str(maximumNumberJobs)+" exceeded!!!")
77  raise AllInOneError(msg)
78  if self.NJobs > 1 and not isinstance(self, ParallelValidation):
79  raise AllInOneError("Parallel jobs not implemented for {}!\n"
80  "Please set parallelJobs = 1.".format(type(self).__name__))
81 
82  self.jobid = self.general["jobid"]
83  if self.jobid:
84  try: #make sure it's actually a valid jobid
85  output = getCommandOutput2("bjobs %(jobid)s 2>&1"%self.general)
86  if "is not found" in output: raise RuntimeError
87  except RuntimeError:
88  raise AllInOneError("%s is not a valid jobid.\nMaybe it finished already?"%self.jobid)
89 
90  self.cmssw = self.general["cmssw"]
91  badcharacters = r"\'"
92  for character in badcharacters:
93  if character in self.cmssw:
94  raise AllInOneError("The bad characters " + badcharacters + " are not allowed in the cmssw\n"
95  "path name. If you really have it in such a ridiculously named location,\n"
96  "try making a symbolic link somewhere with a decent name.")
97  try:
98  os.listdir(self.cmssw)
99  except OSError:
100  raise AllInOneError("Your cmssw release " + self.cmssw + ' does not exist')
101 
102  if self.cmssw == os.environ["CMSSW_BASE"]:
103  self.scramarch = os.environ["SCRAM_ARCH"]
104  self.cmsswreleasebase = os.environ["CMSSW_RELEASE_BASE"]
105  else:
106  command = ("cd '" + self.cmssw + "' && eval `scramv1 ru -sh 2> /dev/null`"
107  ' && echo "$CMSSW_BASE\n$SCRAM_ARCH\n$CMSSW_RELEASE_BASE"')
108  commandoutput = getCommandOutput2(command).split('\n')
109  self.cmssw = commandoutput[0]
110  self.scramarch = commandoutput[1]
111  self.cmsswreleasebase = commandoutput[2]
112 
113  self.packages = {}
114  for package in self.needpackages:
115  for placetolook in self.cmssw, self.cmsswreleasebase:
116  pkgpath = os.path.join(placetolook, "src", package)
117  if os.path.exists(pkgpath):
118  self.packages[package] = pkgpath
119  break
120  else:
121  raise AllInOneError("Package {} does not exist in {} or {}!".format(package, self.cmssw, self.cmsswreleasebase))
122 
123  self.AutoAlternates = True
124  if config.has_option("alternateTemplates","AutoAlternates"):
125  try:
126  self.AutoAlternates = json.loads(config.get("alternateTemplates","AutoAlternates").lower())
127  except ValueError:
128  raise AllInOneError("AutoAlternates needs to be true or false, not %s" % config.get("alternateTemplates","AutoAlternates"))
129 
130  knownOpts = set(self.defaults.keys())|self.mandatories|self.optionals
131  ignoreOpts = []
132  config.checkInput(self.valType+":"+self.name,
133  knownSimpleOptions = knownOpts,
134  ignoreOptions = ignoreOpts)
135 
136  def getRepMap(self, alignment = None):
137  from .plottingOptions import PlottingOptions
138  if alignment == None:
139  alignment = self.alignmentToValidate
140  try:
141  result = PlottingOptions(self.config, self.valType)
142  except KeyError:
143  result = {}
144  result.update(alignment.getRepMap())
145  result.update(self.general)
146  result.update({
147  "workdir": os.path.join(self.general["workdir"],
148  self.randomWorkdirPart),
149  "datadir": self.general["datadir"],
150  "logdir": self.general["logdir"],
151  "CommandLineTemplate": ("#run configfile and post-proccess it\n"
152  "cmsRun %(cfgFile)s\n"
153  "%(postProcess)s "),
154  "CMSSW_BASE": self.cmssw,
155  "SCRAM_ARCH": self.scramarch,
156  "CMSSW_RELEASE_BASE": self.cmsswreleasebase,
157  "alignmentName": alignment.name,
158  "condLoad": alignment.getConditions(),
159  "LoadGlobalTagTemplate": configTemplates.loadGlobalTagTemplate,
160  })
161  result.update(self.packages)
162  return result
163 
164  @abstractproperty
165  def filesToCompare(self):
166  pass
167 
168  def getCompareStrings( self, requestId = None, plain = False ):
169  result = {}
170  repMap = self.getRepMap().copy()
171  for validationId in self.filesToCompare:
172  repMap["file"] = self.filesToCompare[ validationId ]
173  if repMap["file"].startswith( "/castor/" ):
174  repMap["file"] = "rfio:%(file)s"%repMap
175  elif repMap["file"].startswith( "/store/" ):
176  repMap["file"] = "root://eoscms.cern.ch//eos/cms%(file)s"%repMap
177  if plain:
178  result[validationId]=repMap["file"]
179  else:
180  result[validationId]= "%(file)s=%(title)s|%(color)s|%(style)s"%repMap
181  if requestId == None:
182  return result
183  else:
184  if not "." in requestId:
185  requestId += ".%s"%self.defaultReferenceName
186  if not requestId.split(".")[-1] in result:
187  msg = ("could not find %s in reference Objects!"
188  %requestId.split(".")[-1])
189  raise AllInOneError(msg)
190  return result[ requestId.split(".")[-1] ]
191 
192  def createFiles(self, fileContents, path, repMap = None, repMaps = None):
193  """repMap: single map for all files
194  repMaps: a dict, with the filenames as the keys"""
195  if repMap is not None and repMaps is not None:
196  raise AllInOneError("createFiles can only take repMap or repMaps (or neither), not both")
197  result = []
198  for fileName in fileContents:
199  filePath = os.path.join(path, fileName)
200  result.append(filePath)
201 
202  for (i, filePathi) in enumerate(addIndex(filePath, self.NJobs)):
203  theFile = open( filePathi, "w" )
204  fileContentsi = fileContents[ fileName ]
205  if repMaps is not None:
206  repMap = repMaps[fileName]
207  if repMap is not None:
208  repMap.update({"nIndex": str(i)})
209  fileContentsi = replaceByMap(fileContentsi, repMap)
210  theFile.write( fileContentsi )
211  theFile.close()
212 
213  return result
214 
215  def createConfiguration(self, fileContents, path, schedule = None, repMap = None, repMaps = None):
216  self.configFiles = self.createFiles(fileContents,
217  path, repMap = repMap, repMaps = repMaps)
218  if not schedule == None:
219  schedule = [os.path.join( path, cfgName) for cfgName in schedule]
220  for cfgName in schedule:
221  if not cfgName in self.configFiles:
222  msg = ("scheduled %s missing in generated configfiles: %s"
223  %(cfgName, self.configFiles))
224  raise AllInOneError(msg)
225  for cfgName in self.configFiles:
226  if not cfgName in schedule:
227  msg = ("generated configuration %s not scheduled: %s"
228  %(cfgName, schedule))
229  raise AllInOneError(msg)
230  self.configFiles = schedule
231  return self.configFiles
232 
233  def createScript(self, fileContents, path, downloadFiles=[], repMap = None, repMaps = None):
234  self.scriptFiles = self.createFiles(fileContents,
235  path, repMap = repMap, repMaps = repMaps)
236  for script in self.scriptFiles:
237  for scriptwithindex in addIndex(script, self.NJobs):
238  os.chmod(scriptwithindex,0o755)
239  return self.scriptFiles
240 
241  def createCrabCfg(self, fileContents, path ):
242  if self.NJobs > 1:
243  msg = ("jobmode 'crab' not supported for parallel validation."
244  " Please set parallelJobs = 1.")
245  raise AllInOneError(msg)
246  self.crabConfigFiles = self.createFiles(fileContents, path)
247  return self.crabConfigFiles
248 
249 
251  """
252  Subclass of `GenericValidation` which is the base for validations using
253  datasets.
254  """
255  needParentFiles = False
256  mandatories = {"dataset", "maxevents"}
257  defaults = {
258  "runRange": "",
259  "firstRun": "",
260  "lastRun": "",
261  "begin": "",
262  "end": "",
263  "JSON": "",
264  "dasinstance": "prod/global",
265  "ttrhbuilder":"WithAngleAndTemplate",
266  "usepixelqualityflag": "True",
267  }
268  optionals = {"magneticfield"}
269 
270  def __init__(self, valName, alignment, config):
271  """
272  This method adds additional items to the `self.general` dictionary
273  which are only needed for validations using datasets.
274 
275  Arguments:
276  - `valName`: String which identifies individual validation instances
277  - `alignment`: `Alignment` instance to validate
278  - `config`: `BetterConfigParser` instance which includes the
279  configuration of the validations
280  """
281 
282  super(GenericValidationData, self).__init__(valName, alignment, config)
283 
284  # if maxevents is not specified, cannot calculate number of events for
285  # each parallel job, and therefore running only a single job
286  if int( self.general["maxevents"] ) < 0 and self.NJobs > 1:
287  msg = ("Maximum number of events (maxevents) not specified: "
288  "cannot use parallel jobs.")
289  raise AllInOneError(msg)
290  if int( self.general["maxevents"] ) / self.NJobs != float( self.general["maxevents"] ) / self.NJobs:
291  msg = ("maxevents has to be divisible by parallelJobs")
292  raise AllInOneError(msg)
293 
294  tryPredefinedFirst = (not self.jobmode.split( ',' )[0] == "crab" and self.general["JSON"] == ""
295  and self.general["firstRun"] == "" and self.general["lastRun"] == ""
296  and self.general["begin"] == "" and self.general["end"] == "")
297 
298  if self.general["dataset"] not in globalDictionaries.usedDatasets:
299  globalDictionaries.usedDatasets[self.general["dataset"]] = {}
300 
301  if self.cmssw not in globalDictionaries.usedDatasets[self.general["dataset"]]:
302  if globalDictionaries.usedDatasets[self.general["dataset"]] != {}:
303  print(("Warning: you use the same dataset '%s' in multiple cmssw releases.\n"
304  "This is allowed, but make sure it's not a mistake") % self.general["dataset"])
305  globalDictionaries.usedDatasets[self.general["dataset"]][self.cmssw] = {False: None, True: None}
306 
307  Bfield = self.general.get("magneticfield", None)
308  if globalDictionaries.usedDatasets[self.general["dataset"]][self.cmssw][tryPredefinedFirst] is None:
309  dataset = Dataset(
310  self.general["dataset"], tryPredefinedFirst = tryPredefinedFirst,
311  cmssw = self.cmssw, cmsswrelease = self.cmsswreleasebase, magneticfield = Bfield,
312  dasinstance = self.general["dasinstance"])
313  globalDictionaries.usedDatasets[self.general["dataset"]][self.cmssw][tryPredefinedFirst] = dataset
314  if tryPredefinedFirst and not dataset.predefined(): #No point finding the data twice in that case
315  globalDictionaries.usedDatasets[self.general["dataset"]][self.cmssw][False] = dataset
316 
317  self.dataset = globalDictionaries.usedDatasets[self.general["dataset"]][self.cmssw][tryPredefinedFirst]
318  self.general["magneticField"] = self.dataset.magneticField()
319  self.general["defaultMagneticField"] = "MagneticField"
320  if self.general["magneticField"] == "unknown":
321  print("Could not get the magnetic field for this dataset.")
322  print("Using the default: ", self.general["defaultMagneticField"])
323  self.general["magneticField"] = '.oO[defaultMagneticField]Oo.'
324 
325  if not self.jobmode.split( ',' )[0] == "crab":
326  try:
327  self.general["datasetDefinition"] = self.dataset.datasetSnippet(
328  jsonPath = self.general["JSON"],
329  firstRun = self.general["firstRun"],
330  lastRun = self.general["lastRun"],
331  begin = self.general["begin"],
332  end = self.general["end"],
333  parent = self.needParentFiles )
334  except AllInOneError as e:
335  msg = "In section [%s:%s]: "%(self.valType, self.name)
336  msg += str(e)
337  raise AllInOneError(msg)
338  else:
339  if self.dataset.predefined():
340  msg = ("For jobmode 'crab' you cannot use predefined datasets "
341  "(in your case: '%s')."%( self.dataset.name() ))
342  raise AllInOneError( msg )
343  try:
344  theUpdate = config.getResultingSection(self.valType+":"+self.name,
345  demandPars = ["parallelJobs"])
346  except AllInOneError as e:
347  msg = str(e)[:-1]+" when using 'jobmode: crab'."
348  raise AllInOneError(msg)
349  self.general.update(theUpdate)
350  if self.general["begin"] or self.general["end"]:
351  ( self.general["begin"],
352  self.general["end"],
353  self.general["firstRun"],
354  self.general["lastRun"] ) = self.dataset.convertTimeToRun(
355  firstRun = self.general["firstRun"],
356  lastRun = self.general["lastRun"],
357  begin = self.general["begin"],
358  end = self.general["end"],
359  shortTuple = False)
360  if self.general["begin"] == None:
361  self.general["begin"] = ""
362  if self.general["end"] == None:
363  self.general["end"] = ""
364  self.general["firstRun"] = str( self.general["firstRun"] )
365  self.general["lastRun"] = str( self.general["lastRun"] )
366  if ( not self.general["firstRun"] ) and \
367  ( self.general["end"] or self.general["lastRun"] ):
368  self.general["firstRun"] = str(
369  self.dataset.runList()[0]["run_number"])
370  if ( not self.general["lastRun"] ) and \
371  ( self.general["begin"] or self.general["firstRun"] ):
372  self.general["lastRun"] = str(
373  self.dataset.runList()[-1]["run_number"])
374  if self.general["firstRun"] and self.general["lastRun"]:
375  if int(self.general["firstRun"]) > int(self.general["lastRun"]):
376  msg = ( "The lower time/runrange limit ('begin'/'firstRun') "
377  "chosen is greater than the upper time/runrange limit "
378  "('end'/'lastRun').")
379  raise AllInOneError( msg )
380  self.general["runRange"] = (self.general["firstRun"]
381  + '-' + self.general["lastRun"])
382  try:
383  self.general["datasetDefinition"] = self.dataset.datasetSnippet(
384  jsonPath = self.general["JSON"],
385  firstRun = self.general["firstRun"],
386  lastRun = self.general["lastRun"],
387  begin = self.general["begin"],
388  end = self.general["end"],
389  crab = True )
390  except AllInOneError as e:
391  msg = "In section [%s:%s]: "%(self.valType, self.name)
392  msg += str( e )
393  raise AllInOneError( msg )
394 
395  self.general["usepixelqualityflag"] = pythonboolstring(self.general["usepixelqualityflag"], "usepixelqualityflag")
396 
397  def getRepMap(self, alignment = None):
398  result = super(GenericValidationData, self).getRepMap(alignment)
399  outputfile = os.path.expandvars(replaceByMap(
400  "%s_%s_.oO[name]Oo..root" % (self.outputBaseName, self.name)
401  , result))
402  resultfile = os.path.expandvars(replaceByMap(("/store/group/alca_trackeralign/AlignmentValidation/.oO[eosdir]Oo./" +
403  "%s_%s_.oO[name]Oo..root" % (self.resultBaseName, self.name))
404  , result))
405  result.update({
406  "resultFile": ".oO[resultFiles[.oO[nIndex]Oo.]]Oo.",
407  "resultFiles": addIndex(resultfile, self.NJobs),
408  "finalResultFile": resultfile,
409  "outputFile": ".oO[outputFiles[.oO[nIndex]Oo.]]Oo.",
410  "outputFiles": addIndex(outputfile, self.NJobs),
411  "finalOutputFile": outputfile,
412  "ProcessName": self.ProcessName,
413  "Bookkeeping": self.Bookkeeping,
414  "LoadBasicModules": self.LoadBasicModules,
415  "TrackSelectionRefitting": self.TrackSelectionRefitting,
416  "ValidationConfig": self.ValidationTemplate,
417  "FileOutputTemplate": self.FileOutputTemplate,
418  "DefinePath": self.DefinePath,
419  })
420  return result
421 
422  @property
423  def cfgName(self):
424  return "%s.%s.%s_cfg.py"%( self.configBaseName, self.name,
425  self.alignmentToValidate.name )
426  @abstractproperty
427  def ProcessName(self):
428  pass
429 
430  @property
431  def cfgTemplate(self):
432  return configTemplates.cfgTemplate
433 
434  @abstractproperty
436  pass
437 
438  @property
439  def filesToCompare(self):
440  return {self.defaultReferenceName: self.getRepMap()["finalResultFile"]}
441 
442  def createConfiguration(self, path ):
443  repMap = self.getRepMap()
444  cfgs = {self.cfgName: self.cfgTemplate}
445  super(GenericValidationData, self).createConfiguration(cfgs, path, repMap=repMap)
446 
447  def createScript(self, path, template = configTemplates.scriptTemplate, downloadFiles=[], repMap = None, repMaps = None):
448  scriptName = "%s.%s.%s.sh"%(self.scriptBaseName, self.name,
449  self.alignmentToValidate.name )
450  if repMap is None and repMaps is None:
451  repMap = self.getRepMap()
452  repMap["CommandLine"]=""
453  for cfg in self.configFiles:
454  repMap["CommandLine"]+= repMap["CommandLineTemplate"]%{"cfgFile":addIndex(cfg, self.NJobs, ".oO[nIndex]Oo."),
455  "postProcess":""
456  }
457  scripts = {scriptName: template}
458  return super(GenericValidationData, self).createScript(scripts, path, downloadFiles = downloadFiles,
459  repMap = repMap, repMaps = repMaps)
460 
461  def createCrabCfg(self, path, crabCfgBaseName):
462  """
463  Method which creates a `crab.cfg` for a validation on datasets.
464 
465  Arguments:
466  - `path`: Path at which the file will be stored.
467  - `crabCfgBaseName`: String which depends on the actual type of
468  validation calling this method.
469  """
470  crabCfgName = "crab.%s.%s.%s.cfg"%( crabCfgBaseName, self.name,
471  self.alignmentToValidate.name )
472  repMap = self.getRepMap()
473  repMap["script"] = "dummy_script.sh"
474  # repMap["crabOutputDir"] = os.path.basename( path )
475  repMap["crabWorkingDir"] = crabCfgName.split( '.cfg' )[0]
476  self.crabWorkingDir = repMap["crabWorkingDir"]
477  repMap["numberOfJobs"] = self.general["parallelJobs"]
478  repMap["cfgFile"] = self.configFiles[0]
479  repMap["queue"] = self.jobmode.split( ',' )[1].split( '-q' )[1]
480  if self.dataset.dataType() == "mc":
481  repMap["McOrData"] = "events = .oO[nEvents]Oo."
482  elif self.dataset.dataType() == "data":
483  repMap["McOrData"] = "lumis = -1"
484  if self.jobmode.split( ',' )[0] == "crab":
485  print ("For jobmode 'crab' the parameter 'maxevents' will be "
486  "ignored and all events will be processed.")
487  else:
488  raise AllInOneError("Unknown data type! Can't run in crab mode")
489  crabCfg = {crabCfgName: replaceByMap( configTemplates.crabCfgTemplate,
490  repMap ) }
491  return super(GenericValidationData, self).createCrabCfg( crabCfg, path )
492 
493  @property
494  def Bookkeeping(self):
495  return configTemplates.Bookkeeping
496  @property
497  def LoadBasicModules(self):
498  return configTemplates.LoadBasicModules
499  @abstractproperty
501  pass
502  @property
504  return configTemplates.FileOutputTemplate
505  @abstractproperty
506  def DefinePath(self):
507  pass
508 
509 class GenericValidationData_CTSR(GenericValidationData):
510  #common track selection and refitting
511  defaults = {
512  "momentumconstraint": "None",
513  "openmasswindow": "False",
514  "cosmicsdecomode": "True",
515  "removetrackhitfiltercommands": "",
516  "appendtrackhitfiltercommands": "",
517  }
518  def getRepMap(self, alignment=None):
519  result = super(GenericValidationData_CTSR, self).getRepMap(alignment)
520 
521  from .trackSplittingValidation import TrackSplittingValidation
522  result.update({
523  "ValidationSequence": self.ValidationSequence,
524  "istracksplitting": str(isinstance(self, TrackSplittingValidation)),
525  "cosmics0T": str(self.cosmics0T),
526  "use_d0cut": str(self.use_d0cut),
527  "ispvvalidation": str(self.isPVValidation)
528  })
529 
530  commands = []
531  for removeorappend in "remove", "append":
532  optionname = removeorappend + "trackhitfiltercommands"
533  if result[optionname]:
534  for command in result[optionname].split(","):
535  command = command.strip()
536  commands.append('process.TrackerTrackHitFilter.commands.{}("{}")'.format(removeorappend, command))
537  result["trackhitfiltercommands"] = "\n".join(commands)
538 
539  return result
540  @property
541  def use_d0cut(self):
542  return "Cosmics" not in self.general["trackcollection"] #use it for collisions only
543  @property
544  def isPVValidation(self):
545  return False # only for PV Validation sequence
546  @property
548  return configTemplates.CommonTrackSelectionRefitting
549  @property
550  def DefinePath(self):
551  return configTemplates.DefinePath_CommonSelectionRefitting
552  @abstractproperty
554  pass
555  @property
556  def cosmics0T(self):
557  if "Cosmics" not in self.general["trackcollection"]: return False
558  Bfield = self.dataset.magneticFieldForRun()
559  if Bfield < 0.5: return True
560  if isinstance(Bfield, str):
561  if "unknown " in Bfield:
562  msg = Bfield.replace("unknown ","",1)
563  elif Bfield == "unknown":
564  msg = "Can't get the B field for %s." % self.dataset.name()
565  else:
566  msg = "B field = {}???".format(Bfield)
567  raise AllInOneError(msg + "\n"
568  "To use this dataset, specify magneticfield = [value] in your .ini config file.")
569  return False
570 
572  @classmethod
573  def initMerge(cls):
574  return ""
575  @abstractmethod
576  def appendToMerge(self):
577  pass
578 
579  @classmethod
580  def doInitMerge(cls):
581  from .plottingOptions import PlottingOptions
582  result = cls.initMerge()
583  result = replaceByMap(result, PlottingOptions(None, cls))
584  if result and result[-1] != "\n": result += "\n"
585  return result
586  def doMerge(self):
587  result = self.appendToMerge()
588  if result[-1] != "\n": result += "\n"
589  result += ("if [[ tmpMergeRetCode -eq 0 ]]; then\n"
590  " xrdcp -f .oO[finalOutputFile]Oo. root://eoscms//eos/cms.oO[finalResultFile]Oo.\n"
591  "fi\n"
592  "if [[ ${tmpMergeRetCode} -gt ${mergeRetCode} ]]; then\n"
593  " mergeRetCode=${tmpMergeRetCode}\n"
594  "fi\n")
595  result = replaceByMap(result, self.getRepMap())
596  return result
597 
599  @classmethod
600  def runPlots(cls, validations):
601  return ("cp .oO[plottingscriptpath]Oo. .\n"
602  "root -x -b -q .oO[plottingscriptname]Oo.++")
603  @abstractmethod
604  def appendToPlots(self):
605  pass
606  @abstractmethod
608  """override with a classmethod"""
609  @abstractmethod
611  """override with a classmethod"""
612  @abstractmethod
613  def plotsdirname(cls):
614  """override with a classmethod"""
615 
616  @classmethod
617  def doRunPlots(cls, validations):
618  from .plottingOptions import PlottingOptions
619  cls.createPlottingScript(validations)
620  result = cls.runPlots(validations)
621  result = replaceByMap(result, PlottingOptions(None, cls))
622  if result and result[-1] != "\n": result += "\n"
623  return result
624  @classmethod
625  def createPlottingScript(cls, validations):
626  from .plottingOptions import PlottingOptions
627  repmap = PlottingOptions(None, cls).copy()
628  filename = replaceByMap(".oO[plottingscriptpath]Oo.", repmap)
629  repmap["PlottingInstantiation"] = "\n".join(
630  replaceByMap(v.appendToPlots(), v.getRepMap()).rstrip("\n")
631  for v in validations
632  )
633  plottingscript = replaceByMap(cls.plottingscripttemplate(), repmap)
634  with open(filename, 'w') as f:
635  f.write(plottingscript)
636 
639  def __init__(self, name, values, format=None, latexname=None, latexformat=None):
640  """
641  name: name of the summary item, goes on top of the column
642  values: value for each alignment (in order of rows)
643  format: python format string (default: {:.3g}, meaning up to 3 significant digits)
644  latexname: name in latex form, e.g. if name=sigma you might want latexname=\sigma (default: name)
645  latexformat: format for latex (default: format)
646  """
647  if format is None: format = "{:.3g}"
648  if latexname is None: latexname = name
649  if latexformat is None: latexformat = format
650 
651  self.__name = name
652  self.__values = values
653  self.__format = format
654  self.__latexname = latexname
655  self.__latexformat = latexformat
656 
657  def name(self, latex=False):
658  if latex:
659  return self.__latexname
660  else:
661  return self.__name
662 
663  def format(self, value, latex=False):
664  if latex:
665  fmt = self.__latexformat
666  else:
667  fmt = self.__format
668  if re.match(".*[{][^}]*[fg][}].*", fmt):
669  value = float(value)
670  return fmt.format(value)
671 
672  def values(self, latex=False):
673  result = [self.format(v, latex=latex) for v in self.__values]
674  return result
675 
676  def value(self, i, latex):
677  return self.values(latex)[i]
678 
679  @abstractmethod
680  def getsummaryitems(cls, folder):
681  """override with a classmethod that returns a list of SummaryItems
682  based on the plots saved in folder"""
683 
684  __summaryitems = None
685  __lastfolder = None
686 
687  @classmethod
688  def summaryitemsstring(cls, folder=None, latex=False, transpose=True):
689  if folder is None: folder = cls.plotsdirname()
690  if folder.startswith( "/castor/" ):
691  folder = "rfio:%(file)s"%repMap
692  elif folder.startswith( "/store/" ):
693  folder = "root://eoscms.cern.ch//eos/cms%(file)s"%repMap
694 
695  if cls.__summaryitems is None or cls.__lastfolder != folder:
696  cls.__lastfolder = folder
697  cls.__summaryitems = cls.getsummaryitems(folder)
698 
699  summaryitems = cls.__summaryitems
700 
701  if not summaryitems:
702  raise AllInOneError("No summary items!")
703  size = {len(_.values(latex)) for _ in summaryitems}
704  if len(size) != 1:
705  raise AllInOneError("Some summary items have different numbers of values\n{}".format(size))
706  size = size.pop()
707 
708  if transpose:
709  columnwidths = ([max(len(_.name(latex)) for _ in summaryitems)]
710  + [max(len(_.value(i, latex)) for _ in summaryitems) for i in range(size)])
711  else:
712  columnwidths = [max(len(entry) for entry in [_.name(latex)] + _.values(latex)) for _ in summaryitems]
713 
714  if latex:
715  join = " & "
716  else:
717  join = " "
718  row = join.join("{{:{}}}".format(width) for width in columnwidths)
719 
720  if transpose:
721  rows = [row.format(*[_.name(latex)]+_.values(latex)) for _ in summaryitems]
722  else:
723  rows = []
724  rows.append(row.format(*(_.name for _ in summaryitems)))
725  for i in range(size):
726  rows.append(row.format(*(_.value(i, latex) for _ in summaryitems)))
727 
728  if latex:
729  join = " \\\\\n"
730  else:
731  join = "\n"
732  result = join.join(rows)
733  if latex:
734  result = (r"\begin{{tabular}}{{{}}}".format("|" + "|".join("c"*(len(columnwidths))) + "|") + "\n"
735  + result + "\n"
736  + r"\end{tabular}")
737  return result
738 
739  @classmethod
740  def printsummaryitems(cls, *args, **kwargs):
741  print(cls.summaryitemsstring(*args, **kwargs))
742  @classmethod
743  def writesummaryitems(cls, filename, *args, **kwargs):
744  with open(filename, "w") as f:
745  f.write(cls.summaryitemsstring(*args, **kwargs)+"\n")
746 
748  @classmethod
749  def getsummaryitems(cls, folder):
750  result = []
751  with open(os.path.join(folder, "{}Summary.txt".format(cls.__name__))) as f:
752  for line in f:
753  split = line.rstrip("\n").split("\t")
754  kwargs = {}
755  for thing in split[:]:
756  if thing.startswith("format="):
757  kwargs["format"] = thing.replace("format=", "", 1)
758  split.remove(thing)
759  if thing.startswith("latexname="):
760  kwargs["latexname"] = thing.replace("latexname=", "", 1)
761  split.remove(thing)
762  if thing.startswith("latexformat="):
763  kwargs["latexformat"] = thing.replace("latexformat=", "", 1)
764  split.remove(thing)
765 
766  name = split[0]
767  values = split[1:]
768  result.append(cls.SummaryItem(name, values, **kwargs))
769  return result
770 
772  @classmethod
773  def doComparison(cls, validations):
774  from .plottingOptions import PlottingOptions
775  repmap = PlottingOptions(None, cls).copy()
776  repmap["compareStrings"] = " , ".join(v.getCompareStrings("OfflineValidation") for v in validations)
777  repmap["compareStringsPlain"] = " , ".join(v.getCompareStrings("OfflineValidation", True) for v in validations)
778  comparison = replaceByMap(cls.comparisontemplate(), repmap)
779  return comparison
780 
781  @classmethod
783  return configTemplates.compareAlignmentsExecution
784  @classmethod
786  return ".oO[Alignment/OfflineValidation]Oo./scripts/.oO[compareAlignmentsName]Oo."
787  @abstractmethod
789  """classmethod"""
790 
791 class ValidationForPresentation(ValidationWithPlots):
792  @abstractmethod
794  """classmethod"""
def __init__(self, valName, alignment, config)
def pythonboolstring(string, name)
def createConfiguration(self, fileContents, path, schedule=None, repMap=None, repMaps=None)
def getCommandOutput2(command)
def createScript(self, fileContents, path, downloadFiles=[], repMap=None, repMaps=None)
def writesummaryitems(cls, filename, args, kwargs)
def __init__(self, valName, alignment, config)
def addIndex(filename, njobs, index=None)
def createCrabCfg(self, path, crabCfgBaseName)
def __new__(cls, clsname, bases, dct)
void print(TMatrixD &m, const char *label=nullptr, bool mathematicaFormat=false)
Definition: Utilities.cc:47
def summaryitemsstring(cls, folder=None, latex=False, transpose=True)
def PlottingOptions(config, valType)
def replaceByMap(target, the_map)
— Helpers —############################
def createFiles(self, fileContents, path, repMap=None, repMaps=None)
static std::string join(char **cmd)
Definition: RemoteFile.cc:19
def __init__(self, name, values, format=None, latexname=None, latexformat=None)
def boolfromstring(string, name)
def createScript(self, path, template=configTemplates.scriptTemplate, downloadFiles=[], repMap=None, repMaps=None)
def createCrabCfg(self, fileContents, path)
#define update(a, b)
def getRepMap(self, alignment=None)
def getCompareStrings(self, requestId=None, plain=False)
#define str(s)