Automating Mathematica Calculations
Modular LaTeX with make: Part 3 of 3
- Part 1 - Setting up a modular LaTeX project
- Part 2 - Adding make to our modular LaTeX project
- Part 3 - This Article
Imagine you generate a figure in a Mathematica notebook to insert into a PDF. Imagine needing to change the image and update our PDF. You would need to re-run your Mathematica code, then re-run LaTeX. This is the sort of nightmarish scenario that never needs to happen again.
We’re going to be adding a new dependency to our series
- Mathematica, for doing math and computing things
The code for this series is available at this GitHub repository. For this post specifically, the final source can be downloaded from this tag, or be browsed directly in GitHub at this commit.
Getting out of the notebook
Mathematica is a fantastic tool, but it has massive, massive structural problems that make it hard to use in a well-designed project. Mathematica makes PHP look like Python; it makes the insanity of Javascript look as organised as Haskell. Mathematica is not designed for good practices, which means that it hurts productivity. There are alternatives, but unfortunately for a large number of applications, we’re stuck with Mathematica.
The *.nb
file format is an example of this bad design. Notebook files are terrible to use from a reproducibility
point of view; they depend on the kernel state, which depends on human interaction (which as we’ve discussed earlier,
isn’t really satisfactory for physics projects). Additionally, they’re terrible in source control because they include
tons of pointless display metadata. Being terrible in source control also means that notebook files are pretty poor
options for collaboration. Lastly, notebook files are terrible for reuse or distribution. Obviously, it’s only a good
thing for science for people to be able to improve and expand on other people’s work.
As an alternative, we’re going to explore a better way to use Mathematica: creating packages.
Mathematica packages
Mathematica packages are essentially source files for Mathematica code. They can be run as scripts, or imported to provide code for other Mathematica packages. Packages are the standard way to distribute Mathematica code for other people. They share most of their syntax with Mathematica notebooks, but can be run in new kernels on each evaluation, which means that they’re more repeatable.
Mathematica packages can either use the .m
extension or the .wl
extension. I believe .m
is more standard, but also
conflicts with Matlab files, which also use .m
. As a result, I prefer to use .wl
file extensions for Mathematica
packages. The quickest way to run a Mathematica package as a script via the command line is to use wolframscript
,
which is installed alongside Mathematica. To call it for a particular script, you could call wolframscript -f script.wl
.
Back to our situation
In this series, we’ve been looking at a hypothetical homework set about the expression \(x^2\). Let’s say our second homework problem is the following:
- Draw a graph of \(x^2\) over the range \(-10 \leq x \leq 10\).
Setting up the files for the second problem
The first thing to do is to create a new .tex
file for the separate problem. Next to the problems/Problem1
directory,
we’ll create a problems/Problem2
directory as well, and create a placeholder Problem2.tex
file:
% Problem2.tex
\documentclass[../../main.tex]{subfiles}
\begin{document}
\section{Problem 2}
The graph will go here.
\end{document}
In order to include this into our main PDF file, we just need to add the reference to the second problem into main.tex
:
\subfile{problems/Problem1/Problem1}
% adding this line
\subfile{problems/Problem2/Problem2}
Finally, to make sure that our build knows that the main PDF depends on Problem2.tex
, we’ll add it to our makefile:
# Add Problem2 to this line, and the rest of the
# makefile will automatically pick up the .tex dependency
SUBFILE_NAMES = Problem1 Problem2
Creating our package
We just want a Mathematica script to plot \(x^2\) over the desired range. Here’s a short Mathematica package that makes
a plot, and exports it to an images folder within the problems/Problem2
directory. I’ll save it as
QuadraticFigureScript.wl
:
(* QuadraticFigureScript.wl *)
BeginPackage["QuadraticFigureScript`"];
(* Automatically gets the current directory, which should be problems/Problem2 *)
currentDirectory = DirectoryName[
FileNameJoin[{
Directory[],
$ScriptCommandLine[[1]]
}]
];
(* Gets the name of the output file, problems/Problem2/images/quadraticFigure.jpg *)
figureFilename = FileNameJoin[{
currentDirectory, "images/quadraticFigure.jpg"
}];
Export[figureFilename,
Plot[x^2, {x, -10, 10}]
];
EndPackage[];
This is a new source file, which creates a generated file problems/Problem2/images/quadraticFigure.jpg
. The dependency
chain looks like this: Problem2.pdf
uses the image file, so it depends on problems/Problem2/images/quadraticFigure.jpg
.
The image file quadraticFigure.jpg
depends on QuadraticFigureScript.wl
. We need to add these to the makefile.
Additionally, we need to make sure that main.pdf
is aware of the image file dependency.
There are a couple good ways of making sure that the makefile knows that both the individual problem PDF and the main
PDF are both aware of the image file dependency. The first is to have main.pdf
depend on the individual problem PDFs
directly, which automatically would handle all of the dependencies (drawing the dependency tree is a good way of seeing
that). However, this isn’t semantically true. The way I prefer to do it is to create a list of the extra dependencies
for each of the subfiles. Here’s the extra section that needs to be added to Makefile
:
# We add $(Prob2Dependencies) here to add the extra dependencies, as necessary
main.pdf: main.tex $(SUBFILE_TEXS) $(Prob2Dependencies)
$(LATEXMK) main.tex
# Problem 2
# just to shorten some folder names
Prob2 = problems/Problem2
# this is the list of extra dependencies. If we had multiple figures for
# this problem, or other dependencies they'd go here
Prob2Dependencies = $(Prob2)/images/quadraticFigure.jpg
# Generates our figure
$(Prob2)/images/quadraticFigure.jpg: $(Prob2)/QuadraticFigureScript.wl
@echo "Creating images folder if it doesn't exist"
mkdir -p $(Prob2)/images
wolframscript -f $(Prob2)/QuadraticFigureScript.wl
# This is only necessary to specify that there are extra dependencies for Problem2.pdf
$(Prob2)/Problem2.pdf: $(Prob2)/Problem2.tex $(Prob2Dependencies)
cd $(<D); $(LATEXMK) $(<F)
Now, if you type make
:
- make goes to the bottom of the dependency tree, and if necessary,
tries to generate
quadraticFigure.jpg
using the Mathematica script latexmk
generates all of the PDF files- All the PDF files are copied to the
docs
folder
It’s very helpful to handle all of this in one command!
Adding the figure to the .tex
file
We’re still not done. We still need to modify Problem2.tex
to actually include the generated image. There are a couple
subtle points with using images with subfiles, especially in nested directories like I’m using for this project.
Here are the changes for Problem2.tex
:
% Problem2.tex
\documentclass[../../main.tex]{subfiles}
\begin{document}
% We need to add this to tell LaTeX where our images folder is
\graphicspath{{images/}{./problems/Problem2/images/}}
\section{Problem 2}
\begin{figure}[htp]
\centering
\includegraphics[width=10cm]{quadraticFigure} % don't need to add .jpg
\caption{Here's our figure}
\end{figure}
\end{document}
The graphicspath
command comes from LaTeX package graphicsx
, which we need to include in our main.tex
file. Here’s
the complete main.tex
file.
% main.tex
\documentclass{article}
\usepackage{subfiles}
\usepackage{graphicx}
\title{Homework}
\begin{document}
\maketitle
\subfile{problems/Problem1/Problem1}
\subfile{problems/Problem2/Problem2}
\end{document}
We’re done! Now, we can just use make
to handle the entire process from start to finish, and correctly include this
figure from Mathematica. We can do the whole thing without ever having to muck around with Mathematica notebook. The
best part of this process is that no matter what changes, whether updating any of the three .tex
files or updating
the Mathematica script, make
is the only command necessary to handle the entire process.