ATLAS Offline Software
Loading...
Searching...
No Matches
CPGridRun.CPGridRun Class Reference
Collaboration diagram for CPGridRun.CPGridRun:

Public Member Functions

 __init__ (self)
 inputList (self)
 outputFilesParsing (self)
 printHelp (self)
 getParser (self)
 configureSubmission (self)
 configureSubmissionSingleSample (self, input, name)
 printInputDetails (self)
 hasPyami (self)
bool checkInputInPyami (self)
 outputDSFormatter (self, name, label)
 execFormatter (self)
 outputsFormatter (self)
bool hasPrun (self)
 submit (self)
 printDelayedErrorCollection (self)
 checkExternalTools (self)
 askSubmission (self)

Static Public Member Functions

 isAtlasProductionFormat (name)
 rucioCustomNameParser (filename)
 atlasProductionNameParser (filename)

Public Attributes

dict cmd = {}
dict outputs = {}
 gridParser = self._parseGridArguments()
dict prunArgsDict = self._createPrunArgsDict()
 args
 unknown_args = parser.parse_known_args()
 output_files = output_files
 inputList

Protected Member Functions

 _initRunscript (self)
 _parseGridArguments (self)
dict _createPrunArgsDict (self)
dict _unknownArgsDict (self)
 _checkPrunArgs (self, argDict)
 _prepareAmiQueryFromInputList (self)
bool _analyzeAmiResults (self, results, datasetPtag)
 _filesChangedOrTarballNotCreated (self)
 _hasCompressedTarball (self)
 _outputDSFormatter (self, name)
 _customOutputDSFormatter (self, name, label)
 _suffixFormatter (self)
 _filesChanged (self)
 _buildDir (self)
 _sourceDir (self)
 _checkYamlExists (self, runscriptArgs)

Static Protected Member Functions

tuple[list[str], list[str]] _parseInputFileList (Path path, bool bulk_submission=False)

Protected Attributes

dict _errorCollector = {}
 _runscript = None
str _tarfile = 'cpgrid.tar.gz'
bool _isFirstRun = True
bool _tarballRecreated = False
list _inputList = None
tuple[list[str], list[str]] _inputNames = None
 _yamlPath = None

Detailed Description

Definition at line 14 of file CPGridRun.py.

Constructor & Destructor Documentation

◆ __init__()

CPGridRun.CPGridRun.__init__ ( self)

Definition at line 15 of file CPGridRun.py.

15 def __init__(self):
16 self._errorCollector = {} # Delay the error collection until the end of the script for better user experience
17 self._runscript = None
18 self._tarfile = 'cpgrid.tar.gz'
19 self._isFirstRun = True
20 self._tarballRecreated = False
21 self._inputList = None
22 self._inputNames = None
23 self._yamlPath = None
24 self.cmd = {} # sample name -> command
25 self.outputs = {} # sample name -> output dataset name
26
27 self.gridParser = self._parseGridArguments()
28 self.prunArgsDict = self._createPrunArgsDict()
29
30 if self.args.help:
31 self._initRunscript()
32 self.printHelp()
33 sys.exit(0)
34

Member Function Documentation

◆ _analyzeAmiResults()

bool CPGridRun.CPGridRun._analyzeAmiResults ( self,
results,
datasetPtag )
protected

Definition at line 304 of file CPGridRun.py.

304 def _analyzeAmiResults(self, results, datasetPtag) -> bool:
305 import re
306 regex = re.compile("_p[0-9]+")
307 results = [r['ldn'] for r in results]
308 notFound = []
309 latestPtag = {}
310
311 for datasetName in self.cmd:
312 if datasetName not in results:
313 notFound.append(datasetName)
314
315 base = regex.sub("_p%", datasetName)
316 matching = [r for r in results if r.startswith(base.replace("_p%", ""))]
317 for m in matching:
318 mParsed = CPGridRun.atlasProductionNameParser(m)
319 try:
320 mPtagInt = int(mParsed.get('ptag', 'p0')[1:])
321 currentPtagInt = int(datasetPtag.get(datasetName, 'p0')[1:])
322 if mPtagInt > currentPtagInt:
323 latestPtag[datasetName] = f"p{mPtagInt}"
324 except (ValueError, TypeError):
325 continue
326
327 if latestPtag:
328 logCPGridRun.info("Newer version of datasets found in AMI:")
329 for name, ptag in latestPtag.items():
330 logCPGridRun.info(f"{name} -> ptag: {ptag}")
331
332 if notFound:
333 logCPGridRun.error("Some input datasets are not available in AMI, missing datasets are likely to fail on the grid:")
334 logCPGridRun.error(", ".join(notFound))
335 return False
336
337 return True
338

◆ _buildDir()

CPGridRun.CPGridRun._buildDir ( self)
protected

Definition at line 421 of file CPGridRun.py.

421 def _buildDir(self):
422 buildDir = os.environ["CMAKE_PREFIX_PATH"]
423 buildDir = os.path.dirname(buildDir.split(":")[0])
424 return buildDir
425

◆ _checkPrunArgs()

CPGridRun.CPGridRun._checkPrunArgs ( self,
argDict )
protected
check the arguments against the prun script to ensure they are valid
See https://github.com/PanDAWMS/panda-client/blob/master/pandaclient/PrunScript.py

Definition at line 226 of file CPGridRun.py.

