Separate Scripts for the Snakemake Workflow#

For completeness, we here provide the separate analysis scripts called by the snakemake workflow. Existing scripts can be implemented with the script: directive in the snakefile with minimal adaptations. The input and output file paths, as well as the parameters for the corresponding rule can be retrieved in the scripts with snakemake.input, snakemake.output and snakemake.params.

Listing 3.11 offlineanalysis.py#
 1# @cond
 2import uproot
 3import matplotlib.pyplot as plt
 4
 5output_file = {"Mbc": snakemake.output.mbc_plot, "deltaE": snakemake.output.deltaE_plot}
 6BBdata = snakemake.input.data_BB
 7QQdata = snakemake.input.data_QQ
 8
 9treeName = 'BtoPiDtoKPiPi'
10some_variables = ["Mbc", "deltaE"]
11BBtuple = uproot.open(f"{BBdata}:{treeName}")
12QQtuple = uproot.open(f"{QQdata}:{treeName}")
13
14for var in some_variables:
15    plt.hist([QQtuple[var].array(), BBtuple[var].array()], label=["uudd Continuum", "mixed B mesons"], stacked=True)
16    plt.legend(loc='best')
17    plt.xlabel(f"{var} [GeV]")
18    plt.savefig(output_file[var], dpi=100)
19    plt.close()
20# @endcond
Listing 3.12 reconstruction.py#
 1# @cond
 2import json
 3import basf2 as b2
 4import modularAnalysis as ma
 5import vertex as vx
 6
 7runningOnMC = snakemake.params.runningOnMC
 8
 9inputjson = snakemake.input.inputfileList
10outname = snakemake.output[0]
11
12mypath = b2.create_path()
13with open(inputjson) as f:
14    inputMdstList = json.load(f)
15
16ma.inputMdstList(filelist=inputMdstList, path=mypath)
17
18ma.fillParticleList(decayString='K+:my',  cut="dr < 0.5 and abs(dz) < 3 and thetaInCDCAcceptance and kaonID > 0.01", path=mypath)
19ma.fillParticleList(decayString='pi+:my', cut="dr < 0.5 and abs(dz) < 3 and thetaInCDCAcceptance", path=mypath)
20
21ma.reconstructDecay(decayString="D-:K2Pi -> K+:my pi-:my pi-:my", cut="1.844 < M < 1.894", path=mypath)
22
23ma.reconstructDecay(decayString='B0:PiD-toK2Pi -> D-:K2Pi pi+:my', cut='5.0 < Mbc and abs(deltaE) < 1.0', path=mypath)
24vx.treeFit('B0:PiD-toK2Pi', 0, path=mypath, updateAllDaughters=False, ipConstraint=True, massConstraint=[411])
25
26if(runningOnMC):
27    ma.matchMCTruth(list_name='B0:PiD-toK2Pi', path=mypath)
28
29some_variables = ['Mbc', 'deltaE']
30ma.variablesToNtuple(decayString='B0:PiD-toK2Pi', variables=some_variables,
31                     filename=outname,  path=mypath, treename='BtoPiDtoKPiPi')
32
33b2.process(mypath)
34# @endcond
Listing 3.13 batchToTxt.py#
 1# @cond
 2import json
 3skimfiles = json.loads(open(snakemake.input.skim_dirs, "r").read())
 4outputfiles = snakemake.output
 5BatchesPerSkim = snakemake.params.BatchesPerSkim
 6
 7binwidth = int(len(skimfiles)/BatchesPerSkim)
 8if(binwidth < 1.):
 9    raise ValueError("Attempting to batching with binwidth smaller 1. Decrease the number of batches!")
10
11batches = {}
12for batch in range(BatchesPerSkim):
13    if(batch == BatchesPerSkim - 1):
14        batches.update({outputfiles[batch]: list(skimfiles[binwidth*batch:])})
15    else:
16        batches.update({outputfiles[batch]: list(skimfiles[binwidth*batch:binwidth*(batch+1)])})
17
18for key, file_list in batches.items():
19    with open(key, "w") as f:
20        f.write(json.dumps(file_list))
21# @endcond

When using our provided wrapper: for submitting gbasf2 jobs to the grid, the steering file does not need any specific adaptations.

Listing 3.14 skim.py#
 1# @cond
 2import basf2 as b2
 3import modularAnalysis as ma
 4import vertex as vx
 5from extravariables import runningOnMC, outputfile
 6import udst
 7
 8gbasf2_dataset = []  # gbasf2 will figure this out
 9
10mypath = b2.create_path()
11ma.inputMdstList(filelist=gbasf2_dataset, path=mypath, entrySequences=['0:10'])
12
13ma.fillParticleList(decayString='K+:my',  cut="dr < 0.5 and abs(dz) < 3 and thetaInCDCAcceptance and kaonID > 0.01", path=mypath)
14ma.fillParticleList(decayString='pi+:my', cut="dr < 0.5 and abs(dz) < 3 and thetaInCDCAcceptance", path=mypath)
15
16ma.reconstructDecay(decayString="D-:K2Pi -> K+:my pi-:my pi-:my", cut="1.5 < M < 2.2", path=mypath)
17
18ma.reconstructDecay(decayString='B0:PiD-toK2Pi -> D-:K2Pi pi+:my', cut='5.0 < Mbc and abs(deltaE) < 1.0', path=mypath)
19vx.treeFit('B0:PiD-toK2Pi', 0, path=mypath, updateAllDaughters=False, ipConstraint=True, massConstraint=[411])
20ma.applyCuts('B0:PiD-toK2Pi', '5.2 < Mbc and abs(deltaE) < 0.5', path=mypath)
21
22# dump in UDST format
23udst.add_skimmed_udst_output(
24    mypath,
25    skimDecayMode='BtoPiD',
26    skimParticleLists=['B0:PiD-toK2Pi'],
27    mc=runningOnMC,
28    outputFile=outputfile)
29b2.process(mypath)
30print(b2.statistics)
31# @endcond