Skip to content

Commit 52692fd

Browse files
gconesabsawenzel
authored andcommitted
first implementation of the script creating the DPG simulation workflow
1 parent 7078052 commit 52692fd

File tree

1 file changed

+312
-0
lines changed

1 file changed

+312
-0
lines changed

MC/bin/o2dpg_sim_workflow.py

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
#!/usr/bin/env python3
2+
3+
#
4+
# A script producing a consistent MC->RECO->AOD workflow
5+
# It aims to handle the different MC possible configurations
6+
# It just creates a workflow.json txt file, to execute the workflow one must execute right after
7+
# ${O2DPG_ROOT}/MC/bin/o2_dpg_workflow_runner.py -f workflow.json
8+
# Execution examples:
9+
# ./o2dpg_sim_workflow.py -e TGeant3 -nb 0 -ns 2 -j 8 -tf 1 -mod "-m TPC" -proc "jets" -ptTrigMin 3.5 -ptHatBin 3 -trigger "external" -ini "\$O2DPG_ROOT/MC/config/PWGGAJE/ini/trigger_decay_gamma.ini" --embedding False
10+
#
11+
# ./o2dpg_sim_workflow.py -e TGeant3 -nb 0 -ns 2 -j 8 -tf 1 -mod "--skipModules ZDC" -proc "ccbar" --embedding True
12+
#
13+
14+
import argparse
15+
from os import environ
16+
import json
17+
import array as arr
18+
19+
parser = argparse.ArgumentParser(description='Create a MC simulation workflow')
20+
21+
parser.add_argument('-ns',help='number of signal events / timeframe', default=20)
22+
parser.add_argument('-gen',help='generator: pythia8, extgen', default='pythia8')
23+
parser.add_argument('-proc',help='process type: dirgamma, jets, ccbar', default='')
24+
parser.add_argument('-trigger',help='event selection: particle, external', default='')
25+
parser.add_argument('-ini',help='generator init parameters file, for example: ${O2DPG_ROOT}/MC/config/PWGHF/ini/GeneratorHF.ini', default='')
26+
parser.add_argument('-confKey',help='generator or trigger configuration key values, for example: GeneratorPythia8.config=pythia8.cfg', default='')
27+
28+
parser.add_argument('-eCMS',help='CMS energy', default=5200.0)
29+
parser.add_argument('-col',help='collision sytem: pp, PbPb, pPb, Pbp, ...', default='pp')
30+
parser.add_argument('-ptHatBin',help='pT hard bin number', default=-1)
31+
parser.add_argument('-ptHatMin',help='pT hard minimum when no bin requested', default=0)
32+
parser.add_argument('-ptHatMax',help='pT hard maximum when no bin requested', default=-1)
33+
parser.add_argument('-ptTrigMin',help='generated pT trigger minimum', default=0)
34+
parser.add_argument('-ptTrigMax',help='generated pT trigger maximum', default=-1)
35+
36+
parser.add_argument('--embedding',help='whether to embedd into background', default=False)
37+
parser.add_argument('-nb',help='number of background events / timeframe', default=20)
38+
parser.add_argument('-genBkg',help='generator', default='pythia8hi')
39+
parser.add_argument('-iniBkg',help='generator init parameters file', default='${O2DPG_ROOT}/MC/config/common/ini/basic.ini')
40+
41+
parser.add_argument('-e',help='simengine', default='TGeant4')
42+
parser.add_argument('-tf',help='number of timeframes', default=2)
43+
parser.add_argument('-j',help='number of workers (if applicable)', default=8)
44+
parser.add_argument('-mod',help='Active modules', default='--skipModules ZDC')
45+
parser.add_argument('-seed',help='random seed number', default=0)
46+
parser.add_argument('-o',help='output workflow file', default='workflow.json')
47+
parser.add_argument('--noIPC',help='disable shared memory in DPL')
48+
args = parser.parse_args()
49+
print (args)
50+
51+
# make sure O2DPG + O2 is loaded
52+
O2DPG_ROOT=environ.get('O2DPG_ROOT')
53+
O2_ROOT=environ.get('O2_ROOT')
54+
55+
if O2DPG_ROOT == None:
56+
print('Error: This needs O2DPG loaded')
57+
# exit(1)
58+
59+
if O2_ROOT == None:
60+
print('Error: This needs O2 loaded')
61+
# exit(1)
62+
63+
# ----------- START WORKFLOW CONSTRUCTION -----------------------------
64+
65+
NTIMEFRAMES=int(args.tf)
66+
NWORKERS=args.j
67+
MODULES=args.mod #"--skipModules ZDC"
68+
SIMENGINE=args.e
69+
70+
# add here other possible types
71+
72+
workflow={}
73+
workflow['stages'] = []
74+
75+
taskcounter=0
76+
def createTask(name='', needs=[], tf=-1, cwd='./'):
77+
global taskcounter
78+
taskcounter = taskcounter + 1
79+
return { 'name': name, 'cmd':'', 'needs': needs, 'resources': { 'cpu': -1 , 'mem': -1 }, 'timeframe' : tf, 'labels' : [], 'cwd' : cwd }
80+
81+
def getDPL_global_options():
82+
if args.noIPC!=None:
83+
return "-b --run --no-IPC"
84+
return "-b --run --shm-segment-size ${SHMSIZE:-50000000000} --session " + str(taskcounter)
85+
86+
87+
doembedding=True if args.embedding=='True' or args.embedding==True else False
88+
89+
if doembedding:
90+
# ---- background transport task -------
91+
NBKGEVENTS=args.nb
92+
GENBKG=args.genBkg
93+
INIBKG=args.iniBkg
94+
BKGtask=createTask(name='bkgsim')
95+
BKGtask['cmd']='o2-sim -e ' + SIMENGINE + ' -j ' + str(NWORKERS) + ' -n ' + str(NBKGEVENTS) + ' -g ' + str(GENBKG) + str(MODULES) + ' -o bkg --configFile ' + str(INIBKG)
96+
workflow['stages'].append(BKGtask)
97+
98+
# loop over timeframes
99+
for tf in range(1, NTIMEFRAMES + 1):
100+
timeframeworkdir='tf'+str(tf)
101+
102+
# ---- transport task -------
103+
# function encapsulating the signal sim part
104+
# first argument is timeframe id
105+
RNDSEED=args.seed # 0 means random seed !
106+
ECMS=args.eCMS
107+
NSIGEVENTS=args.ns
108+
GENERATOR=args.gen
109+
INIFILE=''
110+
if args.ini!= '':
111+
INIFILE=' --configFile ' + args.ini
112+
CONFKEY=''
113+
if args.confKey!= '':
114+
CONFKEY=' --configKeyValue ' + args.confKey
115+
PROCESS=args.proc
116+
TRIGGER=''
117+
if args.trigger != '':
118+
TRIGGER=' -t ' + args.trigger
119+
120+
PTTRIGMIN=float(args.ptTrigMin)
121+
PTTRIGMAX=float(args.ptTrigMax)
122+
123+
# Recover PTHATMIN and PTHATMAX from pre-defined array depending bin number PTHATBIN
124+
# or just the ones passed
125+
PTHATBIN=int(args.ptHatBin)
126+
PTHATMIN=int(args.ptHatMin)
127+
PTHATMAX=int(args.ptHatMax)
128+
# I would move next lines to a external script, not sure how to do it (GCB)
129+
if PTHATBIN > -1:
130+
# gamma-jet
131+
if PROCESS == 'dirgamma':
132+
low_edge = arr.array('l', [5, 11, 21, 36, 57, 84])
133+
hig_edge = arr.array('l', [11, 21, 36, 57, 84, -1])
134+
PTHATMIN=low_edge[PTHATBIN]
135+
PTHATMAX=hig_edge[PTHATBIN]
136+
# jet-jet
137+
elif PROCESS == 'jets':
138+
# Biased jet-jet
139+
# Define the pt hat bin arrays and set bin depending threshold
140+
if PTTRIGMIN == 3.5:
141+
low_edge = arr.array('l', [5, 7, 9, 12, 16, 21])
142+
hig_edge = arr.array('l', [7, 9, 12, 16, 21, -1])
143+
PTHATMIN=low_edge[PTHATBIN]
144+
PTHATMAX=hig_edge[PTHATBIN]
145+
elif PTTRIGMIN == 7:
146+
low_edge = arr.array('l', [ 8, 10, 14, 19, 26, 35, 48, 66])
147+
hig_edge = arr.array('l', [10, 14, 19, 26, 35, 48, 66, -1])
148+
PTHATMIN=low_edge[PTHATBIN]
149+
PTHATMAX=hig_edge[PTHATBIN]
150+
#unbiased
151+
else:
152+
low_edge = arr.array('l', [ 0, 5, 7, 9, 12, 16, 21, 28, 36, 45, 57, 70, 85, 99, 115, 132, 150, 169, 190, 212, 235])
153+
hig_edge = arr.array('l', [ 5, 7, 9, 12, 16, 21, 28, 36, 45, 57, 70, 85, 99, 115, 132, 150, 169, 190, 212, 235, -1])
154+
PTHATMIN=low_edge[PTHATBIN]
155+
PTHATMAX=hig_edge[PTHATBIN]
156+
else:
157+
low_edge = arr.array('l', [ 0, 5, 7, 9, 12, 16, 21, 28, 36, 45, 57, 70, 85, 99, 115, 132, 150, 169, 190, 212, 235])
158+
hig_edge = arr.array('l', [ 5, 7, 9, 12, 16, 21, 28, 36, 45, 57, 70, 85, 99, 115, 132, 150, 169, 190, 212, 235, -1])
159+
PTHATMIN=low_edge[PTHATBIN]
160+
PTHATMAX=hig_edge[PTHATBIN]
161+
162+
# translate here collision type to PDG
163+
# not sure this is what we want to do (GCB)
164+
COLTYPE=args.col
165+
166+
if COLTYPE == 'pp':
167+
PDGA=2212 # proton
168+
PDGB=2212 # proton
169+
170+
if COLTYPE == 'PbPb':
171+
PDGA=2212 # Pb????
172+
PDGB=2212 # Pb????
173+
174+
if COLTYPE == 'pPb':
175+
PDGA=2212 # proton
176+
PDGB=2212 # Pb????
177+
178+
if COLTYPE == 'Pbp':
179+
PDGA=2212 # Pb????
180+
PDGB=2212 # proton
181+
182+
# produce the signal configuration
183+
SGN_CONFIG_task=createTask(name='gensgnconf_'+str(tf), tf=tf, cwd=timeframeworkdir)
184+
if GENERATOR == 'pythia8':
185+
SGN_CONFIG_task['cmd'] = '${O2DPG_ROOT}/MC/config/common/pythia8/utils/mkpy8cfg.py \
186+
--output=pythia8_'+ str(tf) +'.cfg \
187+
--seed='+str(RNDSEED)+' \
188+
--idA='+str(PDGA)+' \
189+
--idB='+str(PDGB)+' \
190+
--eCM='+str(ECMS)+' \
191+
--process='+str(PROCESS)+' \
192+
--ptHatMin=' + str(PTHATMIN) + ' \
193+
--ptHatMax=' + str(PTHATMAX)
194+
workflow['stages'].append(SGN_CONFIG_task)
195+
# elif GENERATOR == 'extgen': what do we do if generator is not pythia8?
196+
197+
if doembedding:
198+
# link background files to current working dir for this timeframe
199+
LinkBKGtask=createTask(name='linkbkg_'+str(tf), needs=[BKGtask['name']], tf=tf, cwd=timeframeworkdir)
200+
LinkBKGtask['cmd']='ln -s ../bkg*.root .'
201+
workflow['stages'].append(LinkBKGtask)
202+
203+
# transport signals
204+
signalprefix='sgn_' + str(tf)
205+
signalneeds=[ SGN_CONFIG_task['name'] ]
206+
embeddinto= "--embedIntoFile bkg_Kine.root" if doembedding else ""
207+
if doembedding:
208+
signalneeds = signalneeds + [ BKGtask['name'], LinkBKGtask['name'] ]
209+
SGNtask=createTask(name='sgnsim_'+str(tf), needs=signalneeds, tf=tf, cwd='tf'+str(tf))
210+
#SGNtask['cmd']='o2-sim -e '+str(SIMENGINE) + ' ' + str(MODULES) + ' -n ' + str(NSIGEVENTS) + ' -j ' + str(NWORKERS) + ' -g extgen \
211+
# --configFile ${O2DPG_ROOT}/MC/config/PWGHF/ini/GeneratorHF.ini \
212+
# --configKeyValues \"GeneratorPythia8.config=pythia8_'+ str(tf) +'.cfg\"' \
213+
# + ' -o ' + signalprefix + ' ' + embeddinto
214+
SGNtask['cmd']='o2-sim -e ' + str(SIMENGINE) + ' ' + str(MODULES) + ' -n ' + str(NSIGEVENTS) + ' -j ' + str(NWORKERS) + ' -g ' + str(GENERATOR) + ' ' + str(TRIGGER)+ ' ' + str(CONFKEY) + ' ' + str(INIFILE) + ' -o ' + signalprefix + ' ' + embeddinto
215+
workflow['stages'].append(SGNtask)
216+
217+
# some tasks further below still want geometry + grp in fixed names, so we provide it here
218+
# Alternatively, since we have timeframe isolation, we could just work with standard o2sim_ files
219+
LinkGRPFileTask=createTask(name='linkGRP_'+str(tf), needs=[SGNtask['name']], tf=tf, cwd=timeframeworkdir)
220+
LinkGRPFileTask['cmd']='ln -s ' + signalprefix + '_grp.root o2sim_grp.root ; ln -s ' + signalprefix + '_geometry.root o2sim_geometry.root'
221+
workflow['stages'].append(LinkGRPFileTask)
222+
223+
224+
CONTEXTFILE='collisioncontext.root'
225+
226+
simsoption=' --sims ' + ('bkg,'+signalprefix if doembedding else signalprefix)
227+
TPCDigitask=createTask(name='tpcdigi_'+str(tf), needs=[SGNtask['name'], LinkGRPFileTask['name']], tf=tf, cwd=timeframeworkdir)
228+
TPCDigitask['cmd'] = 'o2-sim-digitizer-workflow ' + getDPL_global_options() + ' -n ' + str(args.ns) + simsoption + ' --onlyDet TPC --interactionRate 50000 --tpc-lanes ' + str(NWORKERS) + ' --outcontext ' + str(CONTEXTFILE)
229+
workflow['stages'].append(TPCDigitask)
230+
231+
# The TRD digi task has a dependency on TPC only because of the digitization context (and because they both use CPU efficiently)
232+
# TODO: activate only if TRD present
233+
TRDDigitask=createTask(name='trddigi_'+str(tf), needs=[TPCDigitask['name']], tf=tf, cwd=timeframeworkdir)
234+
TRDDigitask['cmd'] = 'o2-sim-digitizer-workflow ' + getDPL_global_options() + ' -n ' + str(args.ns) + simsoption + ' --onlyDet TRD --interactionRate 50000 --configKeyValues \"TRDSimParams.digithreads=' + str(NWORKERS) + '\" --incontext ' + str(CONTEXTFILE)
235+
workflow['stages'].append(TRDDigitask)
236+
237+
RESTDigitask=createTask(name='restdigi_'+str(tf), needs=[TPCDigitask['name'], LinkGRPFileTask['name']], tf=tf, cwd=timeframeworkdir)
238+
RESTDigitask['cmd'] = 'o2-sim-digitizer-workflow ' + getDPL_global_options() + ' -n ' + str(args.ns) + simsoption + ' --skipDet TRD,TPC --interactionRate 50000 --incontext ' + str(CONTEXTFILE)
239+
workflow['stages'].append(RESTDigitask)
240+
241+
# -----------
242+
# reco
243+
# -----------
244+
245+
# TODO: check value for MaxTimeBin; A large value had to be set tmp in order to avoid crashes bases on "exceeding timeframe limit"
246+
TPCRECOtask=createTask(name='tpcreco_'+str(tf), needs=[TPCDigitask['name']], tf=tf, cwd=timeframeworkdir)
247+
TPCRECOtask['cmd'] = 'o2-tpc-reco-workflow ' + getDPL_global_options() + ' --tpc-digit-reader "--infile tpcdigits.root" --input-type digits --output-type clusters,tracks,send-clusters-per-sector --configKeyValues "GPU_global.continuousMaxTimeBin=100000;GPU_proc.ompThreads='+str(NWORKERS)+'"'
248+
workflow['stages'].append(TPCRECOtask)
249+
250+
ITSRECOtask=createTask(name='itsreco_'+str(tf), needs=[RESTDigitask['name']], tf=tf, cwd=timeframeworkdir)
251+
ITSRECOtask['cmd'] = 'o2-its-reco-workflow --trackerCA --tracking-mode async ' + getDPL_global_options()
252+
workflow['stages'].append(ITSRECOtask)
253+
254+
FT0RECOtask=createTask(name='ft0reco_'+str(tf), needs=[RESTDigitask['name']], tf=tf, cwd=timeframeworkdir)
255+
FT0RECOtask['cmd'] = 'o2-ft0-reco-workflow ' + getDPL_global_options()
256+
workflow['stages'].append(FT0RECOtask)
257+
258+
ITSTPCMATCHtask=createTask(name='itstpcMatch_'+str(tf), needs=[TPCRECOtask['name'], ITSRECOtask['name']], tf=tf, cwd=timeframeworkdir)
259+
ITSTPCMATCHtask['cmd']= 'o2-tpcits-match-workflow ' + getDPL_global_options() + ' --tpc-track-reader \"tpctracks.root\" --tpc-native-cluster-reader \"--infile tpc-native-clusters.root\"'
260+
workflow['stages'].append(ITSTPCMATCHtask)
261+
262+
# this can be combined with TRD digitization if benefical
263+
TRDTRAPtask = createTask(name='trdtrap_'+str(tf), needs=[TRDDigitask['name']], tf=tf, cwd=timeframeworkdir)
264+
TRDTRAPtask['cmd'] = 'o2-trd-trap-sim'
265+
workflow['stages'].append(TRDTRAPtask)
266+
267+
TRDTRACKINGtask = createTask(name='trdreco_'+str(tf), needs=[TRDTRAPtask['name'], ITSTPCMATCHtask['name'], TPCRECOtask['name'], ITSRECOtask['name']], tf=tf, cwd=timeframeworkdir)
268+
TRDTRACKINGtask['cmd'] = 'o2-trd-global-tracking'
269+
workflow['stages'].append(TRDTRACKINGtask)
270+
271+
TOFRECOtask = createTask(name='tofmatch_'+str(tf), needs=[ITSTPCMATCHtask['name'], RESTDigitask['name']], tf=tf, cwd=timeframeworkdir)
272+
TOFRECOtask['cmd'] = 'o2-tof-reco-workflow ' + getDPL_global_options()
273+
workflow['stages'].append(TOFRECOtask)
274+
275+
PVFINDERtask = createTask(name='pvfinder_'+str(tf), needs=[ITSTPCMATCHtask['name'], FT0RECOtask['name']], tf=tf, cwd=timeframeworkdir)
276+
PVFINDERtask['cmd'] = 'o2-primary-vertexing-workflow ' + getDPL_global_options()
277+
workflow['stages'].append(PVFINDERtask)
278+
279+
# -----------
280+
# produce AOD
281+
# -----------
282+
283+
# enable later. It still has memory access problems
284+
# taskwrapper aod_${tf}.log o2-aod-producer-workflow --aod-writer-keep dangling --aod-writer-resfile "AO2D" --aod-writer-resmode UPDATE --aod-timeframe-id ${tf} $gloOpt
285+
AODtask = createTask(name='aod_'+str(tf), needs=[PVFINDERtask['name'], TOFRECOtask['name'], TRDTRACKINGtask['name']], tf=tf, cwd=timeframeworkdir)
286+
AODtask['cmd'] = ' echo "Would do AOD (enable later)" '
287+
workflow['stages'].append(AODtask)
288+
289+
# cleanup step for this timeframe (we cleanup disc space early so as to make possible checkpoint dumps smaller)
290+
CLEANUPtask = createTask(name='cleanup_'+str(tf), needs=[AODtask['name']], tf=tf, cwd=timeframeworkdir)
291+
CLEANUPtask['cmd'] = ' echo "Doing cleanup" '
292+
workflow['stages'].append(CLEANUPtask)
293+
294+
295+
def trimString(cmd):
296+
return ' '.join(cmd.split())
297+
298+
# insert taskwrapper stuff
299+
for s in workflow['stages']:
300+
s['cmd']='. ${O2_ROOT}/share/scripts/jobutils.sh; taskwrapper ' + s['name']+'.log \'' + s['cmd'] + '\''
301+
302+
# remove whitespaces etc
303+
for s in workflow['stages']:
304+
s['cmd']=trimString(s['cmd'])
305+
306+
307+
# write workflow to json
308+
workflowfile=args.o
309+
with open(workflowfile, 'w') as outfile:
310+
json.dump(workflow, outfile, indent=2)
311+
312+
exit (0)

0 commit comments

Comments
 (0)