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