226 def _checkPrunArgs(self,argDict):
227 '''
228 check the arguments against the prun script to ensure they are valid
229 See https://github.com/PanDAWMS/panda-client/blob/master/pandaclient/PrunScript.py
230 '''
231 import pandaclient.PrunScript
232 # We need to temporarily clear the sys.argv to avoid the parser from PrunScript to fail
233 original_argv = sys.argv
234 sys.argv = ['prun'] # Reset sys.argv to only contain the script name
235 prunArgsDict = {}
236 prunArgsDict = pandaclient.PrunScript.main(get_options=True)
237 sys.argv = original_argv # Restore the original sys.argv
238 nonPrunOrCPGridArgs = []
239 for arg in argDict:
240 if arg not in prunArgsDict:
241 nonPrunOrCPGridArgs.append(arg)
242 if nonPrunOrCPGridArgs:
243 logCPGridRun.error(f"Unknown arguments detected: {nonPrunOrCPGridArgs}. They do not belong to CPGridRun or Panda.")
244 raise ValueError(f"Unknown arguments detected: {nonPrunOrCPGridArgs}. They do not belong to CPGridRun or Panda.")
245

◆ _checkYamlExists()

CPGridRun.CPGridRun._checkYamlExists ( self,
runscriptArgs )
protected

Definition at line 482 of file CPGridRun.py.

482 def _checkYamlExists(self, runscriptArgs):
483 from AnalysisAlgorithmsConfig.CPBaseRunner import CPBaseRunner
484 if not hasattr(runscriptArgs, 'text_config'):
485 self._errorCollector['no yaml'] = "No YAML configuration file is specified in the exec string. Please provide one using --text-config"
486 return
487 yamlPath = getattr(runscriptArgs, 'text_config')
488 self._yamlPath = yamlPath
489 haveLocalYaml = CPBaseRunner.findLocalPathYamlConfig(yamlPath)
490 if haveLocalYaml:
491 logCPGridRun.warning("A path to a local YAML configuration file is found, but it may not be grid-usable.")
492
493 repoYamls, _ = CPBaseRunner.findRepoPathYamlConfig(yamlPath)
494 if repoYamls and len(repoYamls) > 1:
495 self._errorCollector['ambiguous yamls'] = f'Multiple files named \"{yamlPath}\" found in the analysis repository. Please provide a more specific path to the config file.\nMatches found:\n' + '\n'.join(repoYamls)
496 return
497 elif repoYamls and len(repoYamls) == 1:
498 logCPGridRun.info(f"Found a grid-usable YAML configuration file in the analysis repository: {repoYamls[0]}")
499 return
500
501 if haveLocalYaml and self.args.useCentralPackage:
502 logCPGridRun.warning("A path to a local YAML configuration file is found, no custom packages are found, proceed with /cvmfs packages only.")
503
504 if not repoYamls and not self.args.useCentralPackage:
505 self._errorCollector['no usable yaml'] = f"Grid usable YAML configuration file not found: {yamlPath}"
506 if haveLocalYaml:
507 self._errorCollector['have local yaml'] = f"Only a local YAML configuration file is found: {yamlPath}, not usable in the grid.\n" \
508 f"Make sure the YAML file is in build/x86_64-el9-gcc14-opt/data/package_name/config.yaml. You can install the YAML file through CMakeList.txt with `atlas_install_data( data/* )`; use `-t package_name/config.yaml` in the --exec\n"\
509 f"Or if you are only using central packages, please use the `--useCentralPackage` flag."
510

◆ _createPrunArgsDict()

dict CPGridRun.CPGridRun._createPrunArgsDict ( self)
protected
converting unknown args to a dictionary

Definition at line 93 of file CPGridRun.py.

93 def _createPrunArgsDict(self) -> dict:
94 '''
95 converting unknown args to a dictionary
96 '''
97 unknownArgsDict = self._unknownArgsDict()
98 if unknownArgsDict and self.hasPrun():
99 self._checkPrunArgs(unknownArgsDict)
100 logCPGridRun.info(f"Adding prun exclusive arguments: {unknownArgsDict.keys()}")
101 elif unknownArgsDict:
102 logCPGridRun.warning(f"Unknown arguments detected: {unknownArgsDict}. Cannot check the availability in Prun because Prun is not available / noSubmit is on.")
103 else:
104 pass
105 return unknownArgsDict
106

◆ _customOutputDSFormatter()

CPGridRun.CPGridRun._customOutputDSFormatter ( self,
name,
label )
protected
{group/user}.{username}.{prefix}.{main}.{suffix}

Definition at line 367 of file CPGridRun.py.

367 def _customOutputDSFormatter(self, name, label):
368 '''
369 {group/user}.{username}.{prefix}.{main}.{suffix}
370 '''
371 parts = name.split('.')
372 base = 'group' if self.args.groupProduction else 'user'
373 username = self.args.gridUsername
374 main = label if label else parts[2]
375 main = f"{self.args.prefix}.{main}" if self.args.prefix else main
376 main = f"{main}.{self.args.suffix}" if self.args.suffix else main
377
378 result = [base, username, main]
379 return ".".join(filter(None, result))
380

◆ _filesChanged()

CPGridRun.CPGridRun._filesChanged ( self)
protected

Definition at line 390 of file CPGridRun.py.

390 def _filesChanged(self):
391 tarball_mtime = os.path.getmtime(self._tarfile) if os.path.exists(self._tarfile) else 0
392 buildDir = self._buildDir()
393 sourceDir = self._sourceDir()
394
395 # Check for changes in buildDir
396 for root, _, files in os.walk(buildDir):
397 for file in files:
398 file_path = os.path.join(root, file)
399 try:
400 if os.path.getmtime(file_path) > tarball_mtime:
401 logCPGridRun.info(f"File {file_path} is newer than the tarball.")
402 return True
403 except FileNotFoundError:
404 continue
405
406 # Check for changes in sourceDir
407 if sourceDir is None:
408 logCPGridRun.warning("Source directory is not detected, auto-compression is not performed. Use --recreateTar to update the submission")
409 return False
410 for root, _, files in os.walk(sourceDir):
411 for file in files:
412 file_path = os.path.join(root, file)
413 try:
414 if os.path.getmtime(file_path) > tarball_mtime:
415 logCPGridRun.info(f"File {file_path} is newer than the tarball.")
416 return True
417 except FileNotFoundError:
418 continue
419 return False
420

