Computation Results

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 <dev@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.