Belle II Software development
basf2_mva_evaluate.py
1#!/usr/bin/env python3
2
3
10
11import basf2_mva_util
12
13from basf2_mva_evaluation import plotting
14from basf2 import conditions
15import argparse
16import tempfile
17
18import numpy as np
19from B2Tools import b2latex, format
20from basf2 import B2INFO
21
22import os
23import shutil
24import collections
25from typing import List, Any
26
27
28def get_argument_parser() -> argparse.ArgumentParser:
29 """ Parses the command line options of the fei and returns the corresponding arguments. """
30 parser = argparse.ArgumentParser()
31 parser.add_argument('-id', '--identifiers', dest='identifiers', type=str, required=True, action='append', nargs='+',
32 help='DB Identifier or weightfile')
33 parser.add_argument('-train', '--train_datafiles', dest='train_datafiles', type=str, required=False, action='append', nargs='+',
34 help='Data file containing ROOT TTree used during training')
35 parser.add_argument('-data', '--datafiles', dest='datafiles', type=str, required=True, action='append', nargs='+',
36 help='Data file containing ROOT TTree with independent test data')
37 parser.add_argument('-tree', '--treename', dest='treename', type=str, default='tree', help='Treename in data file')
38 parser.add_argument('-out', '--outputfile', dest='outputfile', type=str, default='output.zip',
39 help='Name of the created .zip archive file if not compiling or a pdf file if compilation is successful.')
40 parser.add_argument('-w', '--working_directory', dest='working_directory', type=str, default='',
41 help="""Working directory where the created images and root files are stored,
42 default is to create a temporary directory.""")
43 parser.add_argument('-l', '--localdb', dest='localdb', type=str, action='append', nargs='+', required=False,
44 help="""path or list of paths to local database(s) containing the mvas of interest.
45 The testing payloads are prepended and take precedence over payloads in global tags.""")
46 parser.add_argument('-g', '--globaltag', dest='globaltag', type=str, action='append', nargs='+', required=False,
47 help='globaltag or list of globaltags containing the mvas of interest. The globaltags are prepended.')
48 parser.add_argument('-n', '--fillnan', dest='fillnan', action='store_true',
49 help='Fill nan and inf values with actual numbers')
50 parser.add_argument('-c', '--compile', dest='compile', action='store_true',
51 help='Compile latex to pdf directly')
52 parser.add_argument('-a', '--abbreviation_length', dest='abbreviation_length',
53 action='store', type=int, default=10,
54 help='Number of characters to which variable names are abbreviated.')
55 parser.add_argument('-s', '--importance-scale', dest='importance_scale',
56 choices=['normalized', 'hundredzero'], default='normalized',
57 help='Scaling applied to importance values before plotting. '
58 '"normalized" (default): each column is divided by its sum and multiplied by 100; '
59 '"hundredzero": each column is rescaled so the minimum is 0 and the maximum is 100.')
60 return parser
61
62
63def unique(input_list: List[Any]) -> List[Any]:
64 """
65 Returns a list containing only unique elements, keeps the original order of the list
66 @param input_list list containing the elements
67 """
68 output = []
69 for x in input_list:
70 if x not in output:
71 output.append(x)
72 return output
73
74
75def flatten(input_list: List[List[Any]]) -> List[Any]:
76 """
77 Flattens a list of lists
78 @param input_list list of lists to be flattened
79 """
80 return [item for sublist in input_list for item in sublist]
81
82
83def smart_abbreviation(name):
84 shortName = name
85 shortName = shortName.replace("daughter", "d")
86 shortName = shortName.replace("Angle", "Ang")
87 shortName = shortName.replace("useCMSFrame", "")
88 shortName = shortName.replace("useLabFrame", "")
89 shortName = shortName.replace("useRestFrame", "")
90 shortName = shortName.replace("formula", "")
91 shortName = shortName.replace("(", "")
92 shortName = shortName.replace(")", "")
93 shortName = shortName.replace("conditionalVariableSelector", "")
94 shortName = shortName.replace(",", "")
95 shortName = shortName.replace(" ", "")
96 return shortName
97
98
99def create_abbreviations(names, length=5):
100 count = dict()
101 for name in names:
102 abbreviation = smart_abbreviation(name)[:length]
103 if abbreviation not in count:
104 count[abbreviation] = 0
105 count[abbreviation] += 1
106 abbreviations = collections.OrderedDict()
107
108 count2 = dict()
109 for name in names:
110 abbreviation = smart_abbreviation(name)[:length]
111 abbreviations[name] = abbreviation
112 if count[abbreviation] > 1:
113 if abbreviation not in count2:
114 count2[abbreviation] = 0
115 count2[abbreviation] += 1
116 abbreviations[name] += str(count2[abbreviation])
117 return abbreviations
118
119
120if __name__ == '__main__':
121
122 import ROOT # noqa
123 ROOT.PyConfig.IgnoreCommandLineOptions = True
124 ROOT.PyConfig.StartGuiThread = False
125 ROOT.gROOT.SetBatch(True)
126
127 old_cwd = os.getcwd()
128 parser = get_argument_parser()
129 args = parser.parse_args()
130
131 identifiers = flatten(args.identifiers)
132 identifier_abbreviations = create_abbreviations(identifiers, args.abbreviation_length)
133
134 datafiles = flatten(args.datafiles)
135 if args.localdb is not None:
136 for localdb in flatten(args.localdb):
137 conditions.prepend_testing_payloads(localdb)
138
139 if args.globaltag is not None:
140 for tag in flatten(args.globaltag):
141 conditions.prepend_globaltag(tag)
142
143 print("Load methods")
144 methods = [basf2_mva_util.Method(identifier) for identifier in identifiers]
145
146 print("Apply experts on independent data")
147 test_probability = {}
148 test_target = {}
149 for method in methods:
150 p, t = method.apply_expert(datafiles, args.treename)
151 test_probability[identifier_abbreviations[method.identifier]] = p
152 test_target[identifier_abbreviations[method.identifier]] = t
153
154 print("Apply experts on training data")
155 train_probability = {}
156 train_target = {}
157 if args.train_datafiles is not None:
158 train_datafiles = sum(args.train_datafiles, [])
159 for method in methods:
160 p, t = method.apply_expert(train_datafiles, args.treename)
161 train_probability[identifier_abbreviations[method.identifier]] = p
162 train_target[identifier_abbreviations[method.identifier]] = t
163
164 variables = unique(v for method in methods for v in method.variables)
165 variable_abbreviations = create_abbreviations(variables, args.abbreviation_length)
166 root_variables = unique(v for method in methods for v in method.root_variables)
167
168 spectators = unique(v for method in methods for v in method.spectators)
169 spectator_abbreviations = create_abbreviations(spectators, args.abbreviation_length)
170 root_spectators = unique(v for method in methods for v in method.root_spectators)
171
172 print("Load variables array")
173 rootchain = ROOT.TChain(args.treename)
174 rootchain_spec = ROOT.TChain(args.treename)
175 for datafile in datafiles:
176 rootchain.Add(datafile)
177 rootchain_spec.Add(datafile)
178
179 variables_data = basf2_mva_util.chain2dict(rootchain, root_variables, list(variable_abbreviations.values()))
180 spectators_data = basf2_mva_util.chain2dict(rootchain, root_spectators, list(spectator_abbreviations.values()))
181 varSpec_data = basf2_mva_util.chain2dict(
182 rootchain_spec,
183 root_variables +
184 root_spectators,
185 list(
186 variable_abbreviations.values()) +
187 list(
188 spectator_abbreviations.values()))
189
190 if train_probability:
191 rootchain_train = ROOT.TChain(args.treename)
192 rootchain_train_spec = ROOT.TChain(args.treename)
193 for train_datafile in train_datafiles:
194 rootchain_train.Add(train_datafile)
195 rootchain_train_spec.Add(train_datafile)
196 variables_train_data = basf2_mva_util.chain2dict(rootchain_train, root_variables, list(variable_abbreviations.values()))
197 spectators_train_data = basf2_mva_util.chain2dict(rootchain_train, root_spectators, list(spectator_abbreviations.values()))
198 varSpec_train_data = basf2_mva_util.chain2dict(
199 rootchain_train_spec,
200 root_variables +
201 root_spectators,
202 list(
203 variable_abbreviations.values()) +
204 list(
205 spectator_abbreviations.values()))
206
207 if args.fillnan:
208 for column in variable_abbreviations.values():
209 np.nan_to_num(variables_data[column], copy=False)
210 np.nan_to_num(varSpec_data[column], copy=False)
211 if train_probability:
212 np.nan_to_num(variables_train_data[column], copy=False)
213 np.nan_to_num(varSpec_train_data[column], copy=False)
214
215 for column in spectator_abbreviations.values():
216 np.nan_to_num(spectators_data[column], copy=False)
217 np.nan_to_num(varSpec_data[column], copy=False)
218 if train_probability:
219 np.nan_to_num(spectators_train_data[column], copy=False)
220 np.nan_to_num(varSpec_train_data[column], copy=False)
221
222 print("Create latex file")
223 # Change working directory after experts run, because they might want to access
224 # a localdb in the current working directory.
225 with tempfile.TemporaryDirectory() as tempdir:
226 if args.working_directory == '':
227 os.chdir(tempdir)
228 else:
229 os.chdir(args.working_directory)
230
231 with open('abbreviations.txt', 'w') as f:
232 f.write('Identifier Abbreviation : Identifier \n')
233 for name, abbrev in identifier_abbreviations.items():
234 f.write(f'\t{abbrev} : {name}\n')
235 f.write('\n\n\nVariable Abbreviation : Variable \n')
236 for name, abbrev in variable_abbreviations.items():
237 f.write(f'\t{abbrev} : {name}\n')
238 f.write('\n\n\nSpectator Abbreviation : Spectator \n')
239 for name, abbrev in spectator_abbreviations.items():
240 f.write(f'\t{abbrev} : {name}\n')
241
242 o = b2latex.LatexFile()
243 o += b2latex.TitlePage(title='Automatic MVA Evaluation',
244 authors=[r'Thomas Keck\\ Moritz Gelb\\ Nils Braun'],
245 abstract='Evaluation plots',
246 add_table_of_contents=True).finish()
247
248 o += b2latex.Section("Classifiers")
249 o += b2latex.String(r"""
250 This section contains the GeneralOptions and SpecificOptions of all classifiers represented by an XML tree.
251 The same information can be retrieved using the basf2\_mva\_info tool.
252 """)
253
254 table = b2latex.LongTable(r"ll", "Abbreviations of identifiers", "{name} & {abbr}", r"Identifier & Abbreviation")
255 for identifier in identifiers:
256 table.add(name=format.string(identifier), abbr=format.string(identifier_abbreviations[identifier]))
257 o += table.finish()
258
259 for method in methods:
260 o += b2latex.SubSection(format.string(method.identifier))
261 o += b2latex.Listing(language='XML').add(method.description).finish()
262
263 o += b2latex.Section("Variables")
264 importance_scale_description = {
265 'normalized': 'Each variable\'s importance is shown as a percentage of the total importance '
266 'for that method (column sums to 100), preserving relative magnitudes between variables.',
267 'hundredzero': 'Each column is rescaled so that the least important variable is 0 and the most '
268 'important is 100. Relative magnitudes between variables are not preserved.',
269 }
270 o += b2latex.String(f"""
271 This section contains an overview of the importance and correlation of the variables used by the classifiers.
272 And distribution plots of the variables on the independent dataset. The distributions are normed for signal and
273 background separately, and only the region +- 3 sigma around the mean is shown.
274
275 {importance_scale_description[args.importance_scale]}
276 If the method does not provide a ranking, all importances will be 0.
277 """)
278
279 table = b2latex.LongTable(r"ll", "Abbreviations of variables", "{name} & {abbr}", r"Variable & Abbreviation")
280 for v in variables:
281 table.add(name=format.string(v), abbr=format.string(variable_abbreviations[v]))
282 o += table.finish()
283
284 o += b2latex.SubSection("Importance")
285 graphics = b2latex.Graphics()
287 p.add({identifier_abbreviations[i.identifier]: np.array([i.importances.get(v, 0.0) for v in variables]) for i in methods},
288 identifier_abbreviations.values(), variable_abbreviations.values(),
289 importance_scale=args.importance_scale)
290 p.finish()
291 p.save('importance.pdf')
292 graphics.add('importance.pdf', width=1.0)
293 o += graphics.finish()
294
295 o += b2latex.SubSection("Correlation")
296 first_identifier_abbr = list(identifier_abbreviations.values())[0]
297 graphics = b2latex.Graphics()
299 p.add(variables_data, variable_abbreviations.values(),
300 test_target[first_identifier_abbr] == 1,
301 test_target[first_identifier_abbr] == 0)
302 p.finish()
303 p.save('correlation_plot.pdf')
304 graphics.add('correlation_plot.pdf', width=1.0)
305 o += graphics.finish()
306
307 if train_probability:
308 o += b2latex.SubSection("Correlation on Training Data")
309 graphics = b2latex.Graphics()
311 p.add(variables_train_data, variable_abbreviations.values(),
312 train_target[first_identifier_abbr] == 1,
313 train_target[first_identifier_abbr] == 0)
314 p.finish()
315 p.save('correlation_plot_train.pdf')
316 graphics.add('correlation_plot_train.pdf', width=1.0)
317 o += graphics.finish()
318
319 for v in variables:
320 variable_abbr = variable_abbreviations[v]
321 o += b2latex.SubSection(format.string(v))
322 graphics = b2latex.Graphics()
323 p = plotting.VerboseDistribution(normed=True, range_in_std=3, x_axis_label=v)
324 p.add(variables_data, variable_abbr, test_target[first_identifier_abbr] == 1, label="Sig")
325 p.add(variables_data, variable_abbr, test_target[first_identifier_abbr] == 0, label="Bkg")
326 if train_probability:
327 p.add(variables_train_data, variable_abbr, train_target[first_identifier_abbr] == 1, label="Sig_train")
328 p.add(variables_train_data, variable_abbr, train_target[first_identifier_abbr] == 0, label="Bkg_train")
329 p.finish()
330 p.save(f'variable_{variable_abbr}_{hash(v)}.pdf')
331 graphics.add(f'variable_{variable_abbr}_{hash(v)}.pdf', width=1.0)
332 o += graphics.finish()
333
334 o += b2latex.Section("Classifier Plot")
335 o += b2latex.String("This section contains the receiver operating characteristics (ROC), purity projection, ..."
336 "of the classifiers on training and independent data."
337 "The legend of each plot contains the shortened identifier and the area under the ROC curve"
338 "in parenthesis.")
339 plot_classes = [
345 ]
346
347 for plot_class in plot_classes:
348 # Start section for each plot
349 o += b2latex.Section(f"{plot_class.__name__} Plot")
350
351 graphics = b2latex.Graphics()
352 p = plot_class()
353 for i, identifier in enumerate(identifiers):
354 identifier_abbr = identifier_abbreviations[identifier]
355 p.add(
356 test_probability,
357 identifier_abbr,
358 test_target[identifier_abbr] == 1,
359 test_target[identifier_abbr] == 0,
360 label=identifier_abbr)
361 p.finish()
362 p.axis.set_title(f"{plot_class.__name__} Plot on independent data")
363 p.save(f'{plot_class.__name__.lower()}_plot_test.pdf')
364 graphics.add(f'{plot_class.__name__.lower()}_plot_test.pdf', width=1.0)
365 o += graphics.finish()
366
367 if train_probability:
368 for i, identifier in enumerate(identifiers):
369 graphics = b2latex.Graphics()
370 p = plot_class()
371 identifier_abbr = identifier_abbreviations[identifier]
372 p.add(train_probability, identifier_abbr, train_target[identifier_abbr] == 1,
373 train_target[identifier_abbr] == 0, label=f'Train {identifier_abbr}')
374 p.add(test_probability, identifier_abbr, test_target[identifier_abbr] == 1,
375 test_target[identifier_abbr] == 0, label=f'Test {identifier_abbr}')
376 p.finish()
377 p.axis.set_title(f"{plot_class.__name__} Plot for \n" + identifier)
378 p.save(f'{plot_class.__name__.lower()}_plot_{hash(identifier)}.pdf')
379 graphics.add(f'{plot_class.__name__.lower()}_plot_{hash(identifier)}.pdf', width=1.0)
380 o += graphics.finish()
381
382 o += b2latex.Section("Classification Results")
383 for identifier in identifiers:
384 identifier_abbr = identifier_abbreviations[identifier]
385 o += b2latex.SubSection(format.string(identifier_abbr))
386 graphics = b2latex.Graphics()
387 if train_probability:
389 else:
391 p.add(0, test_probability, identifier_abbr, test_target[identifier_abbr] == 1,
392 test_target[identifier_abbr] == 0, normed=True)
393 p.sub_plots[0].axis.set_title(f"Classification result in test data for \n{identifier}")
394
395 p.add(1, test_probability, identifier_abbr, test_target[identifier_abbr] == 1,
396 test_target[identifier_abbr] == 0, normed=False)
397 p.sub_plots[1].axis.set_title(f"Classification result in test data for \n{identifier}")
398
399 if train_probability:
400 p.add(2, train_probability, identifier_abbr, train_target[identifier_abbr] == 1,
401 train_target[identifier_abbr] == 0, normed=True)
402 p.sub_plots[2].axis.set_title(f"Classification result in training data for \n{identifier}")
403
404 p.add(3, train_probability, identifier_abbr, train_target[identifier_abbr] == 1,
405 train_target[identifier_abbr] == 0, normed=False)
406 p.sub_plots[3].axis.set_title(f"Classification result in training data for \n{identifier}")
407
408 p.figure.subplots_adjust(wspace=0.3, hspace=0.3)
409 p.finish()
410 p.save(f'classification_result_{hash(identifier)}.pdf')
411 graphics.add(f'classification_result_{hash(identifier)}.pdf', width=1)
412 o += graphics.finish()
413
414 o += b2latex.Section("Diagonal Plot")
415 graphics = b2latex.Graphics()
417 for identifier in identifiers:
418 identifier_abbr = identifier_abbreviations[identifier]
419 p.add(
420 test_probability,
421 identifier_abbr,
422 test_target[identifier_abbr] == 1,
423 test_target[identifier_abbr] == 0,
424 label=identifier_abbr)
425 p.finish()
426 p.axis.set_title("Diagonal plot on independent data")
427 p.save('diagonal_plot_test.pdf')
428 graphics.add('diagonal_plot_test.pdf', width=1.0)
429 o += graphics.finish()
430
431 if train_probability:
432 for identifier in identifiers:
433 identifier_abbr = identifier_abbreviations[identifier]
434 o += b2latex.SubSection(format.string(identifier_abbr))
435 graphics = b2latex.Graphics()
437
438 p.add(
439 train_probability,
440 identifier_abbr,
441 train_target[identifier_abbr] == 1,
442 train_target[identifier_abbr] == 0,
443 label='Train')
444 p.add(
445 test_probability,
446 identifier_abbr,
447 test_target[identifier_abbr] == 1,
448 test_target[identifier_abbr] == 0,
449 label='Test')
450
451 p.finish()
452 p.axis.set_title("Diagonal plot for \n" + identifier)
453 p.save(f'diagonal_plot_{hash(identifier)}.pdf')
454 graphics.add(f'diagonal_plot_{hash(identifier)}.pdf', width=1.0)
455 o += graphics.finish()
456
457 if train_probability:
458 o += b2latex.Section("Overtraining Plot")
459 for identifier in identifiers:
460 identifier_abbr = identifier_abbreviations[identifier]
461 probability = {identifier_abbr: np.r_[train_probability[identifier_abbr], test_probability[identifier_abbr]]}
462 target = np.r_[train_target[identifier_abbr], test_target[identifier_abbr]]
463 train_mask = np.r_[np.ones(len(train_target[identifier_abbr])), np.zeros(len(test_target[identifier_abbr]))]
464 graphics = b2latex.Graphics()
466 p.add(probability, identifier_abbr,
467 train_mask == 1, train_mask == 0,
468 target == 1, target == 0, )
469 p.finish()
470 p.axis.set_title(f"Overtraining check for \n{identifier}")
471 p.save(f'overtraining_plot_{hash(identifier)}.pdf')
472 graphics.add(f'overtraining_plot_{hash(identifier)}.pdf', width=1.0)
473 o += graphics.finish()
474
475 o += b2latex.Section("Spectators")
476 o += b2latex.String("This section contains the distribution and dependence on the"
477 "classifier outputs of all spectator variables.")
478
479 table = b2latex.LongTable(r"ll", "Abbreviations of spectators", "{name} & {abbr}", r"Spectator & Abbreviation")
480 for s in spectators:
481 table.add(name=format.string(s), abbr=format.string(spectator_abbreviations[s]))
482 o += table.finish()
483
484 for spectator in spectators:
485 spectator_abbr = spectator_abbreviations[spectator]
486 o += b2latex.SubSection(format.string(spectator))
487 graphics = b2latex.Graphics()
489 p.add(spectators_data, spectator_abbr, test_target[first_identifier_abbr] == 1, label="Sig")
490 p.add(spectators_data, spectator_abbr, test_target[first_identifier_abbr] == 0, label="Bkg")
491 if train_probability:
492 p.add(spectators_train_data, spectator_abbr, train_target[first_identifier_abbr] == 1, label="Sig_train")
493 p.add(spectators_train_data, spectator_abbr, train_target[first_identifier_abbr] == 0, label="Bkg_train")
494 p.finish()
495 p.save(f'spectator_{spectator_abbr}_{hash(spectator)}.pdf')
496 graphics.add(f'spectator_{spectator_abbr}_{hash(spectator)}.pdf', width=1.0)
497 o += graphics.finish()
498
499 for identifier in identifiers:
500 o += b2latex.SubSubSection(format.string(spectator) + " with classifier " + format.string(identifier))
501 identifier_abbr = identifier_abbreviations[identifier]
502 data = {identifier_abbr: test_probability[identifier_abbr], spectator_abbr: spectators_data[spectator_abbr]}
503 graphics = b2latex.Graphics()
505 p.add(data, spectator_abbr, identifier_abbr, list(range(10, 100, 10)),
506 test_target[identifier_abbr] == 1,
507 test_target[identifier_abbr] == 0)
508 p.figure.subplots_adjust(hspace=0.5)
509 p.finish()
510 p.save(f'correlation_plot_{spectator_abbr}_{hash(spectator)}_{hash(identifier)}.pdf')
511 graphics.add(f'correlation_plot_{spectator_abbr}_{hash(spectator)}_{hash(identifier)}.pdf', width=1.0)
512 o += graphics.finish()
513
514 if train_probability:
515 o += b2latex.SubSubSection(format.string(spectator) + " with classifier " +
516 format.string(identifier) + " on training data")
517 data = {identifier_abbr: train_probability[identifier_abbr],
518 spectator_abbr: spectators_train_data[spectator_abbr]}
519 graphics = b2latex.Graphics()
521 p.add(data, spectator_abbr, identifier_abbr, list(range(10, 100, 10)),
522 train_target[identifier_abbr] == 1,
523 train_target[identifier_abbr] == 0)
524 p.figure.subplots_adjust(hspace=0.5)
525 p.finish()
526 p.save(f'correlation_plot_{spectator_abbr}_{hash(spectator)}_{hash(identifier)}_train.pdf')
527 graphics.add(f'correlation_plot_{spectator_abbr}_{hash(spectator)}_{hash(identifier)}_train.pdf', width=1.0)
528 o += graphics.finish()
529
530 if len(spectators) > 0:
531 o += b2latex.SubSection("Correlation of Spectators")
532 first_identifier_abbr = list(identifier_abbreviations.values())[0]
533 graphics = b2latex.Graphics()
535 p.add(
536 varSpec_data,
537 list(variable_abbreviations.values()) + list(spectator_abbreviations.values()),
538 test_target[first_identifier_abbr] == 1,
539 test_target[first_identifier_abbr] == 0
540 )
541 p.finish()
542 p.save('correlation_spec_plot.pdf')
543 graphics.add('correlation_spec_plot.pdf', width=1.0)
544 o += graphics.finish()
545
546 if train_probability:
547 o += b2latex.SubSection("Correlation of Spectators on Training Data")
548 graphics = b2latex.Graphics()
550 p.add(
551 varSpec_train_data,
552 list(variable_abbreviations.values()) + list(spectator_abbreviations.values()),
553 train_target[first_identifier_abbr] == 1,
554 train_target[first_identifier_abbr] == 0
555 )
556 p.finish()
557 p.save('correlation_spec_plot_train.pdf')
558 graphics.add('correlation_spec_plot_train.pdf', width=1.0)
559 o += graphics.finish()
560
561 if args.compile:
562 B2INFO(f"Creating a PDF file at {args.outputfile}. Please remove the '-c' switch if this fails.")
563 o.save('latex.tex', compile=True)
564 else:
565 B2INFO(f"Creating a .zip archive containing plots and a TeX file at {args.outputfile}."
566 f"Please unpack the archive and compile the latex.tex file with pdflatex.")
567 o.save('latex.tex', compile=False)
568
569 os.chdir(old_cwd)
570 if args.working_directory == '':
571 working_directory = tempdir
572 else:
573 working_directory = args.working_directory
574
575 if args.compile:
576 shutil.copy(os.path.join(working_directory, 'latex.pdf'), args.outputfile)
577 else:
578 base_name = os.path.join(old_cwd, args.outputfile.rsplit('.', 1)[0])
579 shutil.make_archive(base_name, 'zip', working_directory)
chain2dict(chain, tree_columns, dict_columns=None, max_entries=None)