◆ _filesChangedOrTarballNotCreated()

CPGridRun.CPGridRun._filesChangedOrTarballNotCreated ( self)
protected

Definition at line 339 of file CPGridRun.py.

339 def _filesChangedOrTarballNotCreated(self):
340 return not self._tarballRecreated and (self.args.recreateTar or not os.path.exists(self._tarfile) or self._filesChanged())
341

◆ _hasCompressedTarball()

CPGridRun.CPGridRun._hasCompressedTarball ( self)
protected

Definition at line 342 of file CPGridRun.py.

342 def _hasCompressedTarball(self):
343 return os.path.exists(self._tarfile) or self._tarballRecreated
344

◆ _initRunscript()

CPGridRun.CPGridRun._initRunscript ( self)
protected

Definition at line 35 of file CPGridRun.py.

35 def _initRunscript(self):
36 if self._runscript is not None:
37 return self._runscript
38 elif isAthena:
39 from AnalysisAlgorithmsConfig.AthenaCPRunScript import AthenaCPRunScript
40 self._runscript = AthenaCPRunScript()
41 else:
42 from AnalysisAlgorithmsConfig.EventLoopCPRunScript import EventLoopCPRunScript
43 self._runscript = EventLoopCPRunScript()
44 return self._runscript
45

◆ _outputDSFormatter()

CPGridRun.CPGridRun._outputDSFormatter ( self,
name )
protected
{group/user}.{username}.{prefix}.{DSID}.{format}.{tags}.{suffix}

Definition at line 351 of file CPGridRun.py.

351 def _outputDSFormatter(self, name):
352 '''
353 {group/user}.{username}.{prefix}.{DSID}.{format}.{tags}.{suffix}
354 '''
355 nameParser = CPGridRun.atlasProductionNameParser(name)
356 base = 'group' if self.args.groupProduction else 'user'
357 username = self.args.gridUsername
358 dsid = nameParser['DSID']
359 tags = '_'.join(nameParser['tags'])
360 fileFormat = nameParser['format']
361 prefix = self.args.prefix if self.args.prefix else nameParser['main'].split('_')[0] # Dynamically set the prefix, likely to be something like PhPy8Eg
362 suffix = self._suffixFormatter()
363
364 result = [base, username, prefix, dsid, fileFormat, tags, suffix]
365 return ".".join(filter(None, result))
366
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:179

◆ _parseGridArguments()

CPGridRun.CPGridRun._parseGridArguments ( self)
protected

Definition at line 46 of file CPGridRun.py.

46 def _parseGridArguments(self):
47 parser = argparse.ArgumentParser(description='CPGrid runscript to submit CPRun.py jobs to the grid. '
48 'This script will submit a job to the grid using files in the input text one by one.'
49 'CPRun.py can handle multiple sources of input and create one output; but not this script',
50 add_help=False,
51 formatter_class=argparse.RawTextHelpFormatter)
52 parser.add_argument('-h', '--help', dest='help', action='store_true', help='Show this help message and continue')
53
54 ioGroup = parser.add_argument_group('Input/Output file configuration')
55 ioGroup.add_argument('-i','--input-list', dest='input_list', help='Path to the text file containing list of containers on the panda grid. Each container will be passed to prun as --inDS and is run individually')
56 ioGroup.add_argument('--output-files', dest='output_files', nargs='+', default=['output.root'],
57 help='The output files of the grid job. Example: --output-files A.root B.txt B.root results in A/A.root, B/B.txt, B/B.root in the output directory. No need to specify if using CPRun.py')
58 ioGroup.add_argument('--destSE', dest='destSE', default='', type=str, help='Destination storage element (PanDA)')
59 ioGroup.add_argument('--mergeType', dest='mergeType', default='Default', type=str, help='Output merging type, [None, Default, xAOD]')
60
61 pandaGroup = parser.add_argument_group('Input/Output naming configuration')
62 pandaGroup.add_argument('--gridUsername', dest='gridUsername', default=os.getenv('USER', ''), type=str, help='Grid username, or the groupname. Default is the current user. Only affect file naming')
63 pandaGroup.add_argument('--prefix', dest='prefix', default='', type=str, help='Prefix for the output directory. Dynamically set with input container if not provided')
64 pandaGroup.add_argument('--suffix', dest='suffix', default='',type=str, help='Suffix for the output directory')
65 pandaGroup.add_argument('--outDS', dest='outDS', default='', type=str,
66 help='Name of an output dataset. outDS will contain all output files (PanDA). If not provided, support dynamic naming if input name is in the Atlas production format or typical user production format')
67
68 cpgridGroup = parser.add_argument_group('CPGrid configuration')
69 cpgridGroup.add_argument('--groupProduction', dest='groupProduction', action='store_true', help='Only use for official production')
70
71 cpgridGroup.add_argument('--exec', dest='exec', type=str,
72 help='Executable line for the CPRun.py or custom script to run on the grid encapsulated in a double quote (PanDA)\n'
73 'Run CPRun.py with preset behavior including streamlined file i/o. E.g, "CPRun.py -t config.yaml --no-systematics".\n'
74 'Run custom script: "customRun.py -i inputs -o output --text-config config.yaml --flagA --flagB"\n'
75 )
76
77 submissionGroup = parser.add_argument_group('Submission configuration')
78 submissionGroup.add_argument('--noSubmit', dest='noSubmit', action='store_true', help='Do not submit the job to the grid (PanDA). Useful to inspect the prun command')
79 submissionGroup.add_argument('--testRun', dest='testRun', action='store_true', help='Will submit job to the grid but greatly limit the number of files per job (10) and number of events (300)')
80 submissionGroup.add_argument('--recreateTar', dest='recreateTar', action='store_true', help='Re-compress the source code. Source code are compressed by default in submission, this is useful when the source code is updated')
81 submissionGroup.add_argument('--useCentralPackage', dest='useCentralPackage', action='store_true', help='Use central package instead of custom packages')
82 submissionGroup.add_argument('--bulk-submission', dest='bulk_submission', action='store_true', help='Submit all containers in the input list as one task.')
83
84 miscGroup = parser.add_argument_group('Miscellaneous configuration')
85 miscGroup.add_argument('-y', '--agreeAll', dest='agreeAll', action='store_true', help='Agree to all the submission details without asking for confirmation. Use with caution!')
86 miscGroup.add_argument('--checkInputDS', dest='checkInputDS', action='store_true', help='Check if the input datasets are available on the AMI.')
87 miscGroup.add_argument('--framework', dest='framework', default='CPGridRun', type=str, help='Declaring a name for your submission for PanDA team to collect statistics. Default is CPGridRun')
88
89 self.args, self.unknown_args = parser.parse_known_args()
90 self.outputFilesParsing()
91 return parser
92

