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 13 of file CPGridRun.py.

Constructor & Destructor Documentation

◆ __init__()

CPGridRun.CPGridRun.__init__ ( self)

Definition at line 14 of file CPGridRun.py.

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

Member Function Documentation

◆ _analyzeAmiResults()

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

Definition at line 301 of file CPGridRun.py.

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

◆ _buildDir()

CPGridRun.CPGridRun._buildDir ( self)
protected

Definition at line 419 of file CPGridRun.py.

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

◆ _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 223 of file CPGridRun.py.

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

◆ _checkYamlExists()

CPGridRun.CPGridRun._checkYamlExists ( self,
runscriptArgs )
protected

Definition at line 480 of file CPGridRun.py.

480 def _checkYamlExists(self, runscriptArgs):
481 from AnalysisAlgorithmsConfig.CPBaseRunner import CPBaseRunner
482 if not hasattr(runscriptArgs, 'text_config'):
483 self._errorCollector['no yaml'] = "No YAML configuration file is specified in the exec string. Please provide one using --text-config"
484 return
485 yamlPath = getattr(runscriptArgs, 'text_config')
486 self._yamlPath = yamlPath
487 haveLocalYaml = CPBaseRunner.findLocalPathYamlConfig(yamlPath)
488 if haveLocalYaml:
489 logCPGridRun.warning("A path to a local YAML configuration file is found, but it may not be grid-usable.")
490
491 repoYamls, _ = CPBaseRunner.findRepoPathYamlConfig(yamlPath)
492 if repoYamls and len(repoYamls) > 1:
493 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)
494 return
495 elif repoYamls and len(repoYamls) == 1:
496 logCPGridRun.info(f"Found a grid-usable YAML configuration file in the analysis repository: {repoYamls[0]}")
497 return
498
499 if haveLocalYaml and self.args.useCentralPackage:
500 logCPGridRun.warning("A path to a local YAML configuration file is found, no custom packages are found, proceed with /cvmfs packages only.")
501
502 if not repoYamls and not self.args.useCentralPackage:
503 self._errorCollector['no usable yaml'] = f"Grid usable YAML configuration file not found: {yamlPath}"
504 if haveLocalYaml:
505 self._errorCollector['have local yaml'] = f"Only a local YAML configuration file is found: {yamlPath}, not usable in the grid.\n" \
506 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"\
507 f"Or if you are only using central packages, please use the `--useCentralPackage` flag."
508

◆ _createPrunArgsDict()

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

Definition at line 92 of file CPGridRun.py.

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

◆ _customOutputDSFormatter()

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

Definition at line 365 of file CPGridRun.py.

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

◆ _filesChanged()

CPGridRun.CPGridRun._filesChanged ( self)
protected

Definition at line 388 of file CPGridRun.py.

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

◆ _filesChangedOrTarballNotCreated()

CPGridRun.CPGridRun._filesChangedOrTarballNotCreated ( self)
protected

Definition at line 336 of file CPGridRun.py.

336 def _filesChangedOrTarballNotCreated(self):
337 return not self._tarballRecreated and (self.args.recreateTar or not os.path.exists(self._tarfile) or self._filesChanged())
338

◆ _hasCompressedTarball()

CPGridRun.CPGridRun._hasCompressedTarball ( self)
protected

Definition at line 339 of file CPGridRun.py.

339 def _hasCompressedTarball(self):
340 return os.path.exists(self._tarfile) or self._tarballRecreated
341

◆ _initRunscript()

CPGridRun.CPGridRun._initRunscript ( self)
protected

Definition at line 34 of file CPGridRun.py.

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

◆ _outputDSFormatter()

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

Definition at line 348 of file CPGridRun.py.

348 def _outputDSFormatter(self, name):
349 '''
350 {group/user}.{username}.{prefix}.{DSID}.{format}.{tags}.{suffix}
351 '''
352 nameParser = CPGridRun.atlasProductionNameParser(name)
353 base = 'group' if self.args.groupProduction else 'user'
354 username = self.args.gridUsername
355 dsid = nameParser['DSID']
356 tags = '_'.join(nameParser['tags'])
357 fileFormat = nameParser['format']
358 base = 'group' if self.args.groupProduction else 'user'
359 prefix = self.args.prefix if self.args.prefix else nameParser['main'].split('_')[0] # Dynamically set the prefix, likely to be something like PhPy8Eg
360 suffix = self._suffixFormatter()
361
362 result = [base, username, prefix, dsid, fileFormat, tags, suffix]
363 return ".".join(filter(None, result))
364
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 45 of file CPGridRun.py.

45 def _parseGridArguments(self):
46 parser = argparse.ArgumentParser(description='CPGrid runscript to submit CPRun.py jobs to the grid. '
47 'This script will submit a job to the grid using files in the input text one by one.'
48 'CPRun.py can handle multiple sources of input and create one output; but not this script',
49 add_help=False,
50 formatter_class=argparse.RawTextHelpFormatter)
51 parser.add_argument('-h', '--help', dest='help', action='store_true', help='Show this help message and continue')
52
53 ioGroup = parser.add_argument_group('Input/Output file configuration')
54 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')
55 ioGroup.add_argument('--output-files', dest='output_files', nargs='+', default=['output.root'],
56 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')
57 ioGroup.add_argument('--destSE', dest='destSE', default='', type=str, help='Destination storage element (PanDA)')
58 ioGroup.add_argument('--mergeType', dest='mergeType', default='Default', type=str, help='Output merging type, [None, Default, xAOD]')
59
60 pandaGroup = parser.add_argument_group('Input/Output naming configuration')
61 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')
62 pandaGroup.add_argument('--prefix', dest='prefix', default='', type=str, help='Prefix for the output directory. Dynamically set with input container if not provided')
63 pandaGroup.add_argument('--suffix', dest='suffix', default='',type=str, help='Suffix for the output directory')
64 pandaGroup.add_argument('--outDS', dest='outDS', default='', type=str,
65 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')
66
67 cpgridGroup = parser.add_argument_group('CPGrid configuration')
68 cpgridGroup.add_argument('--groupProduction', dest='groupProduction', action='store_true', help='Only use for official production')
69
70 cpgridGroup.add_argument('--exec', dest='exec', type=str,
71 help='Executable line for the CPRun.py or custom script to run on the grid encapsulated in a double quote (PanDA)\n'
72 'Run CPRun.py with preset behavior including streamlined file i/o. E.g, "CPRun.py -t config.yaml --no-systematics".\n'
73 'Run custom script: "customRun.py -i inputs -o output --text-config config.yaml --flagA --flagB"\n'
74 )
75
76 submissionGroup = parser.add_argument_group('Submission configuration')
77 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')
78 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)')
79 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')
80 submissionGroup.add_argument('--useCentralPackage', dest='useCentralPackage', action='store_true', help='Use central package instead of custom packages')
81 submissionGroup.add_argument('--bulk-submission', dest='bulk_submission', action='store_true', help='Submit all containers in the input list as one task.')
82
83 miscGroup = parser.add_argument_group('Miscellaneous configuration')
84 miscGroup.add_argument('-y', '--agreeAll', dest='agreeAll', action='store_true', help='Agree to all the submission details without asking for confirmation. Use with caution!')
85 miscGroup.add_argument('--checkInputDS', dest='checkInputDS', action='store_true', help='Check if the input datasets are available on the AMI.')
86 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')
87
88 self.args, self.unknown_args = parser.parse_known_args()
89 self.outputFilesParsing()
90 return parser
91

◆ _parseInputFileList()

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

Definition at line 656 of file CPGridRun.py.

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

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

◆ _sourceDir()

CPGridRun.CPGridRun._sourceDir ( self)
protected

Definition at line 424 of file CPGridRun.py.

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

◆ _suffixFormatter()

CPGridRun.CPGridRun._suffixFormatter ( self)
protected

Definition at line 379 of file CPGridRun.py.

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

◆ _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 207 of file CPGridRun.py.

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

◆ askSubmission()

CPGridRun.CPGridRun.askSubmission ( self)

Definition at line 702 of file CPGridRun.py.

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

◆ 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 560 of file CPGridRun.py.

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

◆ checkExternalTools()

CPGridRun.CPGridRun.checkExternalTools ( self)

Definition at line 697 of file CPGridRun.py.

697 def checkExternalTools(self):
698 self.hasPrun()
699 if self.args.checkInputDS:
700 self.checkInputInPyami()
701

◆ checkInputInPyami()

bool CPGridRun.CPGridRun.checkInputInPyami ( self)

Definition at line 269 of file CPGridRun.py.

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

◆ configureSubmission()

CPGridRun.CPGridRun.configureSubmission ( self)

Definition at line 146 of file CPGridRun.py.

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

◆ configureSubmissionSingleSample()

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

Definition at line 153 of file CPGridRun.py.

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

◆ execFormatter()

CPGridRun.CPGridRun.execFormatter ( self)

Definition at line 436 of file CPGridRun.py.

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

◆ getParser()

CPGridRun.CPGridRun.getParser ( self)

Definition at line 141 of file CPGridRun.py.

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

◆ hasPrun()

bool CPGridRun.CPGridRun.hasPrun ( self)

Definition at line 513 of file CPGridRun.py.

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

◆ hasPyami()

CPGridRun.CPGridRun.hasPyami ( self)

Definition at line 253 of file CPGridRun.py.

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

◆ inputList()

CPGridRun.CPGridRun.inputList ( self)

Definition at line 107 of file CPGridRun.py.

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

◆ isAtlasProductionFormat()

CPGridRun.CPGridRun.isAtlasProductionFormat ( name)
static

Definition at line 536 of file CPGridRun.py.

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

◆ outputDSFormatter()

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

Definition at line 342 of file CPGridRun.py.

342 def outputDSFormatter(self, name, label):
343 if CPGridRun.isAtlasProductionFormat(name) and not label:
344 return self._outputDSFormatter(name)
345 else:
346 return self._customOutputDSFormatter(name, label)
347

◆ outputFilesParsing()

CPGridRun.CPGridRun.outputFilesParsing ( self)

Definition at line 126 of file CPGridRun.py.

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

◆ outputsFormatter()

CPGridRun.CPGridRun.outputsFormatter ( self)

Definition at line 509 of file CPGridRun.py.

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

◆ printDelayedErrorCollection()

CPGridRun.CPGridRun.printDelayedErrorCollection ( self)

Definition at line 688 of file CPGridRun.py.

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

◆ printHelp()

CPGridRun.CPGridRun.printHelp ( self)

Definition at line 135 of file CPGridRun.py.

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

◆ printInputDetails()

CPGridRun.CPGridRun.printInputDetails ( self)

Definition at line 243 of file CPGridRun.py.

243 def printInputDetails(self):
244 for key, cmd in self.cmd.items():
245 parsed_name = CPGridRun.atlasProductionNameParser(key)
246 logCPGridRun.info("\n"
247 f"Input: {key}\n" +
248 "\n".join([f" {k.replace('_', ' ').title()}: {v}" for k, v in parsed_name.items()]))
249 logCPGridRun.info(f"Command: \n{cmd}")
250 print("-" * 70)
251 # Add your submission logic here
252
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 547 of file CPGridRun.py.

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

◆ submit()

CPGridRun.CPGridRun.submit ( self)

Definition at line 528 of file CPGridRun.py.

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

Member Data Documentation

◆ _errorCollector

dict CPGridRun.CPGridRun._errorCollector = {}
protected

Definition at line 15 of file CPGridRun.py.

◆ _inputList

CPGridRun.CPGridRun._inputList = None
protected

Definition at line 20 of file CPGridRun.py.

◆ _inputNames

CPGridRun.CPGridRun._inputNames = None
protected

Definition at line 21 of file CPGridRun.py.

◆ _isFirstRun

bool CPGridRun.CPGridRun._isFirstRun = True
protected

Definition at line 18 of file CPGridRun.py.

◆ _runscript

CPGridRun.CPGridRun._runscript = None
protected

Definition at line 16 of file CPGridRun.py.

◆ _tarballRecreated

bool CPGridRun.CPGridRun._tarballRecreated = False
protected

Definition at line 19 of file CPGridRun.py.

◆ _tarfile

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

Definition at line 17 of file CPGridRun.py.

◆ _yamlPath

CPGridRun.CPGridRun._yamlPath = None
protected

Definition at line 22 of file CPGridRun.py.

◆ args

CPGridRun.CPGridRun.args

Definition at line 88 of file CPGridRun.py.

◆ cmd

dict CPGridRun.CPGridRun.cmd = {}

Definition at line 23 of file CPGridRun.py.

◆ gridParser

CPGridRun.CPGridRun.gridParser = self._parseGridArguments()

Definition at line 26 of file CPGridRun.py.

◆ inputList

CPGridRun.CPGridRun.inputList

Definition at line 147 of file CPGridRun.py.

◆ output_files

CPGridRun.CPGridRun.output_files = output_files

Definition at line 133 of file CPGridRun.py.

◆ outputs

dict CPGridRun.CPGridRun.outputs = {}

Definition at line 24 of file CPGridRun.py.

◆ prunArgsDict

CPGridRun.CPGridRun.prunArgsDict = self._createPrunArgsDict()

Definition at line 27 of file CPGridRun.py.

◆ unknown_args

CPGridRun.CPGridRun.unknown_args = parser.parse_known_args()

Definition at line 88 of file CPGridRun.py.


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