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