◆ _parseInputFileList()

tuple[list[str], list[str]] CPGridRun.CPGridRun._parseInputFileList ( Path path,
bool bulk_submission = False )
staticprotected

Definition at line 658 of file CPGridRun.py.

658 def _parseInputFileList(path: Path, bulk_submission: bool = False) -> tuple[list[str], list[str]]:
659 files = []
660 with path.open('r') as inputText:
661 for line in inputText.readlines():
662 # skip comments and empty lines
663 if line.strip().startswith("#") or not line.strip():
664 continue
665 files += line.split(",")
666 # remove leading/trailing whitespaces, and \n
667 files = [file.strip() for file in files]
668
669 # bulk submission
670 if bulk_submission:
671 if any((path.parent / file).exists() or (path.parent / f"{file}.txt").exists() for file in files):
672 files_bulk = []
673 names_bulk = []
674 for file in files:
675 file_path = path.parent / file
676 if not file_path.exists():
677 file_path = path.parent / f"{file}.txt"
678 if not file_path.exists():
679 logCPGridRun.error(f"File {file} or {file}.txt does not exist in the input list directory.")
680 raise FileNotFoundError(f"File {file} or {file}.txt does not exist in the input list directory.")
681 files_current, names_current = CPGridRun._parseInputFileList(file_path, bulk_submission=True)
682 files_bulk.extend(files_current)
683 names_bulk.extend(names_current)
684 return files_bulk, names_bulk
685 else:
686 return [','.join(files)], [path.stem.replace("+", "")]
687
688 return files, [None] * len(files)
689
bool exists(const std::string &filename)
does a file exist

◆ _prepareAmiQueryFromInputList()

CPGridRun.CPGridRun._prepareAmiQueryFromInputList ( self)
protected
Helper function to prepare a list of queries for the AMI based on the input list.
It will replace the _p### with _p% to match the latest ptag.

Definition at line 289 of file CPGridRun.py.

289 def _prepareAmiQueryFromInputList(self):
290 '''
291 Helper function to prepare a list of queries for the AMI based on the input list.
292 It will replace the _p### with _p% to match the latest ptag.
293 '''
294 import re
295 regex = re.compile("_p[0-9]+")
296 queries = []
297 datasetPtag = {}
298 for datasetName in self.cmd:
299 parsed = CPGridRun.atlasProductionNameParser(datasetName)
300 datasetPtag[datasetName] = parsed.get('ptag')
301 queries.append(regex.sub("_p%", datasetName))
302 return queries, datasetPtag
303

◆ _sourceDir()

CPGridRun.CPGridRun._sourceDir ( self)
protected

Definition at line 426 of file CPGridRun.py.

426 def _sourceDir(self):
427 cmakeCachePath = os.path.join(self._buildDir(), 'CMakeCache.txt')
428 sourceDir = None
429 if not os.path.exists(cmakeCachePath):
430 return sourceDir
431 with open(cmakeCachePath, 'r') as cmakeCache:
432 for line in cmakeCache:
433 if '_SOURCE_DIR:STATIC=' in line:
434 sourceDir = line.split('=')[1].strip()
435 break
436 return sourceDir
437

◆ _suffixFormatter()

CPGridRun.CPGridRun._suffixFormatter ( self)
protected

Definition at line 381 of file CPGridRun.py.

381 def _suffixFormatter(self):
382 if self.args.suffix:
383 return self.args.suffix
384 if self.args.testRun:
385 import uuid
386 return f"test_{uuid.uuid4().hex[:6]}"
387 else:
388 return ''
389

◆ _unknownArgsDict()

dict CPGridRun.CPGridRun._unknownArgsDict ( self)
protected
Cleans the unknown args by removing leading dashes and ensuring they are in key-value pairs

Definition at line 210 of file CPGridRun.py.

