CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
validateAlignments.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #test execute: export CMSSW_BASE=/tmp/CMSSW && ./validateAlignments.py -c defaultCRAFTValidation.ini,test.ini -n -N test
3 import os
4 import sys
5 import optparse
6 import datetime
7 import shutil
8 import fnmatch
9 
10 import Alignment.OfflineValidation.TkAlAllInOneTool.configTemplates \
11  as configTemplates
12 import Alignment.OfflineValidation.TkAlAllInOneTool.crabWrapper as crabWrapper
13 from Alignment.OfflineValidation.TkAlAllInOneTool.TkAlExceptions \
14  import AllInOneError
15 from Alignment.OfflineValidation.TkAlAllInOneTool.helperFunctions \
16  import replaceByMap, getCommandOutput2
17 from Alignment.OfflineValidation.TkAlAllInOneTool.betterConfigParser \
18  import BetterConfigParser
19 from Alignment.OfflineValidation.TkAlAllInOneTool.alignment import Alignment
20 
21 from Alignment.OfflineValidation.TkAlAllInOneTool.genericValidation \
22  import GenericValidation
23 from Alignment.OfflineValidation.TkAlAllInOneTool.geometryComparison \
24  import GeometryComparison
25 from Alignment.OfflineValidation.TkAlAllInOneTool.offlineValidation \
26  import OfflineValidation, OfflineValidationDQM, OfflineValidationParallel
27 from Alignment.OfflineValidation.TkAlAllInOneTool.monteCarloValidation \
28  import MonteCarloValidation
29 from Alignment.OfflineValidation.TkAlAllInOneTool.trackSplittingValidation \
30  import TrackSplittingValidation
31 from Alignment.OfflineValidation.TkAlAllInOneTool.zMuMuValidation \
32  import ZMuMuValidation
33 import Alignment.OfflineValidation.TkAlAllInOneTool.globalDictionaries \
34  as globalDictionaries
35 
36 
37 ####################--- Classes ---############################
39  def __init__( self, validation, config, options ):
40  if validation[1] == "":
41  # intermediate syntax
42  valString = validation[0].split( "->" )[0]
43  alignments = validation[0].split( "->" )[1]
44  # force user to use the normal syntax
45  if "->" in validation[0]:
46  msg = ("Instead of using the intermediate syntax\n'"
47  +valString.strip()+"-> "+alignments.strip()
48  +":'\nyou have to use the now fully supported syntax \n'"
49  +valString.strip()+": "
50  +alignments.strip()+"'.")
51  raise AllInOneError(msg)
52  else:
53  valString = validation[0]
54  alignments = validation[1]
55  valString = valString.split()
56  self.__valType = valString[0]
57  self.__valName = valString[1]
58  self.__commandLineOptions = options
59  self.__config = config
60  # workaround for intermediate parallel version
61  if self.__valType == "offlineParallel":
62  section = "offline" + ":" + self.__valName
63  else:
64  section = self.__valType + ":" + self.__valName
65  if not self.__config.has_section( section ):
66  raise AllInOneError, ("Validation '%s' of type '%s' is requested in"
67  " '[validation]' section, but is not defined."
68  "\nYou have to add a '[%s]' section."
69  %( self.__valName, self.__valType, section ))
70  self.validation = self.__getValidation( self.__valType, self.__valName,
71  alignments, self.__config,
72  options )
73 
74  def __getValidation( self, valType, name, alignments, config, options ):
75  if valType == "compare":
76  alignmentsList = alignments.split( "," )
77  firstAlignList = alignmentsList[0].split()
78  firstAlignName = firstAlignList[0].strip()
79  if firstAlignName == "IDEAL":
80  raise AllInOneError, ("'IDEAL' has to be the second (reference)"
81  " alignment in 'compare <val_name>: "
82  "<alignment> <reference>'.")
83  if len( firstAlignList ) > 1:
84  firstRun = firstAlignList[1]
85  else:
86  firstRun = "1"
87  firstAlign = Alignment( firstAlignName, self.__config, firstRun )
88  secondAlignList = alignmentsList[1].split()
89  secondAlignName = secondAlignList[0].strip()
90  if len( secondAlignList ) > 1:
91  secondRun = secondAlignList[1]
92  else:
93  secondRun = "1"
94  if secondAlignName == "IDEAL":
95  secondAlign = secondAlignName
96  else:
97  secondAlign = Alignment( secondAlignName, self.__config,
98  secondRun )
99  # check if alignment was already compared previously
100  try:
101  randomWorkdirPart = \
102  globalDictionaries.alignRandDict[firstAlignName]
103  except KeyError:
104  randomWorkdirPart = None
105 
106  validation = GeometryComparison( name, firstAlign, secondAlign,
107  self.__config,
108  self.__commandLineOptions.getImages,
109  randomWorkdirPart )
110  globalDictionaries.alignRandDict[firstAlignName] = \
111  validation.randomWorkdirPart
112  if not secondAlignName == "IDEAL":
113  globalDictionaries.alignRandDict[secondAlignName] = \
114  validation.randomWorkdirPart
115  elif valType == "offline":
116  validation = OfflineValidation( name,
117  Alignment( alignments.strip(), self.__config ), self.__config )
118  elif valType == "offlineDQM":
119  validation = OfflineValidationDQM( name,
120  Alignment( alignments.strip(), self.__config ), self.__config )
121  elif valType == "offlineParallel":
122  validation = OfflineValidationParallel( name,
123  Alignment( alignments.strip(), self.__config ), self.__config )
124  elif valType == "mcValidate":
125  validation = MonteCarloValidation( name,
126  Alignment( alignments.strip(), self.__config ), self.__config )
127  elif valType == "split":
128  validation = TrackSplittingValidation( name,
129  Alignment( alignments.strip(), self.__config ), self.__config )
130  elif valType == "zmumu":
131  validation = ZMuMuValidation( name,
132  Alignment( alignments.strip(), self.__config ), self.__config )
133  else:
134  raise AllInOneError, "Unknown validation mode '%s'"%valType
135  return validation
136 
137  def __createJob( self, jobMode, outpath ):
138  """This private method creates the needed files for the validation job.
139  """
140  self.validation.createConfiguration( outpath )
141  self.__scripts = self.validation.createScript( outpath )
142  if jobMode.split( ',' )[0] == "crab":
143  self.validation.createCrabCfg( outpath )
144  return None
145 
146  def createJob(self):
147  """This is the method called to create the job files."""
148  self.__createJob( self.validation.jobmode,
149  os.path.abspath( self.__commandLineOptions.Name) )
150 
151  def runJob( self ):
152  general = self.__config.getGeneral()
153  log = ""
154  for script in self.__scripts:
155  name = os.path.splitext( os.path.basename( script) )[0]
156  if self.__commandLineOptions.dryRun:
157  print "%s would run: %s"%( name, os.path.basename( script) )
158  continue
159  log = "> Validating "+name
160  print "> Validating "+name
161  if self.validation.jobmode == "interactive":
162  log += getCommandOutput2( script )
163  elif self.validation.jobmode.split(",")[0] == "lxBatch":
164  repMap = {
165  "commands": self.validation.jobmode.split(",")[1],
166  "logDir": general["logdir"],
167  "jobName": name,
168  "script": script,
169  "bsub": "/afs/cern.ch/cms/caf/scripts/cmsbsub"
170  }
171  log+=getCommandOutput2("%(bsub)s %(commands)s -J %(jobName)s "
172  "-o %(logDir)s/%(jobName)s.stdout -e "
173  "%(logDir)s/%(jobName)s.stderr "
174  "%(script)s"%repMap)
175  elif self.validation.jobmode.split( "," )[0] == "crab":
176  os.chdir( general["logdir"] )
177  crabName = "crab." + os.path.basename( script )[:-3]
178  theCrab = crabWrapper.CrabWrapper()
179  options = { "-create": "",
180  "-cfg": crabName + ".cfg",
181  "-submit": "" }
182  try:
183  theCrab.run( options )
184  except AllInOneError, e:
185  print "crab:", str(e).split("\n")[0]
186  exit(1)
187  else:
188  raise AllInOneError, ("Unknown 'jobmode'!\n"
189  "Please change this parameter either in "
190  "the [general] or in the ["
191  + self.__valType + ":" + self.__valName
192  + "] section to one of the following "
193  "values:\n"
194  "\tinteractive\n\tlxBatch, -q <queue>\n"
195  "\tcrab, -q <queue>")
196  return log
197 
198  def getValidation( self ):
199  return self.validation
200 
201 
202 ####################--- Functions ---############################
203 def createOfflineJobsMergeScript(offlineValidationList, outFilePath):
204  repMap = offlineValidationList[0].getRepMap() # bit ugly since some special features are filled
205  repMap[ "mergeOfflinParJobsInstantiation" ] = "" #give it a "" at first in order to get the initialisation back
206 
207  theFile = open( outFilePath, "w" )
208  theFile.write( replaceByMap( configTemplates.mergeOfflineParJobsTemplate ,repMap ) )
209  theFile.close()
210 
211 def createExtendedValidationScript(offlineValidationList, outFilePath, resultPlotFile):
212  repMap = offlineValidationList[0].getRepMap() # bit ugly since some special features are filled
213  repMap[ "resultPlotFile" ] = resultPlotFile
214  repMap[ "extendedInstantiation" ] = "" #give it a "" at first in order to get the initialisation back
215 
216  for validation in offlineValidationList:
217  repMap[ "extendedInstantiation" ] = validation.appendToExtendedValidation( repMap[ "extendedInstantiation" ] )
218 
219  theFile = open( outFilePath, "w" )
220  # theFile.write( replaceByMap( configTemplates.extendedValidationTemplate ,repMap ) )
221  theFile.write( replaceByMap( configTemplates.extendedValidationTemplate ,repMap ) )
222  theFile.close()
223 
224 def createTrackSplitPlotScript(trackSplittingValidationList, outFilePath):
225  repMap = trackSplittingValidationList[0].getRepMap() # bit ugly since some special features are filled
226  repMap[ "trackSplitPlotInstantiation" ] = "" #give it a "" at first in order to get the initialisation back
227 
228  for validation in trackSplittingValidationList:
229  repMap[ "trackSplitPlotInstantiation" ] = validation.appendToExtendedValidation( repMap[ "trackSplitPlotInstantiation" ] )
230 
231  theFile = open( outFilePath, "w" )
232  # theFile.write( replaceByMap( configTemplates.trackSplitPlotTemplate ,repMap ) )
233  theFile.write( replaceByMap( configTemplates.trackSplitPlotTemplate ,repMap ) )
234  theFile.close()
235 
236 def createMergeScript( path, validations ):
237  if(len(validations) == 0):
238  msg = "Cowardly refusing to merge nothing!"
239  raise AllInOneError(msg)
240 
241  repMap = validations[0].getRepMap() #FIXME - not nice this way
242  repMap.update({
243  "DownloadData":"",
244  "CompareAlignments":"",
245  "RunExtendedOfflineValidation":"",
246  "RunTrackSplitPlot":""
247  })
248 
249  comparisonLists = {} # directory of lists containing the validations that are comparable
250  resultPlotFile = "" # string of a file name for createExtendedValidationScript
251  for validation in validations:
252  for referenceName in validation.filesToCompare:
253  validationName = "%s.%s"%(validation.__class__.__name__, referenceName)
254  validationName = validationName.split(".%s"%GenericValidation.defaultReferenceName )[0]
255  if validationName in comparisonLists:
256  comparisonLists[ validationName ].append( validation )
257  else:
258  comparisonLists[ validationName ] = [ validation ]
259  if validationName == "OfflineValidation":
260  resultPlotFile = validationName
261 
262  if "OfflineValidation" in comparisonLists:
263  repMap["extendeValScriptPath"] = \
264  os.path.join(path, "TkAlExtendedOfflineValidation.C")
265  createExtendedValidationScript(comparisonLists["OfflineValidation"],
266  repMap["extendeValScriptPath"],
267  resultPlotFile)
268  repMap["RunExtendedOfflineValidation"] = \
269  replaceByMap(configTemplates.extendedValidationExecution, repMap)
270 
271  if "TrackSplittingValidation" in comparisonLists:
272  repMap["trackSplitPlotScriptPath"] = \
273  os.path.join(path, "TkAlTrackSplitPlot.C")
274  createTrackSplitPlotScript(comparisonLists["TrackSplittingValidation"],
275  repMap["trackSplitPlotScriptPath"] )
276  repMap["RunTrackSplitPlot"] = \
277  replaceByMap(configTemplates.trackSplitPlotExecution, repMap)
278 
279  repMap["CompareAlignments"] = "#run comparisons"
280  for validationId in comparisonLists:
281  compareStrings = [ val.getCompareStrings(validationId) for val in comparisonLists[validationId] ]
282 
283  repMap.update({"validationId": validationId,
284  "compareStrings": " , ".join(compareStrings) })
285 
286  repMap["CompareAlignments"] += \
287  replaceByMap(configTemplates.compareAlignmentsExecution, repMap)
288 
289  filePath = os.path.join(path, "TkAlMerge.sh")
290  theFile = open( filePath, "w" )
291  theFile.write( replaceByMap( configTemplates.mergeTemplate, repMap ) )
292  theFile.close()
293  os.chmod(filePath,0755)
294 
295  return filePath
296 
297 def createParallelMergeScript( path, validations ):
298  if( len(validations) == 0 ):
299  raise AllInOneError, "cowardly refusing to merge nothing!"
300 
301  repMap = validations[0].getRepMap() #FIXME - not nice this way
302  repMap.update({
303  "DownloadData":"",
304  "CompareAlignments":"",
305  "RunExtendedOfflineValidation":""
306  })
307 
308  comparisonLists = {} # directory of lists containing the validations that are comparable
309  resultPlotFile = "" # string of a file name for createExtendedValidationScript
310  for validation in validations:
311  for referenceName in validation.filesToCompare:
312  validationName = "%s.%s"%(validation.__class__.__name__, referenceName)
313  validationName = validationName.split(".%s"%GenericValidation.defaultReferenceName )[0]
314  if validationName in comparisonLists:
315  comparisonLists[ validationName ].append( validation )
316  else:
317  comparisonLists[ validationName ] = [ validation ]
318  if validationName == "OfflineValidationParallel":
319  resultPlotFile = validationName
320 
321  if "OfflineValidationParallel" in comparisonLists:
322  repMap["extendeValScriptPath"] = os.path.join(path, "TkAlExtendedOfflineValidation.C")
323  createExtendedValidationScript( comparisonLists["OfflineValidationParallel"], repMap["extendeValScriptPath"], resultPlotFile )
324  repMap["mergeOfflineParJobsScriptPath"] = os.path.join(path, "TkAlOfflineJobsMerge.C")
325  createOfflineJobsMergeScript( comparisonLists["OfflineValidationParallel"],
326  repMap["mergeOfflineParJobsScriptPath"] )
327 
328  # introduced to merge individual validation outputs separately
329  # -> avoids problems with merge script
330  repMap["haddLoop"] = "mergeRetCode=0\n"
331  repMap["rmUnmerged"] = "if [[ mergeRetCode -eq 0 ]]; then\n"
332  for validation in comparisonLists["OfflineValidationParallel"]:
333  repMap["haddLoop"] = validation.appendToMergeParJobs(repMap["haddLoop"])
334  repMap["haddLoop"] += "tmpMergeRetCode=${?}\n"
335  repMap["haddLoop"] += ("if [[ mergeRetCode -eq 0 ]]; "
336  "then mergeRetCode=${tmpMergeRetCode}; "
337  "fi\n")
338  repMap["haddLoop"] += ("cmsStage -f "
339  +validation.getRepMap()["outputFile"]
340  +" "
341  +validation.getRepMap()["resultFile"]
342  +"\n")
343  for f in validation.outputFiles:
344  longName = os.path.join("/store/caf/user/$USER/",
345  validation.getRepMap()["eosdir"], f)
346  repMap["rmUnmerged"] += " cmsRm "+longName+"\n"
347  repMap["rmUnmerged"] += ("else\n"
348  " echo \"WARNING: Merging failed, unmerged"
349  " files won't be deleted.\"\n"
350  "fi\n")
351 
352  repMap["RunExtendedOfflineValidation"] = \
353  replaceByMap(configTemplates.extendedValidationExecution, repMap)
354 
355  # DownloadData is the section which merges output files from parallel jobs
356  # it uses the file TkAlOfflineJobsMerge.C
357  repMap["DownloadData"] += replaceByMap("rfcp .oO[mergeOfflineParJobsScriptPath]Oo. .", repMap)
358  repMap["DownloadData"] += replaceByMap( configTemplates.mergeOfflineParallelResults, repMap )
359 
360  repMap["CompareAlignments"] = "#run comparisons"
361  for validationId in comparisonLists:
362  compareStrings = [ val.getCompareStrings(validationId) for val in comparisonLists[validationId] ]
363 
364  repMap.update({"validationId": validationId,
365  "compareStrings": " , ".join(compareStrings) })
366 
367  repMap["CompareAlignments"] += \
368  replaceByMap(configTemplates.compareAlignmentsExecution, repMap)
369 
370  filePath = os.path.join(path, "TkAlMerge.sh")
371  theFile = open( filePath, "w" )
372  theFile.write( replaceByMap( configTemplates.mergeTemplate, repMap ) )
373  theFile.close()
374  os.chmod(filePath,0755)
375 
376  return filePath
377 
378 def loadTemplates( config ):
379  if config.has_section("alternateTemplates"):
380  for templateName in config.options("alternateTemplates"):
381  newTemplateName = config.get("alternateTemplates", templateName )
382  #print "replacing default %s template by %s"%( templateName, newTemplateName)
383  configTemplates.alternateTemplate(templateName, newTemplateName)
384 
385 
386 ####################--- Main ---############################
387 def main(argv = None):
388  if argv == None:
389  argv = sys.argv[1:]
390  optParser = optparse.OptionParser()
391  optParser.description = """ all-in-one alignment Validation
392  This will run various validation procedures either on batch queues or interactviely.
393 
394  If no name is given (-N parameter) a name containing time and date is created automatically
395 
396  To merge the outcome of all validation procedures run TkAlMerge.sh in your validation's directory.
397  """
398  optParser.add_option("-n", "--dryRun", dest="dryRun", action="store_true", default=False,
399  help="create all scripts and cfg File but do not start jobs (default=False)")
400  optParser.add_option( "--getImages", dest="getImages", action="store_true", default=False,
401  help="get all Images created during the process (default= False)")
402  defaultConfig = "TkAlConfig.ini"
403  optParser.add_option("-c", "--config", dest="config", default = defaultConfig,
404  help="configuration to use (default TkAlConfig.ini) this can be a comma-seperated list of all .ini file you want to merge", metavar="CONFIG")
405  optParser.add_option("-N", "--Name", dest="Name",
406  help="Name of this validation (default: alignmentValidation_DATE_TIME)", metavar="NAME")
407  optParser.add_option("-r", "--restrictTo", dest="restrictTo",
408  help="restrict validations to given modes (comma seperated) (default: no restriction)", metavar="RESTRICTTO")
409  optParser.add_option("-s", "--status", dest="crabStatus", action="store_true", default = False,
410  help="get the status of the crab jobs", metavar="STATUS")
411  optParser.add_option("-d", "--debug", dest="debugMode", action="store_true",
412  default = False,
413  help="Run the tool to get full traceback of errors.",
414  metavar="DEBUG")
415 
416  (options, args) = optParser.parse_args(argv)
417 
418  if not options.restrictTo == None:
419  options.restrictTo = options.restrictTo.split(",")
420 
421  options.config = [ os.path.abspath( iniFile ) for iniFile in \
422  options.config.split( "," ) ]
423  config = BetterConfigParser()
424  outputIniFileSet = set( config.read( options.config ) )
425  failedIniFiles = [ iniFile for iniFile in options.config if iniFile not in outputIniFileSet ]
426 
427  # Check for missing ini file
428  if options.config == [ os.path.abspath( defaultConfig ) ]:
429  if ( not options.crabStatus ) and \
430  ( not os.path.exists( defaultConfig ) ):
431  raise AllInOneError, ( "Default 'ini' file '%s' not found!\n"
432  "You can specify another name with the "
433  "command line option '-c'/'--config'."
434  %( defaultConfig ))
435  else:
436  for iniFile in failedIniFiles:
437  if not os.path.exists( iniFile ):
438  raise AllInOneError, ( "'%s' does not exist. Please check for "
439  "typos in the filename passed to the "
440  "'-c'/'--config' option!"
441  %( iniFile ) )
442  else:
443  raise AllInOneError, ( "'%s' does exist, but parsing of the "
444  "content failed!" )
445 
446  # get the job name
447  if options.Name == None:
448  if not options.crabStatus:
449  options.Name = "alignmentValidation_%s"%(datetime.datetime.now().strftime("%y%m%d_%H%M%S"))
450  else:
451  existingValDirs = fnmatch.filter( os.walk( '.' ).next()[1],
452  "alignmentValidation_*" )
453  if len( existingValDirs ) > 0:
454  options.Name = existingValDirs[-1]
455  else:
456  print "Cannot guess last working directory!"
457  print ( "Please use the parameter '-N' or '--Name' to specify "
458  "the task for which you want a status report." )
459  return 1
460 
461  # set output path
462  outPath = os.path.abspath( options.Name )
463 
464  # Check status of submitted jobs and return
465  if options.crabStatus:
466  os.chdir( outPath )
467  crabLogDirs = fnmatch.filter( os.walk('.').next()[1], "crab.*" )
468  if len( crabLogDirs ) == 0:
469  print "Found no crab tasks for job name '%s'"%( options.Name )
470  return 1
471  theCrab = crabWrapper.CrabWrapper()
472  for crabLogDir in crabLogDirs:
473  print
474  print "*" + "=" * 78 + "*"
475  print ( "| Status report and output retrieval for:"
476  + " " * (77 - len( "Status report and output retrieval for:" ) )
477  + "|" )
478  taskName = crabLogDir.replace( "crab.", "" )
479  print "| " + taskName + " " * (77 - len( taskName ) ) + "|"
480  print "*" + "=" * 78 + "*"
481  print
482  crabOptions = { "-getoutput":"",
483  "-c": crabLogDir }
484  try:
485  theCrab.run( crabOptions )
486  except AllInOneError, e:
487  print "crab: No output retrieved for this task."
488  crabOptions = { "-status": "",
489  "-c": crabLogDir }
490  theCrab.run( crabOptions )
491  return
492 
493  general = config.getGeneral()
494  config.set("internals","workdir",os.path.join(general["workdir"],options.Name) )
495  config.set("general","datadir",os.path.join(general["datadir"],options.Name) )
496  config.set("general","logdir",os.path.join(general["logdir"],options.Name) )
497  config.set("general","eosdir",os.path.join("AlignmentValidation", general["eosdir"], options.Name) )
498 
499  # clean up of log directory to avoid cluttering with files with different
500  # random numbers for geometry comparison
501  if os.path.isdir( outPath ):
502  shutil.rmtree( outPath )
503 
504  if not os.path.exists( outPath ):
505  os.makedirs( outPath )
506  elif not os.path.isdir( outPath ):
507  raise AllInOneError,"the file %s is in the way rename the Job or move it away"%outPath
508 
509  # replace default templates by the ones specified in the "alternateTemplates" section
510  loadTemplates( config )
511 
512  #save backup configuration file
513  backupConfigFile = open( os.path.join( outPath, "usedConfiguration.ini" ) , "w" )
514  config.write( backupConfigFile )
515 
516  validations = []
517  for validation in config.items("validation"):
518  alignmentList = validation[1].split(config.getSep())
519  validationsToAdd = [(validation[0],alignment) \
520  for alignment in alignmentList]
521  validations.extend(validationsToAdd)
522  jobs = [ ValidationJob( validation, config, options) \
523  for validation in validations ]
524  map( lambda job: job.createJob(), jobs )
525  validations = [ job.getValidation() for job in jobs ]
526 
527  if "OfflineValidationParallel" not in [val.__class__.__name__ for val in validations]:
528  createMergeScript(outPath, validations)
529  else:
530  createParallelMergeScript( outPath, validations )
531 
532  print
533  map( lambda job: job.runJob(), jobs )
534 
535 
536 if __name__ == "__main__":
537  # main(["-n","-N","test","-c","defaultCRAFTValidation.ini,latestObjects.ini","--getImages"])
538  if "-d" in sys.argv[1:] or "--debug" in sys.argv[1:]:
539  main()
540  else:
541  try:
542  main()
543  except AllInOneError, e:
544  print "\nAll-In-One Tool:", str(e)
545  exit(1)
— Classes —############################
def main
— Main —############################
def alternateTemplate
### Alternate Templates ###
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def replaceByMap
— Helpers —############################
if(dp >Float(M_PI)) dp-
Definition: main.py:1
double split
Definition: MVATrainer.cc:139