ATLAS Offline Software
Loading...
Searching...
No Matches
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
15import os
16import textwrap
17import subprocess
18import PyUtils.acmdlib as acmdlib
19
21 jobo_template = """\
22#Skeleton joboption for a simple analysis job
23
24#---- Minimal job options -----
25
26jps.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
29jps.AthenaCommonFlags.HistOutputs = ["MYSTREAM:myfile.root"] #register output files like this. MYSTREAM is used in the code
30
31athAlgSeq += 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
40include("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
51from AthenaConfiguration.AllConfigFlags import initConfigFlags
52flags = initConfigFlags()
53flags._parser = flags.getArgumentParser(description=\"\"\"My Demo Application\"\"\") # an argparse.ArgumentParser
54flags.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
57flags.Exec.PrintAlgsSequence = True # displays algsequence at start of job
58
59
60args = flags.fillFromArgs() # parse command line arguments
61flags.lock() # lock the flags
62
63
64# configure main services and input file reading
65from AthenaConfiguration.MainServicesConfig import MainServicesCfg
66from AthenaConfiguration.Enums import Format
67cfg = MainServicesCfg(flags)
68if flags.Input.Format is Format.BS:
69 # read RAW (bytestream)
70 from ByteStreamCnvSvc.ByteStreamConfig import ByteStreamReadCfg
71 cfg.merge(ByteStreamReadCfg(flags))
72else:
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")
82from AthenaConfiguration.ComponentFactory import CompFactory
83if 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
92cfg.addEventAlgo(CompFactory.%(klass)s(),sequenceName="AthAlgSeq")
93
94
95# final cfg tweaks before launching:
96if 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
99cfg.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
105if 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
121class %(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
180StatusCode %(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
200StatusCode %(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
210StatusCode %(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
235StatusCode %(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
286def 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"
382DECLARE_COMPONENT(%(namespace)s::%(klass)s )
383"""%d)
384 else:
385 loadFile.writelines("""
386#include "../%(namespace_klass)s.h"
387DECLARE_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
void print(char *figname, TCanvas *c1)