ATLAS Offline Software
cmake_newanalysisalg.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
2 
3 # @file PyUtils.scripts.cmt_newanalysisalg
4 # @purpose streamline and ease the creation of new athena algs
5 # @author Will Buttinger
6 # @date February 2017
7 
8 #Note - this code could use a serious rewrite, I just hacked it together to get something working
9 
10 from __future__ import with_statement
11 
12 __version__ = "$Revision: 795362 $"
13 __author__ = "Will Buttinger"
14 __doc__ = "streamline and ease the creation of new AthAnalysisAlgorithm"
15 
16 
17 import os
18 import textwrap
19 import subprocess
20 import PyUtils.acmdlib as acmdlib
21 
22 class Templates:
23  jobo_template = """\
24 #Skeleton joboption for a simple analysis job
25 
26 #---- Minimal job options -----
27 
28 jps.AthenaCommonFlags.AccessMode = "ClassAccess" #Choose from TreeAccess,BranchAccess,ClassAccess,AthenaAccess,POOLAccess
29 #jps.AthenaCommonFlags.TreeName = "MyTree" #when using TreeAccess, must specify the input tree name
30 
31 jps.AthenaCommonFlags.HistOutputs = ["MYSTREAM:myfile.root"] #register output files like this. MYSTREAM is used in the code
32 
33 athAlgSeq += CfgMgr.%(klass)s() #adds an instance of your alg to the main alg sequence
34 
35 
36 #---- Options you could specify on command line -----
37 #jps.AthenaCommonFlags.EvtMax=-1 #set on command-line with: --evtMax=-1
38 #jps.AthenaCommonFlags.SkipEvents=0 #set on command-line with: --skipEvents=0
39 #jps.AthenaCommonFlags.FilesInput = ["%(inFile)s"] #set on command-line with: --filesInput=...
40 
41 
42 include("AthAnalysisBaseComps/SuppressLogging.py") #Optional include to suppress as much athena output as possible. Keep at bottom of joboptions so that it doesn't suppress the logging of the things you have configured above
43 
44 """
45 
46  script_template = """\
47 #!/usr/bin/env python
48 
49 # Run this application/script like this:
50 # run%(klass)s.py --filesInput file.root --evtMax 100
51 # See --help for more arguments and flag options
52 
53 from AthenaConfiguration.AllConfigFlags import initConfigFlags
54 flags = initConfigFlags()
55 flags._parser = flags.getArgumentParser(description=\"\"\"My Demo Application\"\"\") # an argparse.ArgumentParser
56 flags.parser().add_argument('--accessMode',default="POOLAccess", # can add arguments to the parser as usual
57  choices={"POOLAccess","ClassAccess"},help="Input file reading mode (ClassAccess can be faster but is less supported)")
58 # changes to default flag values (done before fillFromArgs so appears in the --help flag system
59 flags.Exec.PrintAlgsSequence = True # displays algsequence at start of job
60 
61 
62 args = flags.fillFromArgs() # parse command line arguments
63 flags.lock() # lock the flags
64 
65 
66 # configure main services and input file reading
67 from AthenaConfiguration.MainServicesConfig import MainServicesCfg
68 from AthenaConfiguration.Enums import Format
69 cfg = MainServicesCfg(flags)
70 if flags.Input.Format is Format.BS:
71  # read RAW (bytestream)
72  from ByteStreamCnvSvc.ByteStreamConfig import ByteStreamReadCfg
73  cfg.merge(ByteStreamReadCfg(flags))
74 else:
75  # reading POOL, use argument to decide which read mode
76  if flags.args().accessMode == "POOLAccess":
77  from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
78  cfg.merge(PoolReadCfg(flags))
79  else:
80  from AthenaRootComps.xAODEventSelectorConfig import xAODReadCfg,xAODAccessMode
81  cfg.merge(xAODReadCfg(flags, AccessMode = xAODAccessMode.CLASS_ACCESS))
82 
83 # configure output ROOT files from Output.HISTOutputs flag (should be of form: "STREAMNAME:file.root")
84 from AthenaConfiguration.ComponentFactory import CompFactory
85 if flags.Output.HISTFileName != "":
86  outputs = []
87  for file in (flags.Output.HISTFileName if type(flags.Output.HISTFileName)==list else flags.Output.HISTFileName.split(",")):
88  streamName = file.split(":")[0] if ":" in file else "ANALYSIS"
89  fileName = file.split(":")[1] if ":" in file else file
90  outputs += ["{} DATAFILE='{}' OPT='RECREATE'".format(streamName,fileName)]
91  cfg.addService(CompFactory.THistSvc(Output = outputs))
92 
93 # add our algorithm
94 cfg.addEventAlgo(CompFactory.%(klass)s(),sequenceName="AthAlgSeq")
95 
96 
97 # final cfg tweaks before launching:
98 cfg.getService("AthenaEventLoopMgr").IntervalInSeconds = 5 # enable processing rate reporting every 5s
99 # suppress logging from some core services that we usually don't care about hearing from
100 cfg.getService("MessageSvc").setWarning += ["ClassIDSvc","PoolSvc","AthDictLoaderSvc","AthenaPoolAddressProviderSvc",
101  "ProxyProviderSvc","DBReplicaSvc","MetaDataSvc","MetaDataStore","AthenaPoolCnvSvc",
102  "TagMetaDataStore","EventSelector","CoreDumpSvc","AthMasterSeq","EventPersistencySvc",
103  "ActiveStoreSvc","AthOutSeq","AthRegSeq","FPEAuditor"]
104 
105 # run the job
106 if cfg.run().isFailure():
107  import sys
108  sys.exit(1)
109 """
110 
111  alg_hdr_template = """\
112 #ifndef %(guard)s
113 #define %(guard)s 1
114 
115 #include "AthAnalysisBaseComps/AthAnalysisAlgorithm.h"
116 
117 //Example ROOT Includes
118 //#include "TTree.h"
119 //#include "TH1D.h"
120 
121 %(namespace_begin)s
122 
123 class %(klass)s: public ::AthAnalysisAlgorithm {
124  public:
125  %(klass)s( const std::string& name, ISvcLocator* pSvcLocator );
126  virtual ~%(klass)s();
127 
128  ///uncomment and implement methods as required
129 
130  //IS EXECUTED:
131  virtual StatusCode initialize(); //once, before any input is loaded
132  virtual StatusCode beginInputFile(); //start of each input file, only metadata loaded
133  //virtual StatusCode firstExecute(); //once, after first eventdata is loaded (not per file)
134  virtual StatusCode execute(); //per event
135  //virtual StatusCode endInputFile(); //end of each input file
136  //virtual StatusCode metaDataStop(); //when outputMetaStore is populated by MetaDataTools
137  virtual StatusCode finalize(); //once, after all events processed
138 
139 
140  ///Other useful methods provided by base class are:
141  ///evtStore() : ServiceHandle to main event data storegate
142  ///inputMetaStore() : ServiceHandle to input metadata storegate
143  ///outputMetaStore() : ServiceHandle to output metadata storegate
144  ///histSvc() : ServiceHandle to output ROOT service (writing TObjects)
145  ///currentFile() : TFile* to the currently open input file
146  ///retrieveMetadata(...): See twiki.cern.ch/twiki/bin/view/AtlasProtected/AthAnalysisBase#ReadingMetaDataInCpp
147 
148 
149  private:
150 
151  //Example algorithm property, see constructor for declaration:
152  //int m_nProperty = 0;
153 
154  //Example histogram, see initialize method for registration to output histSvc
155  //TH1D* m_myHist = 0;
156  //TTree* m_myTree = 0;
157 
158 };
159 %(namespace_end)s
160 #endif //> !%(guard)s
161 """
162 
163  alg_cxx_template = """\
164 // %(pkg)s includes
165 #include "%(namespace_klass)s.h"
166 
167 //#include "xAODEventInfo/EventInfo.h"
168 
169 
170 %(namespace_begin)s
171 
172 %(klass)s::%(klass)s( const std::string& name, ISvcLocator* pSvcLocator ) : AthAnalysisAlgorithm( name, pSvcLocator ){
173 
174  //declareProperty( "Property", m_nProperty = 0, "My Example Integer Property" ); //example property declaration
175 
176 }
177 
178 
179 %(klass)s::~%(klass)s() {}
180 
181 
182 StatusCode %(klass)s::initialize() {
183  ATH_MSG_INFO ("Initializing " << name() << "...");
184  //
185  //This is called once, before the start of the event loop
186  //Retrieves of tools you have configured in the joboptions go here
187  //
188 
189  //HERE IS AN EXAMPLE
190  //We will create a histogram and a ttree and register them to the histsvc
191  //Remember to configure the histsvc stream in the joboptions
192  //
193  //m_myHist = new TH1D("myHist","myHist",100,0,100);
194  //CHECK( histSvc()->regHist("/MYSTREAM/myHist", m_myHist) ); //registers histogram to output stream
195  //m_myTree = new TTree("myTree","myTree");
196  //CHECK( histSvc()->regTree("/MYSTREAM/SubDirectory/myTree", m_myTree) ); //registers tree to output stream inside a sub-directory
197 
198 
199  return StatusCode::SUCCESS;
200 }
201 
202 StatusCode %(klass)s::finalize() {
203  ATH_MSG_INFO ("Finalizing " << name() << "...");
204  //
205  //Things that happen once at the end of the event loop go here
206  //
207 
208 
209  return StatusCode::SUCCESS;
210 }
211 
212 StatusCode %(klass)s::execute() {
213  ATH_MSG_DEBUG ("Executing " << name() << "...");
214  setFilterPassed(false); //optional: start with algorithm not passed
215 
216 
217 
218  //
219  //Your main analysis code goes here
220  //If you will use this algorithm to perform event skimming, you
221  //should ensure the setFilterPassed method is called
222  //If never called, the algorithm is assumed to have 'passed' by default
223  //
224 
225 
226  //HERE IS AN EXAMPLE
227  //const xAOD::EventInfo* ei = 0;
228  //CHECK( evtStore()->retrieve( ei , "EventInfo" ) );
229  //ATH_MSG_INFO("eventNumber=" << ei->eventNumber() );
230  //m_myHist->Fill( ei->averageInteractionsPerCrossing() ); //fill mu into histogram
231 
232 
233  setFilterPassed(true); //if got here, assume that means algorithm passed
234  return StatusCode::SUCCESS;
235 }
236 
237 StatusCode %(klass)s::beginInputFile() {
238  //
239  //This method is called at the start of each input file, even if
240  //the input file contains no events. Accumulate metadata information here
241  //
242 
243  //example of retrieval of CutBookkeepers: (remember you will need to include the necessary header files and use statements in requirements file)
244  // const xAOD::CutBookkeeperContainer* bks = 0;
245  // CHECK( inputMetaStore()->retrieve(bks, "CutBookkeepers") );
246 
247  //example of IOVMetaData retrieval (see https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/AthAnalysisBase#How_to_access_file_metadata_in_C)
248  //float beamEnergy(0); CHECK( retrieveMetadata("/TagInfo","beam_energy",beamEnergy) );
249  //std::vector<float> bunchPattern; CHECK( retrieveMetadata("/Digitiation/Parameters","BeamIntensityPattern",bunchPattern) );
250 
251 
252 
253  return StatusCode::SUCCESS;
254 }
255 
256 %(namespace_end)s
257 """
258  testxml_template = """\
259  <TEST name="%(namespace_klass)s" type="athena" suite="ASGTests">
260  <options_atn>%(pkg)s/%(namespace_klass)sJobOptions.py</options_atn>
261  <timelimit>5</timelimit>
262  <author> PLEASE ENTER A NAME </author>
263  <mailto> PLEASEENTER@cern.ch </mailto>
264  <expectations>
265  <errorMessage> Athena exited abnormally </errorMessage>
266  <errorMessage>FAILURE (ERROR)</errorMessage>
267  <returnValue>0</returnValue>
268  </expectations>
269  </TEST>
270 """
271 
272 
273 
274 @acmdlib.command(
275  name='cmake.new-analysisalg'
276  )
277 @acmdlib.argument(
278  'algname',
279  help="name of the new alg"
280  )
281 @acmdlib.argument(
282  '--newJobo',
283  action='store_true',
284  default=False,
285  help='Create a skeleton joboption for execution of the new algorithm'
286  )
287 
288 def main(args):
289  """create a new AthAnalysisAlgorithm inside the current package. Call from within the package directory
290 
291  ex:
292  $ acmd cmake new-analysisalg MyAlg
293  """
294  sc = 0
295 
296  full_alg_name = args.algname
297 
298  #determine the package from the cwd
299  cwd = os.getcwd()
300  #check that src dir exists and CMakeLists.txt exists (i.e. this is a package)
301  if not os.path.isdir(cwd+"/src") or not os.path.isfile(cwd+"/CMakeLists.txt"):
302  print("ERROR you must call new-analysisalg from within the package you want to add the algorithm to")
303  return -1
304 
305 
306  full_pkg_name = os.path.basename(cwd)
307  print(textwrap.dedent("""\
308  ::: create alg [%(full_alg_name)s] in pkg [%(full_pkg_name)s]""" %locals()))
309 
310 
311  #first we must check that CMakeLists.txt file has the AthAnalysisBaseComps dependency in it
312  foundBaseComps=False
313  hasxAODEventInfo=False
314  hasAtlasROOT=False
315  hasAsgTools=False
316  lastUse=0
317  lineCount=0
318  hasLibraryLine=False
319  hasComponentLine=False
320  for line in open('CMakeLists.txt'):
321  lineCount +=1
322  if "atlas_add_library" in line: hasLibraryLine=True
323  if "atlas_add_component" in line: hasComponentLine=True
324 
325 #GOT THIS FAR WITH EDITING
326 
327 
328 
329 
330 
331  #following code borrowed from gen_klass
332  hdr = Templates.alg_hdr_template
333  cxx = Templates.alg_cxx_template
334 
335  namespace_klass = full_alg_name.replace('::','__')
336  namespace_begin,namespace_end = "",""
337  namespace = ""
338  if full_alg_name.count("::")>0:
339  namespace = full_alg_name.split("::")[0]
340  full_alg_name = full_alg_name.split("::")[1]
341  namespace_begin = "namespace %s {" % namespace
342  namespace_end = "} //> end namespace %s" % namespace
343  pass
344 
345  guard = "%s_%s_H" % (full_pkg_name.upper(), namespace_klass.upper())
346 
347  d = dict( pkg=full_pkg_name,
348  klass=full_alg_name,
349  guard=guard,
350  namespace_begin=namespace_begin,
351  namespace_end=namespace_end,namespace_klass=namespace_klass,namespace=namespace
352  )
353  fname = os.path.splitext("src/%s"%namespace_klass)[0]
354  #first check doesn't exist
355  if os.path.isfile(fname+'.h'):
356  print("::: ERROR %s.h already exists" % fname)
357  return -1
358  print("::: INFO Creating %s.h" % fname)
359  o_hdr = open(fname+'.h', 'w')
360  o_hdr.writelines(hdr%d)
361  o_hdr.flush()
362  o_hdr.close()
363 
364  if os.path.isfile(fname+'.cxx'):
365  print("::: ERROR %s.cxx already exists" % fname)
366  return -1
367  print("::: INFO Creating %s.cxx" % fname)
368  o_cxx = open(fname+'.cxx', 'w')
369  o_cxx.writelines(cxx%d)
370  o_cxx.flush()
371  o_cxx.close()
372 
373 
374  #now add the algorithm to the _entries.cxx file in the components folder
375  #first check they exist
376  if not os.path.exists("src/components"): os.mkdir("src/components")
377  if not os.path.isfile("src/components/%s_entries.cxx"%full_pkg_name):
378  print("::: INFO Creating src/components/%s_entries.cxx"%full_pkg_name)
379  loadFile = open("src/components/%s_entries.cxx"%full_pkg_name,'w')
380  if len(namespace_begin)>0:
381  d["namespace"] = args.algname.split("::")[0]
382  loadFile.writelines("""
383 #include "../%(namespace_klass)s.h"
384 DECLARE_COMPONENT(%(namespace)s::%(klass)s )
385 """%d)
386  else:
387  loadFile.writelines("""
388 #include "../%(namespace_klass)s.h"
389 DECLARE_COMPONENT( %(klass)s )
390 """%d)
391  loadFile.flush()
392  loadFile.close()
393  else:
394  #first check algorithm not already in _entries file
395  inFile=False
396  for line in open("src/components/%s_entries.cxx"%full_pkg_name):
397  if len(namespace_begin)==0 and "DECLARE_COMPONENT" in line and d["klass"] in line: inFile=True
398  if len(namespace_begin)>0 and "DECLARE_COMPONENT" in line and d["klass"] in line and d["namespace"]: inFile=True
399 
400  if not inFile:
401  print("::: INFO Adding %s to src/components/%s_entries.cxx"% (args.algname,full_pkg_name))
402  nextAdd=True
403  with open("src/components/%s_entries.cxx"%full_pkg_name, "a") as f:
404  if len(namespace_begin)>0:
405  f.write(""" DECLARE_COMPONENT(%(namespace)s::%(klass)s );"""%d)
406  else:
407  f.write(""" DECLARE_COMPONENT( %(klass)s );"""%d)
408 
409 
410  if args.newJobo:
411  #make the joboptions file too
412  full_jobo_name = namespace_klass + "JobOptions"
413  full_script_name = "run" + namespace_klass
414  full_alg_name = namespace_klass
415 
416  print(textwrap.dedent("""\
417  ::: create jobo [%(full_jobo_name)s] and script [%(full_script_name)s] for alg [%(full_alg_name)s]""" %locals()))
418 
419  #following code borrowed from gen_klass
420  jobo = Templates.jobo_template
421 
422  e = dict( klass=full_alg_name,
423  inFile=os.environ['ASG_TEST_FILE_MC'],
424  )
425  fname = 'share/%s.py' % full_jobo_name
426  #first check doesn't exist
427  if os.path.isfile(fname):
428  print("::: WARNING %s already exists .. will not overwrite" % fname)
429  else:
430  o_hdr = open(fname, 'w')
431  o_hdr.writelines(jobo%e)
432  o_hdr.flush()
433  o_hdr.close()
434 
435  scripto = Templates.script_template
436 
437  e = dict( klass=full_alg_name,
438  inFile=os.environ['ASG_TEST_FILE_MC'],
439  )
440  fname = 'scripts/%s.py' % full_script_name
441  #first check doesn't exist
442  if os.path.isfile(fname):
443  print("::: WARNING %s already exists .. will not overwrite" % fname)
444  else:
445  o_hdr = open(fname, 'w')
446  o_hdr.writelines(scripto%e)
447  o_hdr.flush()
448  o_hdr.close()
449  os.chmod(fname, 0o755)
450 
451  #need to reconfigure cmake so it knows about the new files
452  #rely on the WorkDir_DIR env var for this
453  workDir = os.environ.get("WorkDir_DIR")
454  if workDir is None:
455  print("::: ERROR No WorkDir_DIR env var, did you forget to source the setup.sh script?")
456  print("::: ERROR Please do this and reconfigure cmake manually!")
457  else:
458  print("::: INFO Reconfiguring cmake %s/../." % workDir)
459  res = subprocess.getstatusoutput('cmake %s/../.' % workDir)
460  if res[0]!=0:
461  print("::: WARNING reconfigure unsuccessful. Please reconfigure manually!")
462 
463 
464  print("::: INFO Please ensure your CMakeLists.txt file has ")
465  print("::: atlas_add_component( %s src/component/*.cxx ... )" % full_pkg_name)
466  print("::: INFO and necessary dependencies declared ")
467  print("::: INFO Minimum dependency is: Control/AthAnalysisBaseComps")
468 
python.scripts.cmake_newanalysisalg.main
def main(args)
Definition: cmake_newanalysisalg.py:288
python.scripts.cmake_newanalysisalg.Templates
Definition: cmake_newanalysisalg.py:22
Trk::open
@ open
Definition: BinningType.h:40
dbg::print
void print(std::FILE *stream, std::format_string< Args... > fmt, Args &&... args)
Definition: SGImplSvc.cxx:70