210 def _unknownArgsDict(self)->dict:
211 '''
212 Cleans the unknown args by removing leading dashes and ensuring they are in key-value pairs
213 '''
214 unknown_args_dict = {}
215 idx = 0
216 while idx < len(self.unknown_args):
217 if self.unknown_args[idx].startswith('-'):
218 if idx + 1 < len(self.unknown_args) and not self.unknown_args[idx + 1].startswith('-'):
219 unknown_args_dict[self.unknown_args[idx].lstrip('-')] = self.unknown_args[idx + 1]
220 idx += 2
221 else:
222 unknown_args_dict[self.unknown_args[idx].lstrip('-')] = True
223 idx += 1
224 return unknown_args_dict
225

◆ askSubmission()

CPGridRun.CPGridRun.askSubmission ( self)

Definition at line 704 of file CPGridRun.py.

704 def askSubmission(self):
705 if self.args.agreeAll:
706 logCPGridRun.info("You have agreed to all the submission details. Jobs will be submitted without confirmation.")
707 self.submit()
708 return
709 answer = input("Please confirm ALL the submission details are correct before submitting [y/n]: ")
710 if answer.lower() == 'y':
711 self.submit()
712 elif answer.lower() == 'n':
713 logCPGridRun.info("Feel free to report any unexpected behavior to the CPAlgorithms team!")
714 else:
715 logCPGridRun.error("Invalid input. Please enter 'y' or 'n'. Jobs are not submitted.")
716

◆ atlasProductionNameParser()

CPGridRun.CPGridRun.atlasProductionNameParser ( filename)
static
Parsing file name into a dictionary, an example is given here
mc20_13TeV.410470.PhPy8EG_A14_ttbar_hdamp258p75_nonallhad.deriv.DAOD_PHYS.e6337_s3681_r13167_p5855/DAOD_PHYS.34865530._000740.pool.root.1
For the first part
datasetName: mc20_13TeV.410470.PhPy8EG_A14_ttbar_hdamp258p75_nonallhad.deriv.DAOD_PHYS.e6337_s3681_r13167_p5855
projectName: mc20_13TeV
campaign: mc20
energy: 13 #(TeV)
DSID: 410470
main: PhPy8EG_A14_ttbar_hdamp258p75_nonallhad
TODO  generator: PhPy8Eg
TODO  tune: A14 # For Pythia8
TODO  process: ttbar
TODO  hdamp: 258p75 # For Powheg
TODO  decayType: nonallhad
step: deriv
format: DAOD_PHYS
tags: e###_s###_r###_p###_a###_t###_b#
etag: e6337 # EVNT (EVGEN) production and merging
stag: s3681 # Geant4 simulation to produce HITS and merging!
rtag: r13167 # Digitisation and reconstruction, as well as AOD merging
ptag: p5855 # Production of NTUP_PILEUP format and merging
atag: aXXX: atlfast configuration (both simulation and digit/recon)
ttag: tXXX: tag production configuration
btag: bXXX: bytestream production configuration

For the second part
JeditaskID: 34865530
fileNumber: 000740
version: 1

Definition at line 562 of file CPGridRun.py.

562 def atlasProductionNameParser(filename):
563 '''
564 Parsing file name into a dictionary, an example is given here
565 mc20_13TeV.410470.PhPy8EG_A14_ttbar_hdamp258p75_nonallhad.deriv.DAOD_PHYS.e6337_s3681_r13167_p5855/DAOD_PHYS.34865530._000740.pool.root.1
566 For the first part
567 datasetName: mc20_13TeV.410470.PhPy8EG_A14_ttbar_hdamp258p75_nonallhad.deriv.DAOD_PHYS.e6337_s3681_r13167_p5855
568 projectName: mc20_13TeV
569 campaign: mc20
570 energy: 13 #(TeV)
571 DSID: 410470
572 main: PhPy8EG_A14_ttbar_hdamp258p75_nonallhad
573 TODO generator: PhPy8Eg
574 TODO tune: A14 # For Pythia8
575 TODO process: ttbar
576 TODO hdamp: 258p75 # For Powheg
577 TODO decayType: nonallhad
578 step: deriv
579 format: DAOD_PHYS
580 tags: e###_s###_r###_p###_a###_t###_b#
581 etag: e6337 # EVNT (EVGEN) production and merging
582 stag: s3681 # Geant4 simulation to produce HITS and merging!
583 rtag: r13167 # Digitisation and reconstruction, as well as AOD merging
584 ptag: p5855 # Production of NTUP_PILEUP format and merging
585 atag: aXXX: atlfast configuration (both simulation and digit/recon)
586 ttag: tXXX: tag production configuration
587 btag: bXXX: bytestream production configuration
588
589 For the second part
590 JeditaskID: 34865530
591 fileNumber: 000740
592 version: 1
593
594 '''
595 result = {}
596 #split the / in case
597 # mc20_13TeV.410470.PhPy8EG_A14_ttbar_hdamp258p75_nonallhad.deriv.DAOD_PHYS.e6337_s3681_r13167_p5855
598 # /DAOD_PHYS.34865530._000740.pool.root.1
599 if '/' in filename:
600 datasetPart, filePart = filename.split('/')
601 else:
602 datasetPart = filename
603 filePart = None
604
605 # Remove the scope
606 if ':' in datasetPart:
607 datasetPart = datasetPart.split(':')[1]
608
609 # Do not try to parse user datasets
610 if datasetPart.startswith('user') or datasetPart.startswith('group'):
611 result['datasetName'] = datasetPart
612 return result
613
614 # Split the dataset part by dots
615 datasetParts = datasetPart.split('.')
616 result['datasetName'] = datasetPart
617 # Extract the first part
618 result['projectName'] = datasetParts[0] # is positional
619 # Extract the campaign and energy
620 campaign_energy = result['projectName'].split('_')
621 result['campaign'] = campaign_energy[0]
622 result['energy'] = campaign_energy[1]
623
624 # Extract the DSID, positional
625 result['DSID'] = datasetParts[1]
626 result['main'] = datasetParts[2]
627 result['step'] = datasetParts[3]
628 result['format'] = datasetParts[4]
629
630 # Extract the tags (etag, stag, rtag, ptag)
631 tags = datasetParts[5].split('_')
632 result['tags'] = tags
633 for tag in tags:
634 if tag.startswith('e'):
635 result['etag'] = tag
636 elif tag.startswith('s'):
637 result['stag'] = tag
638 elif tag.startswith('r'):
639 result['rtag'] = tag
640 elif tag.startswith('p'):
641 result['ptag'] = tag
642 elif tag.startswith('a'):
643 result['atag'] = tag
644 elif tag.startswith('t'):
645 result['ttag'] = tag
646 elif tag.startswith('b'):
647 result['btag'] = tag
648
649 # Extract the file part if it exists
650 if filePart:
651 fileParts = filePart.split('.')
652 result['jediTaskID'] = fileParts[1]
653 result['fileNumber'] = fileParts[2]
654 result['version'] = fileParts[-1]
655 return result
656

