Computation Results in LaTeX
For my laboratory reports, I like to write Python programs that do all the calculations and plotting for me. To get the results from the program into my LaTeX document, I use a Python templating system, Jinja.
Over the time, I have refined this a bit. You can only read the first section if you want to get started quickly.
The basic version
So the report might look like so:
We measured the voltage $U = \SI{<< u_val >> \pm << u_err >>}{\volt}$ and the current $I = \SI{<< i_val >> \pm << i_err >>}{\ampere}$. Then we computed the resistance $R = \SI{<< r_val >> \pm << "%.2f" % r_err >>}{\ohm}$. All our measurements: \begin{table}[h] \begin{tabular}{SS} {$U$} & {$I$} \\ \hline %< for u, i in data: ->% \SI{<< u >>}\volt & \SI{<< i >>}\ampere \\ %< endfor ->% \end{tabular} \end{table}
I changed the default variable delimiters from {{
and }}
to <<
and >>
.
And the {%
and %}
became %<
and >%
. That way, at least the latter will
become plain comments.
Now in the Python program, I will do my calculation in the namespace of the
main
method. The template is then rendered with **locals()
, which gives the
complete local namespace to the template. This is probably not very clean, but
I can use the exact same variables in the document.
import jinja2 import math def main(): # Setting up Jinja env = jinja2.Environment( "%<", ">%", "<<", ">>", "[§", "§]", loader=jinja2.FileSystemLoader(".") ) template = env.get_template("article.tex") # Measurements. u_val = 6.2 u_err = 0.1 i_val = 2.0 i_err = 0.1 data = [ (3, 4), (1, 4.0), (5, 1), ] # Calculations r_val = u_val / i_val r_err = math.sqrt( (1/i_val * u_err)**2 + (u_val/i_val**2 * i_err)**2 ) # Rendering LaTeX document with values. with open("out.tex", "w") as f: f.write(template.render(**locals())) if __name__ == "__main__": main()
The result in out.tex
looks like so:
We measured the voltage $U = \SI{6.3 \pm 0.1}{\volt}$ and the current $I = \SI{2.0 \pm 0.1}{\ampere}$. Then we computed the resistance $R = \SI{3.15 \pm 0.17}{\ohm}$. All our measurements: \begin{table}[h] \begin{tabular}{SS} {$U$} & {$I$} \\ \hline \SI{3}\volt & \SI{4.0}\ampere \\ \SI{1}\volt & \SI{4.0}\ampere \\ \SI{5}\volt & \SI{1.0}\ampere \\ \SI{7}\volt & \SI{4.0}\ampere \\ \end{tabular} \end{table}
Together with a small makefile
:
out.pdf: out.tex latexmk -pdf $< out.tex: python.py article.tex python python.py
Now I can just type make
and get all calculations done and put into the final
report.
Free up namespace
Putting all the local variables into the template seemed like a good idea at
first. I realized later that I often want to convert things to other units or
scales before putting them into the document. So I introduced a template
dictionary T
that is filled up by the computations. This has the advantage
that I can split up the calculation into functions.
Previously the rendering was:
f.write(template.render(**locals()))
Now it is:
f.write(template.render(**T))
Speed up compilation
The problem that soon emerged was that the Python program would soon take more than ten seconds to compute. Changing something in the LaTeX document would require the Python program to re-run just to insert the same results into the template.
Therefore, I now store the results of the computation in a JSON file:
with open('_build/template.js', 'w') as f: json.dump(dict(T), f, indent=4, sort_keys=True)
There is an extra program called insert
which only inserts the values from
the JSON file into the template:
#!/usr/bin/python3 # -*- coding: utf-8 -*- # Copyright © 2013-2014 Martin Ueding <mu@martin-ueding.de> # Licensed under The GNU Public License Version 2 (or later) import argparse import json import jinja2 def render_template(template_fn, data_fn, output_fn): # Setting up Jinja env = jinja2.Environment( "%<", ">%", "<<", ">>", "/*", "*/", loader=jinja2.FileSystemLoader(".") ) template = env.get_template(template_fn) with open(data_fn) as handle: data = json.load(handle) # Rendering LaTeX document with values. with open(output_fn, "w") as handle: handle.write(template.render(**data)) def main(): parser = argparse.ArgumentParser(description="") parser.add_argument("template", help="LaTeX template with Jinja 2") parser.add_argument("data", help="JSON encoded data file") parser.add_argument("output", help="Output LaTeX File") options = parser.parse_args() render_template(options.template, options.data, options.output) if __name__ == '__main__': main()
This simplifies the build process since the computations and the template
rendering are two separate steps. The Python program that does all the
calculations is called crunch
, the template is Template.tex
. The results
are stored in template.js
.
Document.tex: Template.tex template.js insert $^ $@ template.js: crunch ./$<
Using unitprint
For these documents, I wrote
python3-unitprint
which can format numbers with errors nicely. Then in the Python program I write
something like:
E_val = ... E_err = ... T['E'] = unitprint.siunitx(E_val, E_err)
This will format the numbers in a way that the siunitx
LaTeX package can
understand. In the template I then use the following:
The energy $E$ is \SI{<< E >>}{\joule}.
Since the value and error are included in the same variable in the template, one does not have to use „±" by hand. This makes the template cleaner.