I just taught Linear Algebra, which means I need to give exams with a lot of questions about computations. I would like to have the numbers in this problems to be random, and I like to solve them with computers. (Sorry, students. Only you need to compute by hand. ๐Ÿ˜†)

I have tried to use Mathematica to do so. The math parts works well. But the typesetting of Mathematica looks quite ugly.

Eventually, I switched to Julia for computations and plotting, and Jupyter Lab for editing the code. I am very happy with the end result.

Here is the Jupyter notebook for one of my exam

In the following, I will tell you how I did it.

Jupyter Lab and IJulia

Jupyter Lab (JL) is an editor which runs in the browser. To use Julia with Jupyter Lab, you need IJulia.

The standard documents you can write with JL is called Jupyter Notebooks, which are very similar to Mathematica notebooks. In other words, you run code chunks called cells in the notebook. The output is then rendered into math equations (if it’s LaTeX) or images (if it’s a plot) and showed right below the input cell. So you can experiment with your code and immediate see the result (an exam problem in this case). Here is how my exam paper looks like as a Jupyter notebook jupyter.png

It is also very easy to convert Jupyter notebooks into PDF. (Behind the scene, a notebook is first converted into a LaTeX file, and then compiled into PDF.) And here is how my exam paper looks after being converted into PDF. pdf.png

Doing the math

Julia includes a standard package LinearAlgebra, which does most things a first Linear Algebra course does. However, it uses numeric methods most of the time. If you want symbolic result, you need SymPy and SymPy.jl. The former is a computer algebra system written in Python. The latter makes using SymPy in Julia a bit easier.

You may also want to install RowEchelon.jl. It turns a matrix into its reduced echelon form.

Random Matrices

The following code generates a random $3 \times 3$ matrix with integer entries in ${-1, \ldots, 5}$.

A = rand(-1:5, (3,3))

However, you don’t want to get a different matrix each time you run the cell. So at the beginning of each problem, you can seed the random number generator like this.

Random.seed!(1234)

This guarantees you get the same random matrix each time. And if you don’t like the result,
just change 1234 to another (fixed) number.

LaTeX

To generate LaTeX programmatically, you need two packages Latexify.jl and LaTeXStrings.jl.

Let’s say you want to start your exam problem with something like

Let $A = \cdots $ be a $5 \times 5$ matrix.

and you want to replace the $\cdots$ by the random matrix A which we just generated, you can write

L"""
A = %$(latexify(A))
"""

Here the L in front of the string indicates that we want the string to be interpreted as LaTeX code, so that Jupyter Lab knows to render it as a math equation. The %$(latexify(A)) is executed and the result is inserted into the string. The function call latexify(A) turns the Julia object A into a piece of LaTeX representing it. The result will be something like

\begin{equation*}
A = \left[
\begin{array}{cccc}
-1 & 3 & 1 & 18 \\
1 & 1 & 2 & 8 \\
1 & 5 & 5 & 34 \\
\end{array}
\right]
\end{equation*}

And Jupyter Lab will display it like this – $$ A = \left[ \begin{array}{cccc} -1 & 3 & 1 & 18 \ 1 & 1 & 2 & 8 \ 1 & 5 & 5 & 34 \ \end{array} \right] $$

Defining LaTeX Marcos

Jupyter Lab uses a JavaScript library MathJax to render LaTeX. It works quite well, but certainly it is not exactly the same as LaTeX. For example,
if you want to define a LaTeX macro which you can both use in Jupyter Lab, i.e., recognized by MathJax, and also works in LaTeX, which is an intermediate stage when a notebook is converted into PDF, you need to use a trick

Create a markdown cell and write

<div hidden>
\newcommand{\require}[1]{}

$$\require{begingroup}\require{newcommand}$$
$$
\gdef\dsR{{\mathbb{R}}}
$$

\vskip-\parskip
\vskip-\baselineskip
</div>

Then the macro \dsR will work both in the notebook and in LaTeX.

Raw Cells

Raw Cells are a special type of cell in which you can write anything and Jupyter will not try to do anything about it. And when a notebook is converted to LaTeX, whatever in a raw cell will be copied to the LaTeX. So if you want to add some LaTeX code, like a line by the end of the paper, you can create a raw cell at the end of the notebook and write

\rule{\textwidth}{0.4pt}
\begin{center}
Good bye! I will see you around.
\end{center}

And this is you will see in the end of the PDF pdf-end.png

Note, you can also just write LaTeX in markdown cells, but keep them in raw cells make things a bit cleaner.

Some things which I have not figured out

One thing that bothers me is that I cannot change the preamble of the LaTeX generated from a notebook This a bit annoying because then I cannot control things like the font size of the final PDF, unless I first convert the notebook to LaTeX and change the generated LaTeX file manually. Maybe I will try customized templates next time when I teach Linear Algebra.