◆ checkExternalTools()

CPGridRun.CPGridRun.checkExternalTools ( self)

Definition at line 699 of file CPGridRun.py.

699 def checkExternalTools(self):
700 self.hasPrun()
701 if self.args.checkInputDS:
702 self.checkInputInPyami()
703

◆ checkInputInPyami()

bool CPGridRun.CPGridRun.checkInputInPyami ( self)

Definition at line 272 of file CPGridRun.py.

272 def checkInputInPyami(self) -> bool:
273 if not self.hasPyami():
274 return False
275
276 client = pyAMI.client.Client('atlas')
277 pyAMI.atlas.api.init()
278
279 queries, datasetPtag = self._prepareAmiQueryFromInputList()
280 try:
281 results = pyAMI.atlas.api.list_datasets(client, patterns=queries)
282 except pyAMI.exception.Error:
283 self._errorCollector['no valid certificate'] = (
284 "Cannot query AMI, please run 'voms-proxy-init -voms atlas' and ensure your certificate is valid.")
285 return False
286
287 return self._analyzeAmiResults(results, datasetPtag)
288

◆ configureSubmission()

CPGridRun.CPGridRun.configureSubmission ( self)

Definition at line 147 of file CPGridRun.py.

147 def configureSubmission(self):
148 for input, name in zip(self.inputList, self._inputNames):
149 cmd, config = self.configureSubmissionSingleSample(input, name)
150 self.cmd[input] = cmd
151 self.outputs[input] = config["outDS"]
152 self._isFirstRun = False
153

◆ configureSubmissionSingleSample()

CPGridRun.CPGridRun.configureSubmissionSingleSample ( self,
input,
name )

Definition at line 154 of file CPGridRun.py.

154 def configureSubmissionSingleSample(self, input, name):
155 config = {
156 'inDS': input,
157 'outDS': self.args.outDS if self.args.outDS else self.outputDSFormatter(input, name),
158 'cmtConfig': os.environ["CMTCONFIG"],
159 'writeInputToTxt': 'IN:in.txt',
160 'outputs': self.outputsFormatter(),
161 'exec': self.execFormatter(),
162 'memory': "2000", # MB
163 'addNthFieldOfInDSToLFN': '2,3,6',
164 'framework': self.args.framework,
165 }
166 if self.args.noSubmit:
167 config['noSubmit'] = True
168
169 if self.args.mergeType == 'xAOD':
170 config['mergeScript'] = 'xAODMerge %OUT `echo %IN | sed \'s/,/ /g\'`'
171
172 if self.args.mergeType != 'None':
173 config['mergeOutput'] = True
174
175 # Three types of files sending the grid
176 if self.args.useCentralPackage: # 1. Using central package and have a yaml file only
177 config['extFile'] = self._yamlPath
178 config['noBuild'] = True
179 config['noCompile'] = True
180 config['athenaTag'] = f"AnalysisBase,{os.environ['AnalysisBase_VERSION']}"
181 elif self._filesChangedOrTarballNotCreated(): # 2. Using custom packages and haven't compressed the tarball since the last changes
182 config['outTarBall'] = self._tarfile
183 config['useAthenaPackages'] = True
184 self._tarballRecreated = True
185 elif self._hasCompressedTarball(): # 3. Using custom packages and have compressed the tarball
186 config['inTarBall'] = self._tarfile
187 config['useAthenaPackages'] = True
188
189 if self.args.groupProduction:
190 config['official'] = True
191 config['voms'] = f'atlas:/atlas/{self.args.gridUsername}/Role=production'
192
193 if self.args.destSE:
194 config['destSE'] = self.args.destSE
195
196 if self.args.testRun:
197 config['nEventsPerFile'] = 100
198 config['nFiles'] = 5
199 config.update(self.prunArgsDict)
200 cmd = 'prun \\\n'
201 for k, v in config.items():
202 if isinstance(v, bool) and v:
203 cmd += f'--{k} \\\n'
204 elif v is not None and v != '':
205 # the exec string is already quoted by execFormatter
206 value = v if k == 'exec' else shlex.quote(str(v))
207 cmd += f'--{k} {value} \\\n'
208 return cmd.rstrip(' \\\n'), config
209

◆ execFormatter()

CPGridRun.CPGridRun.execFormatter ( self)

Definition at line 438 of file CPGridRun.py.

