Belle II Software development
latexReporting.py
1#!/usr/bin/env python3
2
3
10
11"""
12 Call this "python3 fei/latexReporting.py summary.tex"
13 in a directory containing the monitoring output of the FEI
14 It will create a latex document containing a summary and plots
15 and tries to compile this summary.tex into a PDF file summary.pdf
16
17 You can improve / modify this script
18 E.g. If you want to add new plots:
19 Add your plot in the monitoring.py file
20 Add your plot below using b2latex.Graphics
21"""
22
23
24from fei import monitoring
25from fei.core import get_stages_from_particles
26
27from B2Tools import b2latex
28from B2Tools import format
29
30import sys
31import glob
32
33
34def create_latex(output_file, monitoringParticle):
35 # Create latex file
36 o = b2latex.LatexFile()
37
38 o += b2latex.TitlePage(title='Full Event Interpretation Report',
39 authors=['Thomas Keck', 'Christian Pulvermacher', 'William Sutcliffe'],
40 abstract=r"""
41 This report contains key performance indicators and control plots of the Full Event Interpretation.
42 The pre-, and post-cuts as well as trained multivariate selection methods are described.
43 Furthermore the resulting purities and efficiencies are stated.
44 """,
45 add_table_of_contents=True).finish()
46
47 o += b2latex.Section("Summary").finish()
48 o += b2latex.String(r"""
49 For each decay channel of each particle a multivariate selection method is trained after applying
50 a fast pre-cut on the candidates. Afterwards, a post-cut is applied on the signal probability calculated by the method.
51 This reduces combinatorics in the following stages of the Full
52 Event Interpretation.
53 """).finish()
54
55 table = b2latex.LongTable(columnspecs=r'c|rr|rrrrrr',
56 caption='Per-particle efficiency before and after the applied pre- and post-cut.',
57 head=r'Particle & \multicolumn{2}{c}{Covered BR} '
58 r' & \multicolumn{3}{c}{pre-cut} & \multicolumn{3}{c}{post-cut} \\'
59 r' & exc & inc & user & ranking & vertex '
60 r' & absolute & ranking & unique',
61 format_string=r'{name} & {exc_br} & {inc_br} & {user_pre_cut:.3f} & {ranking_pre_cut:.3f}'
62 r' & {vertex_pre_cut:.3f} & {absolute_post_cut:.3f}'
63 r' & {ranking_post_cut:.3f} & {after_tag:.3f}')
64 for p in monitoringParticle:
65 table.add(name=format.decayDescriptor(p.particle.identifier),
66 exc_br=f"{sum(p.exc_br_per_channel.values()):.3f}",
67 inc_br=f"{sum(p.inc_br_per_channel.values()):.3f}",
68 user_pre_cut=sum(p.before_ranking.values()).efficiency,
69 ranking_pre_cut=sum(p.after_ranking.values()).efficiency,
70 vertex_pre_cut=sum(p.after_vertex.values()).efficiency,
71 absolute_post_cut=p.before_ranking_postcut.efficiency,
72 ranking_post_cut=p.after_ranking_postcut.efficiency,
73 after_tag=p.after_tag.efficiency)
74 table.add(name="",
75 exc_br="",
76 inc_br="",
77 user_pre_cut=sum(p.before_ranking.values()).efficiency,
78 ranking_pre_cut=sum(p.after_ranking.values()).nSig / sum(p.before_ranking.values()).nSig,
79 vertex_pre_cut=sum(p.after_vertex.values()).nSig / sum(p.after_ranking.values()).nSig,
80 absolute_post_cut=p.before_ranking_postcut.nSig / sum(p.after_vertex.values()).nSig,
81 ranking_post_cut=p.after_ranking_postcut.nSig / p.before_ranking_postcut.nSig,
82 after_tag=p.after_tag.nSig / p.after_ranking_postcut.nSig)
83 o += table.finish()
84
85 table = b2latex.LongTable(columnspecs=r'c|rrrrrr',
86 caption='Per-particle nSignal before and after the applied pre- and post-cut.',
87 head=r'Particle '
88 r' & \multicolumn{3}{c}{pre-cut} & \multicolumn{3}{c}{post-cut} \\'
89 r' & user & ranking & vertex '
90 r' & absolute & ranking & unique',
91 format_string=r'{name} & {user_pre_cut:.4e} & {ranking_pre_cut:.4e}'
92 r' & {vertex_pre_cut:.4e} & {absolute_post_cut:.4e}'
93 r' & {ranking_post_cut:.4e} & {after_tag:.4e}')
94 for p in monitoringParticle:
95 table.add(name=format.decayDescriptor(p.particle.identifier),
96 user_pre_cut=sum(p.before_ranking.values()).nSig,
97 ranking_pre_cut=sum(p.after_ranking.values()).nSig,
98 vertex_pre_cut=sum(p.after_vertex.values()).nSig,
99 absolute_post_cut=p.before_ranking_postcut.nSig,
100 ranking_post_cut=p.after_ranking_postcut.nSig,
101 after_tag=p.after_tag.nSig)
102 o += table.finish()
103
104 table = b2latex.LongTable(columnspecs=r'c|rrrrrr',
105 caption='Per-particle purity before and after the applied pre- and post-cut.',
106 head=r'Particle '
107 r' & \multicolumn{3}{c}{pre-cut} & \multicolumn{3}{c}{post-cut} \\'
108 r' & user & ranking & vertex '
109 r' & absolute & ranking & unique',
110 format_string=r'{name} & {user_pre_cut:.3f} & {ranking_pre_cut:.3f}'
111 r' & {vertex_pre_cut:.3f} & {absolute_post_cut:.3f}'
112 r' & {ranking_post_cut:.3f} & {after_tag:.3f}')
113
114 for p in monitoringParticle:
115 table.add(name=format.decayDescriptor(p.particle.identifier),
116 user_pre_cut=sum(p.before_ranking.values()).purity,
117 ranking_pre_cut=sum(p.after_ranking.values()).purity,
118 vertex_pre_cut=sum(p.after_vertex.values()).purity,
119 absolute_post_cut=p.before_ranking_postcut.purity,
120 ranking_post_cut=p.after_ranking_postcut.purity,
121 after_tag=p.after_tag.purity)
122 o += table.finish()
123
124 table = b2latex.LongTable(columnspecs=r'c|rrrrrr',
125 caption='Per-particle nBackground before and after the applied pre- and post-cut.',
126 head=r'Particle '
127 r' & \multicolumn{3}{c}{pre-cut} & \multicolumn{3}{c}{post-cut} \\'
128 r' & user & ranking & vertex '
129 r' & absolute & ranking & unique',
130 format_string=r'{name} & {user_pre_cut:.4e} & {ranking_pre_cut:.4e}'
131 r' & {vertex_pre_cut:.4e} & {absolute_post_cut:.4e}'
132 r' & {ranking_post_cut:.4e} & {after_tag:.4e}')
133 for p in monitoringParticle:
134 table.add(name=format.decayDescriptor(p.particle.identifier),
135 user_pre_cut=sum(p.before_ranking.values()).nBg,
136 ranking_pre_cut=sum(p.after_ranking.values()).nBg,
137 vertex_pre_cut=sum(p.after_vertex.values()).nBg,
138 absolute_post_cut=p.before_ranking_postcut.nBg,
139 ranking_post_cut=p.after_ranking_postcut.nBg,
140 after_tag=p.after_tag.nBg)
141 o += table.finish()
142
143 # If you change the number of colors, than change below \ifnum5 accordingly
144 moduleTypes = ['ParticleCombiner', 'MVAExpert', 'MCMatch', 'ParticleVertexFitter', 'BestCandidateSelection', 'Other']
145
146 o += b2latex.Section("CPU time").finish()
147 colour_list = b2latex.DefineColourList()
148 o += colour_list.finish()
149
150 for p in monitoringParticle:
151 o += b2latex.SubSection(format.decayDescriptor(p.particle.identifier)).finish()
152
153 table = b2latex.LongTable(columnspecs=r'lrcrr',
154 caption='Total CPU time spent in event() calls for each channel. Bars show ' +
155 ', '.join(f'\\textcolor{{{c}}}{{{m}}}'
156 for c, m in zip(colour_list.colours, moduleTypes)) +
157 ', in this order. Does not include I/O, initialisation, training, post-cuts etc.',
158 head=r'Decay & CPU time & by module & per (true) candidate & Relative time ',
159 format_string=r'{name} & {time} & {bargraph} & {timePerCandidate} & {timePercent:.2f}\% ')
160
161 tt_channel = sum(p.module_statistic.channel_time.values())
162 tt_particle = p.module_statistic.particle_time + sum(p.module_statistic.channel_time.values())
163 fraction = tt_channel / tt_particle * 100 if tt_particle > 0 else 0.0
164
165 for channel in p.particle.channels:
166 time = p.time_per_channel[channel.label]
167 trueCandidates = p.after_classifier[channel.label].nSig
168 allCandidates = p.after_classifier[channel.label].nTotal
169
170 if trueCandidates == 0 or allCandidates == 0:
171 continue
172
173 timePerCandidate = format.duration(time / trueCandidates) + ' (' + format.duration(time / allCandidates) + ')'
174 # timePercent = time / tt_particle * 100 if tt_particle > 0 else 0 # is this redundant?
175
176 percents = [p.module_statistic.channel_time_per_module[channel.label].get(key, 0.0) / float(time) * 100.0
177 if time > 0 else 0.0 for key in moduleTypes[:-1]]
178 percents.append(100.0 - sum(percents))
179
180 table.add(name=format.decayDescriptor(channel.label),
181 bargraph=r'\plotbar{ %g/, %g/, %g/, %g/, %g/, %g/, }' % tuple(percents),
182 time=format.duration(time),
183 timePerCandidate=timePerCandidate,
184 timePercent=time / tt_particle * 100 if p.total_time > 0 else 0)
185
186 o += table.finish(tail=f'Total & & {format.duration(tt_channel)} / {format.duration(tt_particle)} & & {fraction:.2f}')
187
188 for p in monitoringParticle:
189 print(p.particle.identifier)
190
191 o += b2latex.Section(format.decayDescriptor(p.particle.identifier)).finish()
192 string = b2latex.String(r"In the reconstruction of {name} {nChannels} out of {max_nChannels} possible channels were used. "
193 r"The covered inclusive / exclusive branching fractions is {inc_br:.5f} / {exc_br:.5f}."
194 r"The final unique efficiency and purity was {eff:.5f} / {pur:.5f}")
195
196 o += string.finish(name=format.decayDescriptor(p.particle.identifier),
197 nChannels=p.reconstructed_number_of_channels,
198 max_nChannels=p.total_number_of_channels,
199 exc_br=sum(p.exc_br_per_channel.values()),
200 inc_br=sum(p.inc_br_per_channel.values()),
201 eff=p.after_tag.efficiency,
202 pur=p.after_tag.purity)
203
204 roc_plot_filename = monitoring.removeJPsiSlash(p.particle.identifier + '_ROC')
205 monitoring.MonitorROCPlot(p, roc_plot_filename)
206 o += b2latex.Graphics().add(roc_plot_filename + '.png', width=0.8).finish()
207
208 diag_plot_filename = monitoring.removeJPsiSlash(p.particle.identifier + '_Diag')
209 monitoring.MonitorDiagPlot(p, diag_plot_filename)
210 o += b2latex.Graphics().add(diag_plot_filename + '.png', width=0.8).finish()
211
212 sigprob_plot_filename = monitoring.removeJPsiSlash(p.particle.identifier + '_SigProb')
213 monitoring.MonitorSigProbPlot(p, sigprob_plot_filename)
214 o += b2latex.Graphics().add(sigprob_plot_filename + '.png', width=0.8).finish()
215
216 for spectator in p.particle.mvaConfig.spectators.keys():
217 money_plot_filename = monitoring.removeJPsiSlash(p.particle.identifier + '_' + spectator + '_Money')
218 monitoring.MonitorSpectatorPlot(p, spectator, money_plot_filename, p.particle.mvaConfig.spectators[spectator])
219 g = b2latex.Graphics()
220 for filename in glob.glob(money_plot_filename + '_*.png'):
221 g.add(filename, width=0.49)
222 o += g.finish()
223
224 table = b2latex.LongTable(columnspecs=r'c|rr|rrr',
225 caption='Per-channel efficiency before and after the applied pre-cut.',
226 head=r'Particle & \multicolumn{2}{c}{Covered BR} '
227 r' & \multicolumn{3}{c}{pre-cut} \\'
228 r' & exc & inc & user & ranking & vertex ',
229 format_string=r'{name} & {exc_br} & {inc_br} & {user_pre_cut:.5f} & '
230 r'{ranking_pre_cut:.5f} & {vertex_pre_cut:.5f}')
231
232 for channel in p.particle.channels:
233 table.add(name=format.decayDescriptor(channel.label),
234 exc_br=f"{p.exc_br_per_channel[channel.label]:.3f}",
235 inc_br=f"{p.inc_br_per_channel[channel.label]:.3f}",
236 user_pre_cut=p.before_ranking[channel.label].efficiency,
237 ranking_pre_cut=p.after_ranking[channel.label].efficiency,
238 vertex_pre_cut=p.after_vertex[channel.label].efficiency,
239 absolute_post_cut=p.before_ranking_postcut.efficiency,
240 ranking_post_cut=p.after_ranking_postcut.efficiency,
241 after_tag=p.after_tag.efficiency)
242 table.add(name="",
243 exc_br="",
244 inc_br="",
245 user_pre_cut=p.before_ranking[channel.label].efficiency,
246 ranking_pre_cut=p.after_ranking[channel.label].nSig / p.before_ranking[channel.label].nSig,
247 vertex_pre_cut=p.after_vertex[channel.label].nSig / p.after_ranking[channel.label].nSig,
248 absolute_post_cut=p.before_ranking_postcut.nSig / p.after_vertex[channel.label].nSig,
249 ranking_post_cut=p.after_ranking_postcut.nSig / p.before_ranking_postcut.nSig,
250 after_tag=p.after_tag.nSig / p.after_ranking_postcut.nSig)
251 o += table.finish()
252
253 table = b2latex.LongTable(columnspecs=r'c|c|rrr',
254 caption='Per-channel purity before and after the applied pre-cut.',
255 head=r'Particle & Ignored '
256 r' & \multicolumn{3}{c}{pre-cut} \\'
257 r' && user & ranking & vertex ',
258 format_string=r'{name} & {ignored} & {user_pre_cut:.5f} & {ranking_pre_cut:.5f}'
259 r' & {vertex_pre_cut:.5f}')
260
261 for channel in p.particle.channels:
262 table.add(name=format.decayDescriptor(channel.label),
263 ignored=r'\textcolor{red}{$\blacksquare$}' if p.ignored_channels[channel.label] else '',
264 user_pre_cut=p.before_ranking[channel.label].purity,
265 ranking_pre_cut=p.after_ranking[channel.label].purity,
266 vertex_pre_cut=p.after_vertex[channel.label].purity)
267 o += table.finish()
268
269 table = b2latex.LongTable(columnspecs=r'c|rrr',
270 caption='Per-channel nSignal before and after the applied pre-cut.',
271 head=r'Particle '
272 r' & \multicolumn{3}{c}{pre-cut} \\'
273 r' & user & ranking & vertex ',
274 format_string=r'{name} & {user_pre_cut:.4e} & '
275 r'{ranking_pre_cut:.4e} & {vertex_pre_cut:.4e}')
276 for channel in p.particle.channels:
277 table.add(name=format.decayDescriptor(channel.label),
278 user_pre_cut=p.before_ranking[channel.label].nSig,
279 ranking_pre_cut=p.after_ranking[channel.label].nSig,
280 vertex_pre_cut=p.after_vertex[channel.label].nSig)
281 o += table.finish()
282
283 table = b2latex.LongTable(columnspecs=r'c|rrr',
284 caption='Per-channel nBackground before and after the applied pre-cut.',
285 head=r'Particle '
286 r' & \multicolumn{3}{c}{pre-cut} \\'
287 r' & user & ranking & vertex ',
288 format_string=r'{name} & {user_pre_cut:.4e} & '
289 r'{ranking_pre_cut:.4e} & {vertex_pre_cut:.4e}')
290 for channel in p.particle.channels:
291 table.add(name=format.decayDescriptor(channel.label),
292 user_pre_cut=p.before_ranking[channel.label].nBg,
293 ranking_pre_cut=p.after_ranking[channel.label].nBg,
294 vertex_pre_cut=p.after_vertex[channel.label].nBg)
295 o += table.finish()
296
297 o.save(output_file, compile=False)
298
299
300# =============================================================================
301if __name__ == '__main__':
302 try:
303 output_file = sys.argv[1]
304 except IndexError:
305 raise AttributeError("You have to supply the output tex file.")
306
307 particles, configuration = monitoring.load_config()
308 cache = configuration.cache
309 stages = get_stages_from_particles(particles)
310 monitoringParticle = []
311 for i in range(cache):
312 for particle in particles:
313 if particle in stages[i]:
314 print('FEI: latexReporting: ', i, particle.identifier)
315 monitoringParticle.append(monitoring.MonitoringParticle(particle))
316 create_latex(output_file, monitoringParticle)