I’ve been using AMC for years now, it’s definitely a very good tool that saves me hours of work on large groups of students. Exam after exam, I’ve improved the way the questions are randomised, with all randomisation computed in LaTeX directly, using pgf/tikz as explained here. But this eventually became too much complicated, especially when complex computation is required to generate the exercises. This post explains how I now use Python instead of LaTeX that is relegated to do only the typesetting.

WARNING: this post is not about running Python from LaTeX, but about generating LaTeX from Python, to be included by AMC during the LaTeX compilation.

Let’s take an example and say I want to check whether students can understand the concept of a rainbow table. In this exam, I:

To do so, I first need a Python program to randomly generate the simple rainbow tables. When it comes to randomisation, let me give you an advice:

Always initialise the pseudo-random numbers generator with a fixed constant, and ensure you eliminate all non-deterministic aspects like dict or set iteration orders, so your program runs deterministically. This will be crucial if you need to regenerate things after the exam is passed. Moreover, you may need to save all the parameters that have been randomly generated so you will be able to fix potential bugs in the questions later on.

I won’t explain the code, this is not the topic of this post, but you can get it at this Github repo. In script gen/rtgen.py, a class RainbowTable is defined to generate randomly a mocked rainbow table (note that it would be insane to do this in LaTeX, which is exactly the motivation to use Python instead). It has one method to save a PDF of the table to be included in the exam by AMC, and one method to save a LaTeX file with macros to retrieve the elements of the table from the AMC LaTeX source code. Finally, the script generates repeatedly instances of this class and saves files inc/C1.tex, inc/C1.pdf, inc/C2.tex, etc., for as many exams as needed (I’ve fixed it to 10 for the example to speedup compilation, but my use is more for 100+ students).

Then we have the AMC LaTeX source code, let me show and comment short excerpts. First, we have macros to retrieve the cells saved in the inc/*.tex files. For simplicity I’ve separated words from hashes, so one uses eg \WORD[5,3] to retrieve the word in line 5 and column W3, or \HASH[2,1] for the hash in line 2 and column E1. Macro SPARE allows to retrieve words or hashes that are not in the table, eg, \SPARE[W,0] is the first spare word, and \SPARE[H,3] the fourth spare hash.

{% raw %}
\def\WORD[#1]{\expandafter\csname W#1\endcsname}
\def\HASH[#1]{\expandafter\csname H#1\endcsname}
\def\SPARE[#1]{\expandafter\csname #1\endcsname}
{% endraw %}

Then, we define a macro to retrieve the exam number (numbered by AMC starting from 1), which is needed to load the corresponding PDF and TeX files.

{% raw %}
\makeatletter
\def\SujetNum{\the\AMCid@etud}
\makeatother
{% endraw %}

Let’s see now a question. We ask the student which operations are required to perform a particular lookup in the table. This question is the same for every exam, but the values will be different each time.

{% raw %}
\element{CRY}{
  \begin{questionmult}{C1}
    In the example table, which operations are necessarily performed to search for~\HASH[0,2] (line~0)?
    \begin{multicols}{3}
      \AMCBoxedAnswers
      \begin{reponses}
        \correctchoice{$r_2(\HASH[0,2])$}
        \correctchoice{$h(\WORD[0,0])$}
        \correctchoice{$r_0(\HASH[0,0])$}
        \correctchoice{$h(\WORD[0,1])$}
        \correctchoice{$r_1(\HASH[0,1])$}
        \wrongchoice{$r_0(\HASH[0,1])$}
        \wrongchoice{$r_0(\HASH[0,2])$}
        \wrongchoice{$r_1(\HASH[0,0])$}
        \wrongchoice{$r_1(\HASH[0,2])$}
        \wrongchoice{$r_2(\HASH[0,0])$}
        \wrongchoice{$r_2(\HASH[0,1])$}
        \wrongchoice{$h(\WORD[0,2])$}
        \wrongchoice{$h(\WORD[0,3])$}
        \wrongchoice{$h(\HASH[0,2])$}
        \wrongchoice{$r_0(\WORD[0,0])$}
        \wrongchoice{$r_0(\SPARE[H,0])$}
        \wrongchoice{$r_1(\SPARE[H,1])$}
        \wrongchoice{$h(\SPARE[W,0])$}
      \end{reponses}
    \end{multicols}
  \end{questionmult}
}
{% endraw %}

Finally, see how we present and render the questions. First, we load the TeX file, which requires some trickery with \edef and \expandafter (perhaps there is a simpler method, but I’ve learned plain/TeX before LaTeX so I may reinvent the wheel more than often). From this point, we can use the macros to get elements from the table, for instance to illustrate the explanation about rainbow tables with concrete values from the example table. Then we include the PDF file, with a 90° rotation so it fits in the page width. And at the end, we shuffle and render the questions. Because we have already loaded the TeX file for this exam number, they will be rendered with the values specific to this exam number.

{% raw %}
\edef\TEX{{inc/C\SujetNum.tex}}
\expandafter\input\TEX

\noindent
Explain here what is a rainbow table, with a simple example provided below.
Example of a chain: the row starting at \WORD[0,0] and ending with \WORD[0,3].
Explain the search algorithm.

\begin{center}
  \edef\PDF{{inc/C\SujetNum.pdf}}
  \def\incpdf{\includegraphics[angle=90]}
  \expandafter\incpdf\PDF
\end{center}

\shufflegroup{CRY}
\restituegroupe{CRY}
{% endraw %}

That’s it.

In further posts, I’ll show how I’ve defined randomised programming exams: first, asking question about randomised code, then, requiring the students to program themselves and assessing their code automatically.