438 def execFormatter(self):
439 if not self.args.exec:
440 raise ValueError('No exec command provided, use --exec to specify the command to run on the grid')
441
442 # Check if the execution command starts with 'CPRun.py' or '-'
443 isCPRunDefault = self.args.exec.startswith('-') or self.args.exec.startswith('CPRun.py')
444 formatingClause = {
445 'input_list': 'in.txt',
446 'merge_output_files': len(self.args.output_files) == 1,
447 }
448 if not isCPRunDefault:
449 if self._isFirstRun: logCPGridRun.warning("Non-CPRun.py is detected, please ensure the exec string is formatted correctly. Exec string will not be automatically formatted.")
450 return f'"{self.args.exec}"'
451
452 # Parse the exec string using the parser to validate and extract known arguments
453 self._initRunscript()
454 runscriptArgs, unknownArgs = self._runscript.parser.parse_known_args(self.args.exec.split(' '))
455
456 # Throw error if unknownArgs contains any --args
457 unknown_flags = [arg for arg in unknownArgs if arg.startswith('--')]
458 if unknown_flags:
459 logCPGridRun.error(f"Unknown flags detected in the exec string: {unknown_flags}. Please check the exec string.")
460 raise ValueError(f"Unknown arguments detected: {unknown_flags}")
461
462 # Only override if value is None or the parser default
463 for key, value in formatingClause.items():
464 if hasattr(runscriptArgs, key):
465 old_value = getattr(runscriptArgs, key)
466 if old_value is None or old_value == self._runscript.parser.get_default(key):
467 setattr(runscriptArgs, key, value)
468 if self._isFirstRun: logCPGridRun.info(f"Setting '{key}' to '{value}' (CPRun.py default is: '{old_value}')")
469 else:
470 if self._isFirstRun: logCPGridRun.warning(f"Preserving user-defined '{key}': '{old_value}', default formatting '{value}' will not be applied.")
471 else:
472 logCPGridRun.error(f"Formatting clause '{key}' is not recognized in the CPRun.py script. Check CPGridRun.py")
473 raise ValueError(f"Formatting clause '{key}' is not recognized in the CPRun.py script. Check CPGridRun.py")
474 self._checkYamlExists(runscriptArgs)
475 # Return the formatted arguments as a string
476 arg_string = ' '.join(
477 f'--{k.replace("_", "-")}' if isinstance(v, bool) and v else
478 f'--{k.replace("_", "-")} {v}' for k, v in vars(runscriptArgs).items() if v not in [None, False]
479 )
480 return f'"CPRun.py {arg_string}"'
481

◆ getParser()

CPGridRun.CPGridRun.getParser ( self)

Definition at line 142 of file CPGridRun.py.

142 def getParser(self):
143 return self.gridParser
144

◆ hasPrun()

bool CPGridRun.CPGridRun.hasPrun ( self)

Definition at line 515 of file CPGridRun.py.

515 def hasPrun(self) -> bool:
516 import shutil
517 prun_path = shutil.which("prun")
518 if prun_path is None:
519 self._errorCollector['no prun'] = (
520 "The 'prun' command is not found. If you are on lxplus, please run the following commands:\n\n"
521 "```\n"
522 "lsetup panda\n"
523 "voms-proxy-init -voms atlas\n"
524 "```\n"
525 "Make sure you have a valid certificate."
526 )
527 return False
528 return True
529

◆ hasPyami()

CPGridRun.CPGridRun.hasPyami ( self)

Definition at line 256 of file CPGridRun.py.

256 def hasPyami(self):
257 try:
258 global pyAMI
259 import pyAMI.client
260 import pyAMI.atlas.api
261 except ModuleNotFoundError:
262 self._errorCollector['no AMI'] = (
263 "Cannot import pyAMI, please run the following commands:\n\n"
264 "```\n"
265 "lsetup pyami\n"
266 "voms-proxy-init -voms atlas\n"
267 "```\n"
268 "and make sure you have a valid certificate.")
269 return False
270 return True
271

◆ inputList()

CPGridRun.CPGridRun.inputList ( self)

Definition at line 108 of file CPGridRun.py.

108 def inputList(self):
109 if not self.args.input_list:
110 raise ValueError('No input list provided, use --input-list to specify the input containers')
111 if self._inputList is None:
112 input_list_path = Path(self.args.input_list)
113 if input_list_path.exists():
114 if input_list_path.suffix == '.txt':
115 self._inputList, self._inputNames = CPGridRun._parseInputFileList(input_list_path, self.args.bulk_submission)
116 elif input_list_path.suffix == '.json':
117 raise NotImplementedError('JSON input list parsing is not implemented')
118 else:
119 raise ValueError('Unsupported input list format, only .txt files are supported.')
120 elif CPGridRun.isAtlasProductionFormat(self.args.input_list):
121 self._inputList = [self.args.input_list]
122 self._inputNames = [None]
123 else:
124 raise ValueError('use --input-list to specify input containers')
125 return self._inputList
126

◆ isAtlasProductionFormat()

CPGridRun.CPGridRun.isAtlasProductionFormat ( name)
static

Definition at line 538 of file CPGridRun.py.

538 def isAtlasProductionFormat(name):
539 if ":" in name:
540 name = name.split(":")[1]
541
542 if name.startswith("mc") or name.startswith("data"):
543 return True
544
545 logCPGridRun.warning("Name is not in the Atlas production format, assuming it is a user production")
546 return False
547

◆ outputDSFormatter()

CPGridRun.CPGridRun.outputDSFormatter ( self,
name,
label )

Definition at line 345 of file CPGridRun.py.

345 def outputDSFormatter(self, name, label):
346 if CPGridRun.isAtlasProductionFormat(name) and not label:
347 return self._outputDSFormatter(name)
348 else:
349 return self._customOutputDSFormatter(name, label)
350

◆ outputFilesParsing()

