LaTeX & Typst (papers, theses, resumes and slides) Module 3 – Mathematics
Aligned equations, cases and matrices
Align derivations at their equals signs, choose which lines get numbers, and write cases and matrices in LaTeX and Typst, with every result compiled.
What you will learn
- Format a multi-line derivation so that its equals signs line up, with & in LaTeX and Typst
- Choose which lines get numbers with align, \notag, split and Typst block equations
- Write piecewise definitions with cases, including Typst's escaped comma
- Build matrices with the brackets, bars and augment lines they need, within LaTeX's column limit
- Define an operator name once and reuse it, with limits where it needs them
Before you start
What you’ll learn · Before you start
What you will learn
- Format a multi-line derivation so that its equals signs line up, with & in LaTeX and Typst
- Choose which lines get numbers with align, \notag, split and Typst block equations
- Write piecewise definitions with cases, including Typst's escaped comma
- Build matrices with the brackets, bars and augment lines they need, within LaTeX's column limit
- Define an operator name once and reuse it, with limits where it needs them
Before you start
On this page
The same three-line derivation came out of LaTeX’s align with three numbers, (1), (2) and (3), and out of a Typst
block equation with one, (1), beside its middle line. Lining equations up works the same way in both systems;
numbering them does not, and neither system warns you about the difference.
Below you will align derivations at their equals signs, decide which lines carry numbers in each system, pick the right display for a group of lines, write piecewise definitions, set matrices with the brackets they need and define operator names once. Each number and measurement below was read from PDFs built with TeX Live 2026 and Typst 0.15.1.
Lines line up at their equals signs in both systems
A derivation reads best when its equals signs stand in one column, so the eye can follow each step down the page. This one completes the square and factors a quadratic:
It says that x² + 6x + 5 is (x + 3)² − 4, a perfect square minus 4, which factors as (x + 5)(x + 1); at x = 1 every
line gives 12. Both systems write it with the same two marks: a line break between the lines, \\ in LaTeX and \ in
Typst, and an alignment point: The place where the lines of a display line up, marked with & in LaTeX's align and in a Typst block equation. In LaTeX it goes just before the relation sign, as in &=., &, where the lines should line up. LaTeX needs
amsmath’s align environment around the lines; in Typst they go inside one block equation:
align/align.tex
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{amsmath}
\pagestyle{empty}
\begin{document}
With align, every line gets a number:
\begin{align}
x^2 + 6x + 5 &= x^2 + 6x + 9 - 4 \\
&= (x + 3)^2 - 4 \label{eq:square} \\
&= (x + 5)(x + 1)
\end{align}
With \verb|\notag| on the first two lines, only the last is numbered:
\begin{align}
x^2 + 6x + 5 &= x^2 + 6x + 9 - 4 \notag \\
&= (x + 3)^2 - 4 \notag \\
&= (x + 5)(x + 1) \label{eq:factors}
\end{align}
In an equation with split, the derivation gets one number:
\begin{equation}
\begin{split}
x^2 + 6x + 5 &= x^2 + 6x + 9 - 4 \\
&= (x + 3)^2 - 4 \\
&= (x + 5)(x + 1)
\end{split}
\end{equation}
Line \eqref{eq:square} completes the square and \eqref{eq:factors} factors it.
\end{document} align/align.typ
#set math.equation(numbering: "(1)")
A block equation with three lines gets one number:
$ x^2 + 6x + 5 &= x^2 + 6x + 9 - 4 \
&= (x + 3)^2 - 4 \
&= (x + 5)(x + 1) $ <derivation>
@derivation factors the polynomial. align/separate.typ
#set math.equation(numbering: "(1)")
Three separate equations get three numbers, and each is centred on its own:
$ x^2 + 6x + 5 &= x^2 + 6x + 9 - 4 $
$ &= (x + 3)^2 - 4 $
$ &= (x + 5)(x + 1) $ from session import files, run
# The same three-line derivation numbered three ways in LaTeX (align, align with \notag, equation with split), then
# as one Typst block equation and as three separate ones. LaTeX runs twice so that \eqref finds its labels; pdftotext
# -layout shows where each line and each number sits.
files("align/align.tex", "align/align.typ", "align/separate.typ")
run("""
pdflatex -interaction=nonstopmode -halt-on-error align.tex > /dev/null
pdflatex -interaction=nonstopmode -halt-on-error align.tex | grep -E "Warning|Output written"
pdftotext -layout align.pdf - | head -21
typst compile align.typ align-typst.pdf
pdftotext -layout align-typst.pdf - | head -8
typst compile separate.typ
pdftotext -layout separate.pdf - | head -8
""") Output
$ pdflatex -interaction=nonstopmode -halt-on-error align.tex > /dev/null
$ pdflatex -interaction=nonstopmode -halt-on-error align.tex | grep -E "Warning|Output written"
Output written on align.pdf (1 page, 76152 bytes).
$ pdftotext -layout align.pdf - | head -21
With align, every line gets a number:
x2 + 6x + 5 = x2 + 6x + 9 − 4 (1)
2
= (x + 3) − 4 (2)
= (x + 5)(x + 1) (3)
With \notag on the first two lines, only the last is numbered:
x2 + 6x + 5 = x2 + 6x + 9 − 4
= (x + 3)2 − 4
= (x + 5)(x + 1) (4)
In an equation with split, the derivation gets one number:
x2 + 6x + 5 = x2 + 6x + 9 − 4
= (x + 3)2 − 4 (5)
= (x + 5)(x + 1)
Line (2) completes the square and (4) factors it.
$ typst compile align.typ align-typst.pdf
$ pdftotext -layout align-typst.pdf - | head -8
A block equation with three lines gets one number:
𝑥2 + 6𝑥 + 5 = 𝑥2 + 6𝑥 + 9 − 4
= (𝑥 + 3)2 − 4 (1)
= (𝑥 + 5)(𝑥 + 1)
Equation 1 factors the polynomial.
$ typst compile separate.typ
$ pdftotext -layout separate.pdf - | head -8
Three separate equations get three numbers, and each is centred on its own:
𝑥2 + 6𝑥 + 5 = 𝑥2 + 6𝑥 + 9 − 4 (1)
= (𝑥 + 3)2 − 4 (2)
= (𝑥 + 5)(𝑥 + 1) (3)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 align_session.py
In the align builds and in align.typ the three equals signs form one column: the part before each & ends at the
same place and the part after it starts there. The amsmath guide puts the & before a relation symbol, &=, and says that putting it after
interferes with the normal spacing; it also warns that a \\ after the last line adds space below the display.
Typst’s maths reference says each alignment point alternates between right-aligned and left-aligned, which is the same
column of equals signs.
Spacing inside a line uses named widths in both systems. The session below measures them on the page, together with
the space around = in eqnarray, the old environment that the amsmath guide advises against:
spacing/eqnarray.tex
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{amsmath}
\pagestyle{empty}
\begin{document}
\begin{eqnarray}
y &=& 2x + 1
\end{eqnarray}
\begin{align}
y &= 2x + 1
\end{align}
\end{document} spacing/spacing.tex
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{amsmath}
\pagestyle{empty}
\begin{document}
\noindent Thin: $a\,b$\par
\noindent Quad: $a\quad b$\par
\noindent Two quads: $a\qquad b$
\end{document} spacing/spacing.typ
#set par(spacing: 1.2em)
Thin: $a thin b$
Quad: $a quad b$
Wide: $a wide b$ gaps.py
"""Reads `mutool draw -F stext` output on standard input and prints, for every place where the character given as the
first argument stands on a line of the page, the space to its left and to its right, in points. A line is every
character set on the same baseline, so a wide space does not break it."""
import sys
import xml.etree.ElementTree as ET
target = sys.argv[1]
lines = {}
for c in ET.parse(sys.stdin).getroot().iter("char"):
if c.get("c").strip():
quad = [float(v) for v in c.get("quad").split()]
baseline = round(float(c.get("y")), 1)
lines.setdefault(baseline, []).append((quad[0], quad[2], c.get("c")))
for baseline in sorted(lines):
chars = sorted(lines[baseline])
text = "".join(ch for _, _, ch in chars)
for i, (left, right, ch) in enumerate(chars):
if ch != target:
continue
parts = []
if i > 0:
parts.append(f"{left - chars[i - 1][1]:.2f} pt before {ch}")
if i < len(chars) - 1:
parts.append(f"{chars[i + 1][0] - right:.2f} pt after")
print(f"{text}: {', '.join(parts)}") from session import files, run
# gaps.py reads each page with MuPDF and prints the space on either side of a character, in points. First the space
# around = in the old eqnarray environment and in align, then LaTeX's \, \quad and \qquad and Typst's thin, quad and
# wide between two letters.
files("spacing/eqnarray.tex", "spacing/spacing.tex", "spacing/spacing.typ", "gaps.py")
run("""
pdflatex -interaction=nonstopmode -halt-on-error eqnarray.tex | grep -E "Warning|Output written"
mutool draw -q -F stext eqnarray.pdf | python3 gaps.py =
pdflatex -interaction=nonstopmode -halt-on-error spacing.tex | grep -E "Warning|Output written"
mutool draw -q -F stext spacing.pdf | python3 gaps.py b
typst compile spacing.typ spacing-typst.pdf
mutool draw -q -F stext spacing-typst.pdf | python3 gaps.py 𝑏
""") Output
$ pdflatex -interaction=nonstopmode -halt-on-error eqnarray.tex | grep -E "Warning|Output written" Output written on eqnarray.pdf (1 page, 25487 bytes). $ mutool draw -q -F stext eqnarray.pdf | python3 gaps.py = y=2x+1(1): 10.32 pt before =, 9.96 pt after y=2x+1(2): 3.13 pt before =, 2.76 pt after $ pdflatex -interaction=nonstopmode -halt-on-error spacing.tex | grep -E "Warning|Output written" Output written on spacing.pdf (1 page, 30084 bytes). $ mutool draw -q -F stext spacing.pdf | python3 gaps.py b Thin:ab: 1.66 pt before b Quad:ab: 9.96 pt before b Twoquads:ab: 19.93 pt before b $ typst compile spacing.typ spacing-typst.pdf $ mutool draw -q -F stext spacing-typst.pdf | python3 gaps.py 𝑏 Thin:𝑎𝑏: 1.83 pt before 𝑏 Quad:𝑎𝑏: 11.00 pt before 𝑏 Wide:𝑎𝑏: 22.00 pt before 𝑏
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 spacing_session.py
LaTeX’s \,, \quad and \qquad measured 1.66, 9.96 and 19.93 pt in 10 pt type, and Typst’s thin, quad and
wide 1.83, 11.00 and 22.00 pt in 11 pt type: a sixth of an em, one em and two in each system. Use them for gaps the
formula needs, such as \quad \text{for } x > 0, and leave the space around operators to the engine.
Aligning with eqnarray
Wrong: \begin{eqnarray} y &=& 2x + 1 \end{eqnarray}. The session measured 10.32 pt before its = and 9.96 pt
after it, against 3.13 and 2.76 pt in align. The amsmath guide says spacing around relations in eqnarray is not the
preferred mathematical spacing, and l2tabu says to replace it with align. Right: \begin{align} y &= 2x + 1 \end{align}, with one & before the =.
LaTeX numbers every line of align, Typst numbers the block
The first session also showed the numbers. align gave each of its three lines a number, (1) to (3); with \notag
before the end of the first two lines only the last was numbered, (4); and split inside an equation gave the whole
derivation one number, (5), beside its middle line. The Typst block equation got exactly what equation with split
gets: one number, (1), for the whole block. Step through what align does with the lines:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Lines. align splits its body at every \\ into lines. The amsmath guide warns against a \\ after the last line, which only adds space below the display.
- The alignment point. On each line, & marks the point the lines line up at. The guide puts it before the relation symbol, &=, never after, because &= keeps the normal spacing around the equals sign.
- The left parts. Everything before the & is set flush right against the point, so x² + 6x + 5 on the first line and the empty left parts of the next two all end in the same place.
- The right parts. Everything after the & starts at the point, so the three equals signs stand in one column. Typst does the same with & inside a block equation, alternating right and left at each point.
- The numbers. align gives each line the next equation number, (1) to (3) in the session, and \notag before the end of a line leaves that line out. A Typst block equation instead gets one number for the whole block, shown beside its middle line.
The two systems also refer to equations differently. LaTeX’s \eqref{eq:square} printed (2), the number in
parentheses, while Typst’s @derivation printed Equation 1, the equation’s supplement and its number.
| What you want | LaTeX | Typst |
|---|---|---|
| A number on every line | align |
the equate package, or separate block equations |
| A number on some lines | align with \notag on the others |
equate with its revoke label on the others |
| One number for the group | equation with split |
a block equation with a numbering set rule |
| No numbers | align* |
a block equation with no numbering set |
Typst has no built-in way to number lines one by one. Writing each line as its own equation, as separate.typ does,
gives three numbers, but the session showed the price: each equation is centred on its own, so the equals signs no
longer line up. The equate package on Typst Universe exists for this case: with #show: equate.with(...) it numbers
each line of a block, its <equate:revoke> label switches a line’s number off, and its number-mode setting can number
only the lines that carry a label.
Key idea
& and the line break align lines the same way in both systems, but align numbers every line while a Typst block
equation, like equation with split, gets one number; choose per-line numbers on purpose, with \notag or a
package.
Checkpoint
Check: Lining lines up at & · Which lines get numbers
Pick the display by what the lines need
align is not the only display for several lines. The amsmath guide offers a family, each made for one shape of
material: gather centres each line on its own, multline breaks one long equation with its first line at the left
and its last at the right, split breaks one equation inside another display and gives no number of its own, and
aligned lines up rows inside a larger formula, for example beside a brace. Each numbered form has a starred twin
that numbers nothing. Two questions about your lines decide most of the choice. Answer them in the table:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Display | Lines line up | Numbers | Typical use |
|---|---|---|---|
| align Written as: & before each =, \\ between lines | Good at every &, so the = signs form a column | Good one per line, (1) to (3); \notag drops one, align* drops all | Good a derivation whose lines you refer to one by one |
| equation with split Written as: split inside equation, one & column | Good at one & column only | Mixed one for the whole derivation, (5), from the equation around it | Good one equation or derivation cited as a whole |
| gather Written as: \\ between lines, no & | Poor each line is centred on its own | Good one per line; gather* numbers none | Mixed several short equations that need not line up |
| multline Written as: one long equation, \\ where it breaks | Poor first line at the left, last at the right | Mixed a single number for the equation | Good one equation too long for a line |
| aligned in a formula Written as: aligned inside \[ \] or $ $ | Good at every &, as in align | Mixed none of its own; the display around it decides | Good lines inside a larger formula, such as beside a brace |
| Typst block equation Written as: & and \ inside $ … $ | Good & points alternate right and left, as in align | Mixed one number for the block, (1); per line needs a package | Good any group of lines, numbered by a set rule |
How the answers decide
- “Must the lines line up at their equals signs?” Yes, the = signs form a column: rules out gather and multline. gather centres every line on its own and multline sets the first line left and the last right, so no column of = signs forms.
- “Must the lines line up at their equals signs?” No, each line can stand alone: adds a caveat to aligned in a formula. aligned only makes sense inside a larger formula; for lines that stand alone, gather or separate displays are simpler.
- “How many equation numbers do the lines need?” One on every line: rules out equation with split, multline and aligned in a formula and adds a caveat to Typst block equation. split and multline give one number for the whole, and aligned none of its own; a Typst block also gets one, so per-line numbers need the equate package.
- “How many equation numbers do the lines need?” One for the whole group: adds a caveat to align and gather. align and gather number every line, so all but one line would need \notag; equation with split gives the single number directly.
- “How many equation numbers do the lines need?” None: adds a caveat to align, equation with split, gather, multline and Typst block equation. use the starred form, align*, gather* or multline*, or split inside equation*; in Typst leave the numbering set rule out.
In short: align when the lines line up and you refer to them one by one, equation with split when they line up
and are cited as a whole, gather for unrelated short equations, multline for one equation too long for a line, and
aligned for lines inside a bigger formula. In Typst a block equation covers every case that needs one number or none.
Cases set the conditions in a column
A piecewise definition gives a value for each condition, with a brace on the left:
It reads “the absolute value of x is x when x is at least 0, and minus x when x is negative”: |−3| = 3 and |3| = 3.
amsmath’s cases environment writes it with & between the value and the condition and \\ between rows, and the
guide notes that cases is set in text style, with mathtools’ dcases for display style. Typst’s cases function takes
one argument per row, separated by commas, and its reference says content across branches can be aligned with &.
That comma is the trap. The session builds the definition and an invented postage fee rule in both systems, with a first Typst attempt that types commas as punctuation:
cases/cases.tex
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{amsmath}
\pagestyle{empty}
\begin{document}
\[
|x| =
\begin{cases}
x & \text{if } x \geq 0, \\
-x & \text{if } x < 0.
\end{cases}
\]
\[
\operatorname{fee}(w) =
\begin{cases}
40 & \text{if } w \leq 500, \\
40 + 30 \left\lceil \dfrac{w - 500}{500} \right\rceil & \text{otherwise}.
\end{cases}
\]
\end{document} cases/cases-commas.typ
$ abs(x) = cases(
x & "if" x >= 0,
-x & "if" x < 0.
) $ cases/cases.typ
$ abs(x) = cases(
x & "if" x >= 0\,,
-x & "if" x < 0.
) $
$ op("fee")(w) = cases(
40 & "if" w <= 500\,,
40 + 30 ceil((w - 500) / 500) & "otherwise".
) $ from session import files, run
# Two piecewise definitions in LaTeX's cases environment and in Typst's cases function, with Typst's first attempt,
# whose commas went missing. pdftotext -bbox gives the left edge of every "if", to show whether the conditions line up.
files("cases/cases.tex", "cases/cases-commas.typ", "cases/cases.typ")
run("""
pdflatex -interaction=nonstopmode -halt-on-error cases.tex | grep -E "Warning|Output written"
pdftotext -layout cases.pdf - | head -9
pdftotext -bbox cases.pdf - | grep '>if<'
typst compile cases-commas.typ
pdftotext -layout cases-commas.pdf - | head -3
typst compile cases.typ cases-typst.pdf
pdftotext -layout cases-typst.pdf - | head -7
pdftotext -bbox cases-typst.pdf - | grep '>if<'
""") Output
$ pdflatex -interaction=nonstopmode -halt-on-error cases.tex | grep -E "Warning|Output written"
Output written on cases.pdf (1 page, 41214 bytes).
$ pdftotext -layout cases.pdf - | head -9
{︄
x if x ≥ 0,
|x| =
−x if x < 0.
⎧
⎨40 ⌈︃ ⌉︃ if w ≤ 500,
fee(w) = w − 500
⎩40 + 30 otherwise.
500
$ pdftotext -bbox cases.pdf - | grep '>if<'
<word xMin="315.070129" yMin="139.536769" xMax="320.882310" yMax="148.333744">if</word>
<word xMin="315.068136" yMin="153.882769" xMax="320.880317" yMax="162.679744">if</word>
<word xMin="351.990639" yMin="175.800769" xMax="357.802819" yMax="184.597744">if</word>
$ typst compile cases-commas.typ
$ pdftotext -layout cases-commas.pdf - | head -3
𝑥 if 𝑥 ≥ 0
|𝑥| = {
−𝑥 if 𝑥 < 0.
$ typst compile cases.typ cases-typst.pdf
$ pdftotext -layout cases-typst.pdf - | head -7
𝑥 if 𝑥 ≥ 0,
|𝑥| = {
−𝑥 if 𝑥 < 0.
40 if 𝑤 ≤ 500,
fee(𝑤) = { 𝑤−500
40 + 30⌈ 500 ⌉ otherwise.
$ pdftotext -bbox cases-typst.pdf - | grep '>if<'
<word xMin="304.754800" yMin="71.372200" xMax="311.178800" yMax="82.372200">if</word>
<word xMin="304.754800" yMin="84.528200" xMax="311.178800" yMax="95.528200">if</word>
<word xMin="337.216580" yMin="113.052240" xMax="343.640580" yMax="124.052240">if</word>
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 cases_session.py
In cases-commas.typ the comma after x >= 0 vanished, because Typst read it as the end of the first argument.
Typst’s maths reference says that to write a verbatim comma or semicolon in a maths call, you escape it with a
backslash, so cases.typ writes x >= 0\, and the comma came back. In both systems the conditions lined up: every
“if” of the absolute value started at 315.07 pt in LaTeX and at 304.75 pt in Typst.
The same \, means two things
In LaTeX, \, is a thin space. Inside a Typst maths call such as cases(...) or mat(...), \, is an escaped
comma, and a plain comma ends the argument. Wrong: cases(x & "if" x >= 0, ...) when you want the comma on the page.
Right: cases(x & "if" x >= 0\,, ...).
Checkpoint
Check: Choosing a display for several lines · Piecewise definitions with cases
Matrices take their brackets from a name or from delim
Take the system x + y + z = 6, 2x − y + z = 3 and x + 2y − z = 2, whose solution is x = 1, y = 2, z = 3. As a matrix equation, as an augmented matrix: The matrix of a system of linear equations with the right-hand sides added as one more column, usually set off by a vertical bar: array with {ccc|c} in LaTeX, mat with augment in Typst. with a bar before the last column, and with the determinant of its coefficients:
The determinant, expanded along the first row, is 1·(1 − 2) − 1·(−2 − 1) + 1·(4 + 1) = −1 + 3 + 5 = 7, and because
it is not 0 the system has exactly one solution. LaTeX picks the brackets by the environment’s name: the amsmath guide
lists pmatrix, bmatrix, Bmatrix, vmatrix and Vmatrix for ( ), [ ], , | | and ‖ ‖, and a plain matrix
without any. A bar inside the matrix needs LaTeX’s array with a column rule, {ccc|c}. Typst has one function,
mat, with rows separated by semicolons and entries by commas; its delim argument picks the brackets and augment
draws the bar, augment: #3 after the third column:
matrices/matrices.tex
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{amsmath}
\pagestyle{empty}
\begin{document}
\[
\begin{pmatrix} 1 & 1 & 1 \\ 2 & -1 & 1 \\ 1 & 2 & -1 \end{pmatrix}
\begin{pmatrix} x \\ y \\ z \end{pmatrix}
=
\begin{pmatrix} 6 \\ 3 \\ 2 \end{pmatrix}
\]
\[
\left[
\begin{array}{ccc|c}
1 & 1 & 1 & 6 \\
2 & -1 & 1 & 3 \\
1 & 2 & -1 & 2
\end{array}
\right]
\]
\[
\det A = \begin{vmatrix} 1 & 1 & 1 \\ 2 & -1 & 1 \\ 1 & 2 & -1 \end{vmatrix} = 7
\]
\end{document} matrices/matrices.typ
$ mat(1, 1, 1; 2, -1, 1; 1, 2, -1) vec(x, y, z) = vec(6, 3, 2) $
$ mat(delim: "[", augment: #3,
1, 1, 1, 6;
2, -1, 1, 3;
1, 2, -1, 2) $
$ det A = mat(delim: "|", 1, 1, 1; 2, -1, 1; 1, 2, -1) = 7 $ matrices/wide.tex
\documentclass{article}
\usepackage{amsmath}
\pagestyle{empty}
\begin{document}
\[
\begin{bmatrix} 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 & 11 \end{bmatrix}
\]
\end{document} matrices/wide-fixed.tex
\documentclass{article}
\usepackage{amsmath}
\setcounter{MaxMatrixCols}{11}
\pagestyle{empty}
\begin{document}
\[
\begin{bmatrix} 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 & 11 \end{bmatrix}
\]
\end{document} from session import files, run
# The 3 by 3 system of the lesson as matrices in both systems, then a row of 11 columns, which LaTeX's matrix
# environments refuse until MaxMatrixCols is raised and Typst sets as it is.
files("matrices/matrices.tex", "matrices/matrices.typ", "matrices/wide.tex", "matrices/wide-fixed.tex", "matrices/wide.typ")
run("""
pdflatex -interaction=nonstopmode -halt-on-error matrices.tex | grep -E "Warning|Output written"
typst compile matrices.typ matrices-typst.pdf
pdfinfo matrices-typst.pdf | grep Pages
!pdflatex -interaction=nonstopmode -halt-on-error wide.tex > /dev/null
grep -E "^!|^l\\.[0-9]" wide.log
pdflatex -interaction=nonstopmode -halt-on-error wide-fixed.tex | grep -E "Warning|Output written"
pdftotext wide-fixed.pdf - | paste -s -d " " -
typst compile wide.typ
pdftotext wide.pdf - | head -1
""") Output
$ pdflatex -interaction=nonstopmode -halt-on-error matrices.tex | grep -E "Warning|Output written" Output written on matrices.pdf (1 page, 38322 bytes). $ typst compile matrices.typ matrices-typst.pdf $ pdfinfo matrices-typst.pdf | grep Pages Pages: 1 $ pdflatex -interaction=nonstopmode -halt-on-error wide.tex > /dev/null [exit status 1] $ grep -E "^!|^l\.[0-9]" wide.log ! Extra alignment tab has been changed to \cr. l.6 ...x} 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 & ! ==> Fatal error occurred, no output PDF file produced! $ pdflatex -interaction=nonstopmode -halt-on-error wide-fixed.tex | grep -E "Warning|Output written" Output written on wide-fixed.pdf (1 page, 19978 bytes). $ pdftotext wide-fixed.pdf - | paste -s -d " " - [︁ 1 2 3 4 5 6 7 8 9 10 11 ]︁ $ typst compile wide.typ $ pdftotext wide.pdf - | head -1 [1 2 3 4 5 6 7 8 9 10 11]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 matrices_session.py
Both systems built the three matrices. The row of 11 entries did not build in LaTeX: pdfLaTeX stopped with Extra alignment tab has been changed to \cr, which names neither the matrix nor the limit. The amsmath guide explains it:
the matrix environments take at most the number of columns in the counter MaxMatrixCols, normally 10, and
\setcounter{MaxMatrixCols}{11} in the preamble let wide-fixed.tex build. Typst set all 11 entries without a
setting.
Define an operator once, then use it like sin
The previous lesson set EMI upright with \operatorname and op. A document that uses a name often defines it once.
In LaTeX, \DeclareMathOperator{\EMI}{EMI} goes in the preamble, and its starred form,
\DeclareMathOperator*{\argmax}{arg\,max}, puts subscripts below the name in displays, as the amsmath guide describes
for names like lim and max. In Typst, a #let gives the name to an operator, so the same letters that stopped the build
with unknown variable now find it; op’s limits setting, its reference says, shows attachments as limits in display
mode:
operators/operators.tex
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{amsmath}
\DeclareMathOperator{\EMI}{EMI}
\DeclareMathOperator*{\argmax}{arg\,max}
\pagestyle{empty}
\begin{document}
Declared once, used everywhere: $\EMI(P, r, n)$ and $\EMI(2P, r, n) = 2\EMI(P, r, n)$.
\[
r^{*} = \argmax_{r \in R} \EMI(P, r, n)
\]
Inline, the limit moves beside the name: $\argmax_{r \in R} \EMI(P, r, n)$.
\end{document} operators/operators.typ
#let EMI = math.op("EMI")
#let argmax = math.op("arg max", limits: true)
Declared once, used everywhere: $EMI(P, r, n)$ and $EMI(2P, r, n) = 2 EMI(P, r, n)$.
$ r^* = argmax_(r in R) EMI(P, r, n) $
Inline, the limit moves beside the name: $argmax_(r in R) EMI(P, r, n)$. from session import files, run
# Two operator names declared once, EMI and arg max, the second with limits, used in a sentence and in a display.
files("operators/operators.tex", "operators/operators.typ")
run("""
pdflatex -interaction=nonstopmode -halt-on-error operators.tex | grep -E "Warning|Output written"
pdftotext -layout operators.pdf - | head -6
typst compile operators.typ operators-typst.pdf
pdftotext -layout operators-typst.pdf - | head -5
""") Output
$ pdflatex -interaction=nonstopmode -halt-on-error operators.tex | grep -E "Warning|Output written"
Output written on operators.pdf (1 page, 42719 bytes).
$ pdftotext -layout operators.pdf - | head -6
Declared once, used everywhere: EMI(P, r, n) and EMI(2P, r, n) = 2 EMI(P, r, n).
r∗ = arg max EMI(P, r, n)
r∈R
Inline, the limit moves beside the name: arg maxr∈R EMI(P, r, n).
$ typst compile operators.typ operators-typst.pdf
$ pdftotext -layout operators-typst.pdf - | head -5
Declared once, used everywhere: EMI(𝑃 , 𝑟, 𝑛) and EMI(2𝑃 , 𝑟, 𝑛) = 2 EMI(𝑃 , 𝑟, 𝑛).
𝑟∗ = arg max EMI(𝑃 , 𝑟, 𝑛)
𝑟∈𝑅
Inline, the limit moves beside the name: arg max𝑟∈𝑅 EMI(𝑃 , 𝑟, 𝑛).
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 operators_session.py
In both systems EMI(P, r, n) came out upright with operator spacing, 2 EMI included, and arg max took its
subscript r ∈ R below the name in the display and beside it in the sentence, where a limit below would push the lines
apart. The definition sits in one place, so changing it changes every use.
Checkpoint
Check: Matrices and their brackets · Operators defined once
Practise: count the numbers, then convert matrices
Two small tools from this page. The first predicts how many numbers an amsmath display prints, which is what you check when a style asks you to number only the equations you cite; the second turns Typst matrices into LaTeX environments and refuses a row LaTeX would refuse. Each step has sample tests that run in your browser:
Code lab · 2 steps
Count equation numbers and convert matrices
Predict how many equation numbers an amsmath display prints, then convert a Typst mat() call into the matching LaTeX matrix environment.
- Predict how many numbers an amsmath display prints To do
- Convert a Typst mat() call into a LaTeX matrix To do
Exercise · Easy · Python
Predict how many numbers an amsmath display prints
A style that asks you to number only the equations you cite needs a quick count of what each display prints. Write equation_numbers(env, body) in eqnumbers.py. env is the name of an amsmath display and body is the text between its \begin and \end. Return how many equation numbers LaTeX prints for it:
alignandgathernumber every line; lines are separated by\\, and a blank piece after a final\\is not a line. A line that contains\notagor\nonumberhas no number; a line with\tag{…}shows its tag instead, which still counts as one;equationandmultlineprint one number for the whole display, whatever is inside,splitincluded, unless the body contains\notagor\nonumber; a\tag{…}replaces the number and counts as one;- a starred name, such as
align*orequation*, prints no numbers except one for each line with a\tag{…}.
Starter code · eqnumbers.py
def equation_numbers(env, body):
"""Return how many equation numbers LaTeX prints for the amsmath display `env` with this body."""
# Replace this comment: split the body into lines and count by the rules of the environment.
return 0 The sample tests · test_eqnumbers.py
from eqnumbers import equation_numbers
DERIVATION = r"""
x^2 + 6x + 5 &= x^2 + 6x + 9 - 4 \\
&= (x + 3)^2 - 4 \\
&= (x + 5)(x + 1)
"""
def test_align_numbers_every_line():
"""the lesson's three-line derivation in align: (1), (2) and (3)"""
assert equation_numbers("align", DERIVATION) == 3
def test_notag_removes_numbers():
"""two notags leave the last line's number, as (4) in the session"""
body = DERIVATION.replace(r"- 4 \\", r"- 4 \notag \\")
assert equation_numbers("align", body) == 1
def test_split_inside_equation():
"""equation with split numbers the derivation once, as (5)"""
body = "\\begin{split}" + DERIVATION + "\\end{split}"
assert equation_numbers("equation", body) == 1
def test_starred_forms_number_nothing():
"""align* and gather* print no numbers"""
assert equation_numbers("align*", DERIVATION) == 0
assert equation_numbers("gather*", "a = b \\\\ c = d") == 0
def test_a_tag_counts_even_when_starred():
"""a line with \\tag shows its tag, even in align*"""
assert equation_numbers("align*", "a &= b \\tag{*} \\\\ c &= d") == 1
def test_trailing_line_break_adds_no_line():
"""a \\\\ after the last line is not a fourth line"""
assert equation_numbers("gather", "a = b \\\\ c = d \\\\ e = f \\\\ ") == 3
def test_multline_has_one_number():
"""multline breaks one equation over lines and numbers it once"""
assert equation_numbers("multline", "a + b + c \\\\ + d + e") == 1
def test_a_single_equation_with_notag_or_tag():
"""\\notag removes the number of an equation, and \\tag replaces it"""
assert equation_numbers("equation", "E = mc^2 \\notag") == 0
assert equation_numbers("equation", "E = mc^2 \\tag{A}") == 1 A hint
Split the body with body.split("\\\\"), drop the pieces that are only spaces, and treat each remaining piece as a line. Decide first whether the environment numbers lines (align, gather), the whole display (equation, multline) or nothing (a name ending in *); \tag counts in every case.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Exercise · Medium · Python
Convert a Typst mat() call into a LaTeX matrix
Write mat_to_latex(source) in matrix.py. source is a Typst call such as mat(1, 2; 3, 4) or mat(delim: "[", 1, 0; 0, 1): rows are separated by semicolons and entries by commas, and an optional first argument delim: "…" picks the brackets. Return the amsmath environment that sets the same matrix, with the entries trimmed, & between entries and \\ between rows:
| Typst | LaTeX environment | | --- | --- | | no delim (parentheses are Typst's default) or delim: "(" | pmatrix | | delim: "[" | bmatrix | | delim: "{" | Bmatrix | | delim: "|" | vmatrix |
So mat(1, 2; 3, 4) becomes \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}. A row of more than 10 entries would stop LaTeX, whose matrix environments take at most MaxMatrixCols columns, normally 10: raise a ValueError whose message names MaxMatrixCols instead.
Starter code · matrix.py
def mat_to_latex(source):
"""Return the amsmath matrix environment that sets the same matrix as the Typst call `source`."""
# Replace this comment: read the delimiter, split rows and entries, and build the environment.
return "" The sample tests · test_matrix.py
from matrix import mat_to_latex
def test_default_parentheses():
"""Typst's default delimiter is a pair of parentheses"""
assert mat_to_latex("mat(1, 2; 3, 4)") == "\\begin{pmatrix} 1 & 2 \\\\ 3 & 4 \\end{pmatrix}"
def test_square_brackets():
"""delim "[" gives bmatrix"""
assert mat_to_latex('mat(delim: "[", 1, 0; 0, 1)') == "\\begin{bmatrix} 1 & 0 \\\\ 0 & 1 \\end{bmatrix}"
def test_determinant_bars():
"""the lesson's determinant, with bars"""
source = 'mat(delim: "|", 1, 1, 1; 2, -1, 1; 1, 2, -1)'
expected = "\\begin{vmatrix} 1 & 1 & 1 \\\\ 2 & -1 & 1 \\\\ 1 & 2 & -1 \\end{vmatrix}"
assert mat_to_latex(source) == expected
def test_braces_and_spaces():
"""delim "{" gives Bmatrix, and spaces around entries are trimmed"""
assert mat_to_latex('mat(delim: "{", a ,b;c, d )') == "\\begin{Bmatrix} a & b \\\\ c & d \\end{Bmatrix}"
def test_eleven_columns_are_refused():
"""a row of 11 entries would stop LaTeX at MaxMatrixCols"""
try:
mat_to_latex("mat(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)")
except ValueError as error:
assert "MaxMatrixCols" in str(error)
else:
assert False, "expected a ValueError for 11 columns" A hint
Take the text between mat( and the last ). If it starts with delim:, read the quoted character and cut off everything up to the comma after it. Then split(";") gives the rows and split(",") the entries of each.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Then answer questions on each idea of this page; an idea you get wrong comes back before the others:
Practice that adapts to you
Practise this lesson
13 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): align, align or equation with split, which numbers what?*
align gives every line its own number, so a three-line derivation gets three; \notag before the end of a line
removes that line’s number, and align* numbers no line at all. equation with split inside sets the same aligned
lines but gives the whole group one number, placed beside its middle line, because split provides no numbering of its
own and the equation around it supplies one. In a recorded build, the same derivation came out as (1) (2) (3), as (4)
on its last line only, and as (5) for the whole group. Choose align when you cite single steps, split when you cite
the derivation as a whole, and the starred forms when you cite nothing. In Typst, a block equation behaves like
equation with split.
Two harder questions follow, about a journal’s numbering rule and about per-line numbers in Typst. Decide your first step before you open each one:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A journal's style says to number only the equations the text cites. Your paper has 14 align environments that number every line, 61 numbers in all, and the text cites 9 of them. How do you change the numbering without breaking a single reference?
-
Mid to senior
A department's thesis rule says every line of a derivation carries its own number, as LaTeX's align does. Your Typst template numbers each block equation once. What do you change, and what do you check before 40 chapters depend on it?
Key takeaways
- Both systems align lines at
&and break them with\\(LaTeX) or\(Typst); put the&before the=, and replaceeqnarray, whose space around=measured more than three times that ofalign. alignnumbers every line,\notagremoves one number,equationwithsplitand a Typst block equation give the group one number, and per-line numbers in Typst need a package such as equate.- Choose the display by the lines:
alignorsplitwhen they line up,gatherwhen they stand alone,multlinefor one long equation andalignedinside a larger formula. casessets values and conditions in columns in both systems, and in Typst a comma inside the call ends a branch unless it is escaped as\,, which in LaTeX means a thin space.- LaTeX’s matrix environments take their brackets from their names and at most 10 columns unless
MaxMatrixColsis raised; Typst’smattakesdelimandaugmentand any number of columns. - Define an operator once with
\DeclareMathOperatoror#let name = math.op(...), starred or withlimits: truewhen its subscript belongs below it in displays.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
Which two marks line up a derivation in both systems?
A line break between the lines, \\ in LaTeX and \ in Typst, and an alignment point, &, where the lines should line up, written before the relation: &=.
-
How many numbers does a three-line align give, and how do you change that?
Three, one per line. \notag removes a line's number, align* numbers none, and equation with split gives the whole group one number.
-
How many numbers does a Typst block equation of three lines get?
One, beside the block, as LaTeX's equation with split gives. Numbers on each line need separate equations or a package such as equate.
-
Why replace eqnarray with align?
eqnarray spaces its relations wrongly: 10.32 pt before the = against 3.13 pt in align. The amsmath guide and l2tabu both say to use align.
-
Which amsmath display fits one long equation, and which several unrelated short ones?
multline breaks one long equation, with its first line at the left and its last at the right; gather centres each line on its own.
-
Why did a comma vanish from a Typst cases row?
A plain comma ends an argument of the call; write \, for a comma on the page inside cases(...) or mat(...). In LaTeX the same \, is a thin space.
-
How do you choose a matrix's brackets in each system?
LaTeX by the environment's name: pmatrix ( ), bmatrix [ ], Bmatrix { }, vmatrix | | and Vmatrix ‖ ‖. Typst's mat takes delim, and augment: #3 draws a bar after the third column.
-
Why did a LaTeX matrix with a row of 11 entries stop with an Extra alignment tab error?
amsmath's matrix environments take at most MaxMatrixCols columns, normally 10; \setcounter{MaxMatrixCols}{11} in the preamble lets it build. Typst needs no setting.
Terms in this lesson
- alignment point
- The place where the lines of a display line up, marked with & in LaTeX's align and in a Typst block equation. In LaTeX it goes just before the relation sign, as in &=.
- augmented matrix
- The matrix of a system of linear equations with the right-hand sides added as one more column, usually set off by a vertical bar: array with {ccc|c} in LaTeX, mat with augment in Typst.
References
- User’s Guide for the amsmath Package (American Mathematical Society (CTAN))
- amsmath (CTAN)
- mathtools (CTAN)
- l2tabu: obsolete commands and packages (English) (CTAN)
- Typst reference: Math (alignment and line breaks) (Typst)
- Typst reference: equation (Typst)
- Typst reference: cases (Typst)
- Typst reference: mat (Typst)
- Typst reference: op (Typst)
- equate on Typst Universe (Typst Universe)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress