ATLAS Offline Software
CPBaseRunner.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2 
3 import argparse
4 from AnaAlgorithm.Logging import logging
5 from abc import ABC, abstractmethod
6 import os
7 
8 class CPBaseRunner(ABC):
9  def __init__(self):
10  self.logger = logging.getLogger("CPBaseRunner")
11  self._args = None
12  self._inputList = None
14  # parse the arguments here is a bad idea
15 
16  @property
17  def args(self):
18  if self._args is None:
19  self._args = self.parser.parse_args()
20  return self._args
21 
22  @property
23  def inputList(self):
24  if self._inputList is None:
25  if self.args.input_list.endswith('.txt'):
26  self._inputList = CPBaseRunner._parseInputFileList(self.args.input_list)
27  elif ".root" in self.args.input_list:
28  self._inputList = [self.args.input_list]
29  else:
30  raise FileNotFoundError(f'Input file list \"{self.args.input_list}\" is not supported!'
31  'Please provide a text file with a list of input files or a single root file.')
32  self.logger.info("Initialized input files: %s", self._inputList)
33  return self._inputList
34 
35  @property
36  def outputName(self):
37  if self.args.output_name.endswith('.root'):
38  return self.args.output_name[:-5]
39  else:
40  return self.args.output_name
41 
42  def printFlags(self):
43  self.logger.info("="*73)
44  self.logger.info("="*20 + "FLAG CONFIGURATION" + "="*20)
45  self.logger.info("="*73)
46  self.logger.info(" Input files: %s", self.flags.Input.isMC)
47  self.logger.info(" RunNumber: %s", self.flags.Input.RunNumbers)
48  self.logger.info(" MCCampaign: %s", self.flags.Input.MCCampaign)
49  self.logger.info(" GeneratorInfo: %s", self.flags.Input.GeneratorsInfo)
50  self.logger.info(" MaxEvents: %s", self.flags.Exec.MaxEvents)
51  self.logger.info(" SkipEvents: %s", self.flags.Exec.SkipEvents)
52  self.logger.info("="*73)
53 
54  @abstractmethod
55  def addCustomArguments(self):
56  pass
57 
58  @abstractmethod
59  def makeAlgSequence(self):
60  pass
61 
62  @abstractmethod
63  def run(self):
64  pass
65 
66  # The responsiblity of flag.lock will pass to the caller
68  from AthenaConfiguration.AllConfigFlags import initConfigFlags
69  flags = initConfigFlags()
70  flags.Input.Files = self.inputList
71  flags.Exec.MaxEvents = self.args.max_events
72  flags.Exec.SkipEvents = self.args.skip_n_events
73  return flags
74 
76  parser = argparse.ArgumentParser(
77  description='Runscript for CP Algorithm unit tests')
78  baseGroup = parser.add_argument_group('Base Script Options')
79  baseGroup.add_argument('-i', '--input-list', dest='input_list',
80  help='path to text file containing list of input files, or a single root file')
81  baseGroup.add_argument('-o','--output-name', dest='output_name', default='output',
82  help='output name of the analysis root file')
83  baseGroup.add_argument('-e', '--max-events', dest='max_events', type=int, default=-1,
84  help='Number of events to run')
85  baseGroup.add_argument('-t', '--text-config', dest='text_config',
86  help='path to the YAML configuration file. Tips: use atlas_install_data(path/to/*.yaml) in CMakeLists.txt can help locating the config just by the config file name.')
87  baseGroup.add_argument('--no-systematics', dest='no_systematics',
88  action='store_true', help='Disable systematics')
89  baseGroup.add_argument('--skip-n-events', dest='skip_n_events', type=int, default=0,
90  help='Skip the first N events in the run, not first N events for each file. This is meant for debugging only. \nIn Eventloop, this option disable the cutbookkeeper algorithms due to technical reasons, and can only be ran in direct-driver.')
91  return parser
92 
93  def _readYamlConfig(self):
94  yamlconfig = self._findYamlConfig(local=True)
95  if yamlconfig is None:
96  raise FileNotFoundError(f'Failed to locate \"{self.args.text_config}\" config file!'
97  'Check if you have a typo in -t/--text-config argument or missing file in the analysis configuration sub-directory.')
98  self.logger.info(f"Found YAML config at: {yamlconfig}")
99  self.logger.info("Setting up configuration based on YAML config:")
100  from AnalysisAlgorithmsConfig.ConfigText import TextConfig
101  config = TextConfig(yamlconfig)
102  return config
103 
104 
105  def _findYamlConfig(self, local=True):
106  # Find local and abs path first
107  if local and ((yamlConfig := CPBaseRunner.findLocalPathYamlConfig(self.args.text_config)) is not None):
108  return yamlConfig
109  # Then search in the analysis repository and warn for duplicates
110  elif (yamlConfig := CPBaseRunner.findRepoPathYamlConfig(self.args.text_config)):
111  if len(yamlConfig) > 1:
112  raise FileExistsError(f'Multiple files named \"{self.args.text_config}\" found in the analysis repository. Please provide a more specific path to the config file.\nMatches found:\n' + '\n'.join(yamlConfig))
113  else:
114  return yamlConfig[0]
115  # Finally try the slowest method using AthenaCommon
116  else:
117  from AthenaCommon.Utils.unixtools import find_datafile
118  return find_datafile(self.args.text_config)
119 
120  @staticmethod
121  def findLocalPathYamlConfig(textConfigPath):
122  configPath = os.path.normpath(os.path.expanduser(textConfigPath))
123  if os.path.isabs(configPath) and os.path.isfile(configPath):
124  return configPath
125  cwdPath = os.path.join(os.getcwd(), configPath)
126  if os.path.isfile(cwdPath):
127  return cwdPath
128  return None
129 
130  @staticmethod
131  def findRepoPathYamlConfig(textConfigPath):
132  """
133  Search for the file up to two levels deep within the first DATAPATH entry.
134  First, check directly under the analysis repository (depth 0).
135  Then, check immediate subdirectories (depth 1), looking for the file inside each.
136  Returns a list of all matches found.
137  """
138  matches = []
139  analysisRepoPath = os.environ.get('DATAPATH', '').split(os.pathsep)[0]
140  # Depth 0: Directly under analysisRepoPath
141  searchPath = os.path.join(analysisRepoPath, textConfigPath)
142  if os.path.isfile(searchPath):
143  matches.append(searchPath)
144  # Depth 1: Inside immediate subdirectories
145  try:
146  for subdir in os.listdir(analysisRepoPath):
147  candidate = os.path.join(analysisRepoPath, subdir, textConfigPath)
148  if os.path.isfile(candidate):
149  matches.append(candidate)
150  except Exception:
151  pass
152  return matches
153 
155  files = []
156  with open(path, 'r') as inputText:
157  for line in inputText.readlines():
158  # Strip the line and skip comments and empty lines
159  line = line.strip()
160  if line.startswith('#') or not line:
161  continue
162  if os.path.isdir(line):
163  if not os.listdir(line):
164  raise FileNotFoundError(f"The directory \"{path}\" is empty. Please provide a directory with .root files.")
165  for root_file in os.listdir(line):
166  if '.root' in root_file:
167  files.append(os.path.join(line, root_file))
168  else:
169  files += line.split(',')
170  # Remove leading/trailing whitespaces from file names
171  files = [file.strip() for file in files]
172  return files
173 
174  def setup(self):
175  self.parser.parse_args()
176  self.config = self._readYamlConfig()
178 
180  self.parser.description = 'CPRunScript available arguments'
181  self.parser.usage = argparse.SUPPRESS
182  self.parser.print_help()
python.CPBaseRunner.CPBaseRunner._parseInputFileList
def _parseInputFileList(path)
Definition: CPBaseRunner.py:154
python.CPBaseRunner.CPBaseRunner._findYamlConfig
def _findYamlConfig(self, local=True)
Definition: CPBaseRunner.py:105
python.CPBaseRunner.CPBaseRunner.parser
parser
Definition: CPBaseRunner.py:13
python.CPBaseRunner.CPBaseRunner._readYamlConfig
def _readYamlConfig(self)
Definition: CPBaseRunner.py:93
python.CPBaseRunner.CPBaseRunner._defaultFlagsInitialization
def _defaultFlagsInitialization(self)
Definition: CPBaseRunner.py:67
python.CPBaseRunner.CPBaseRunner.findLocalPathYamlConfig
def findLocalPathYamlConfig(textConfigPath)
Definition: CPBaseRunner.py:121
python.CPBaseRunner.CPBaseRunner.addCustomArguments
def addCustomArguments(self)
Definition: CPBaseRunner.py:55
python.CPBaseRunner.CPBaseRunner.printFlags
def printFlags(self)
Definition: CPBaseRunner.py:42
python.CPBaseRunner.CPBaseRunner._defaultParseArguments
def _defaultParseArguments(self)
Definition: CPBaseRunner.py:75
python.CPBaseRunner.CPBaseRunner.outputName
def outputName(self)
Definition: CPBaseRunner.py:36
python.CPBaseRunner.CPBaseRunner.printAvailableArguments
def printAvailableArguments(self)
Definition: CPBaseRunner.py:179
python.CPBaseRunner.CPBaseRunner.run
def run(self)
Definition: CPBaseRunner.py:63
python.CPBaseRunner.CPBaseRunner.makeAlgSequence
def makeAlgSequence(self)
Definition: CPBaseRunner.py:59
python.CPBaseRunner.CPBaseRunner.setup
def setup(self)
Definition: CPBaseRunner.py:174
python.CPBaseRunner.CPBaseRunner
Definition: CPBaseRunner.py:8
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
python.CPBaseRunner.CPBaseRunner.flags
flags
Definition: CPBaseRunner.py:177
Trk::open
@ open
Definition: BinningType.h:40
python.CPBaseRunner.CPBaseRunner.config
config
Definition: CPBaseRunner.py:176
python.CPBaseRunner.CPBaseRunner.args
def args(self)
Definition: CPBaseRunner.py:17
python.AllConfigFlags.initConfigFlags
def initConfigFlags()
Definition: AllConfigFlags.py:19
confTool.parse_args
def parse_args()
Definition: confTool.py:36
python.CPBaseRunner.CPBaseRunner.findRepoPathYamlConfig
def findRepoPathYamlConfig(textConfigPath)
Definition: CPBaseRunner.py:131
python.CPBaseRunner.CPBaseRunner.logger
logger
Definition: CPBaseRunner.py:10
python.CPBaseRunner.CPBaseRunner._inputList
_inputList
Definition: CPBaseRunner.py:12
python.CPBaseRunner.CPBaseRunner.inputList
def inputList(self)
Definition: CPBaseRunner.py:23
python.Utils.unixtools.find_datafile
def find_datafile(fname, pathlist=None, access=os.R_OK)
pathresolver-like helper function --------------------------------------—
Definition: unixtools.py:67
python.CPBaseRunner.CPBaseRunner._args
_args
Definition: CPBaseRunner.py:11
python.ParticleTypeUtil.info
def info
Definition: ParticleTypeUtil.py:87
Trk::split
@ split
Definition: LayerMaterialProperties.h:38
python.CPBaseRunner.CPBaseRunner.__init__
def __init__(self)
Definition: CPBaseRunner.py:9