CPGridRun.CPGridRun.outputFilesParsing ( self)

Definition at line 127 of file CPGridRun.py.

127 def outputFilesParsing(self):
128 output_files = []
129 for output in self.args.output_files:
130 if ',' in output:
131 output_files.extend(output.split(','))
132 else:
133 output_files.append(output)
134 self.output_files = output_files
135

◆ outputsFormatter()

CPGridRun.CPGridRun.outputsFormatter ( self)

Definition at line 511 of file CPGridRun.py.

511 def outputsFormatter(self):
512 outputs = [f'{output.split(".")[0]}:{output}' if ":" not in output else output for output in self.args.output_files]
513 return ','.join(outputs)
514

◆ printDelayedErrorCollection()

CPGridRun.CPGridRun.printDelayedErrorCollection ( self)

Definition at line 690 of file CPGridRun.py.

690 def printDelayedErrorCollection(self):
691 if self._errorCollector:
692 logCPGridRun.error("Errors were collected during the script execution:")
693
694 for key, value in self._errorCollector.items():
695 logCPGridRun.error(f"{key}: {value}")
696 logCPGridRun.error("Please fix the errors and try again.")
697 sys.exit(1)
698

◆ printHelp()

CPGridRun.CPGridRun.printHelp ( self)

Definition at line 136 of file CPGridRun.py.

136 def printHelp(self):
137 self.gridParser.print_help()
138 logCPGridRun.info("\033[92m\n If you are using CPRun.py, the following flags are for the CPRun.py in this framework\033[0m")
139 self._runscript.parser.usage = argparse.SUPPRESS
140 self._runscript.parser.print_help()
141
void printHelp()

◆ printInputDetails()

CPGridRun.CPGridRun.printInputDetails ( self)

Definition at line 246 of file CPGridRun.py.

246 def printInputDetails(self):
247 for key, cmd in self.cmd.items():
248 parsed_name = CPGridRun.atlasProductionNameParser(key)
249 logCPGridRun.info("\n"
250 f"Input: {key}\n" +
251 "\n".join([f" {k.replace('_', ' ').title()}: {v}" for k, v in parsed_name.items()]))
252 logCPGridRun.info(f"Command: \n{cmd}")
253 print("-" * 70)
254 # Add your submission logic here
255
void print(char *figname, TCanvas *c1)

◆ rucioCustomNameParser()

CPGridRun.CPGridRun.rucioCustomNameParser ( filename)
static
The custom name has many variations, but most of them follow user/group.username.datasetname.suffix

Definition at line 549 of file CPGridRun.py.

549 def rucioCustomNameParser(filename):
550 '''
551 The custom name has many variations, but most of them follow user/group.username.datasetname.suffix
552 '''
553 result = {}
554 parts = filename.split('.')
555 result['userType'] = parts[0]
556 result['username'] = parts[1]
557 result['main'] = parts[2]
558 result['suffix'] = parts[-1]
559 return result
560

◆ submit()

CPGridRun.CPGridRun.submit ( self)

Definition at line 530 of file CPGridRun.py.

530 def submit(self):
531 import subprocess
532 for key, cmd in self.cmd.items():
533 logCPGridRun.info(f"Submitting: {self.outputs[key]}")
534 process = subprocess.Popen(cmd, shell=True, stdout=sys.stdout, stderr=sys.stderr)
535 process.communicate()
536
static Status::Enum submit(SH::Sample *const sample, const bool isFirstSample)

Member Data Documentation

◆ _errorCollector

dict CPGridRun.CPGridRun._errorCollector = {}
protected

Definition at line 16 of file CPGridRun.py.

◆ _inputList

CPGridRun.CPGridRun._inputList = None
protected

Definition at line 21 of file CPGridRun.py.

◆ _inputNames

CPGridRun.CPGridRun._inputNames = None
protected

Definition at line 22 of file CPGridRun.py.

◆ _isFirstRun

bool CPGridRun.CPGridRun._isFirstRun = True
protected

Definition at line 19 of file CPGridRun.py.

◆ _runscript

CPGridRun.CPGridRun._runscript = None
protected

Definition at line 17 of file CPGridRun.py.

◆ _tarballRecreated

bool CPGridRun.CPGridRun._tarballRecreated = False
protected

Definition at line 20 of file CPGridRun.py.

◆ _tarfile

CPGridRun.CPGridRun._tarfile = 'cpgrid.tar.gz'
protected

Definition at line 18 of file CPGridRun.py.

◆ _yamlPath

CPGridRun.CPGridRun._yamlPath = None
protected

Definition at line 23 of file CPGridRun.py.

◆ args

CPGridRun.CPGridRun.args

Definition at line 89 of file CPGridRun.py.

◆ cmd

dict CPGridRun.CPGridRun.cmd = {}

Definition at line 24 of file CPGridRun.py.

◆ gridParser

CPGridRun.CPGridRun.gridParser = self._parseGridArguments()

Definition at line 27 of file CPGridRun.py.

◆ inputList

CPGridRun.CPGridRun.inputList

Definition at line 148 of file CPGridRun.py.

◆ output_files

CPGridRun.CPGridRun.output_files = output_files

Definition at line 134 of file CPGridRun.py.

◆ outputs

dict CPGridRun.CPGridRun.outputs = {}

Definition at line 25 of file CPGridRun.py.

◆ prunArgsDict

CPGridRun.CPGridRun.prunArgsDict = self._createPrunArgsDict()

Definition at line 28 of file CPGridRun.py.

◆ unknown_args

CPGridRun.CPGridRun.unknown_args = parser.parse_known_args()

Definition at line 89 of file CPGridRun.py.


The documentation for this class was generated from the following file: