use-math-macros — condense manuscript math onto the shared macros submodule
Rewrite the math expressions in a lab Quarto/LaTeX document so they use the shared d-morrison/macros submodule (\Ep, \Prf, \paren/\sb/\cb, \expit, \logit, \Var, \hp, \S, \h, …) instead of ad-hoc raw LaTeX. This gives every lab document the same polished, condensed notation, and centralizes the definitions in one versioned place.
When this fires
- “macroize”, “macroize the math”, “use macros”, “use the macros submodule”, “convert math to macros”, “polish the math with macros”.
- Every time you write, edit, or review LaTeX math anywhere: any project or repo, and any format (Quarto, R Markdown, LaTeX, roxygen and
.Rd, slides, Markdown docs, notebooks), a manuscript or a single equation alike — this is mandatory, not a polish pass. Use the semantic macro for each concept the library names (\Epfor a bare expectation operator,\E{x}for one subscripted byx,\Var,\Cov,\P, …) rather than writing its raw LaTeX, and when no macro names the concept, add one (step 6) rather than writing raw LaTeX (standing rule; seememories/preferences.md).
Procedure
1. Locate and check out the macros submodule
It is typically vendored at inst/analyses/macros (URL in .gitmodules):
To bring it up to date with d-morrison/macros:
--remotebumps the tracked gitlink, which dirtiesgit diff HEAD. If the checkout is running SLURM/simulation jobs that stamp git provenance at completion (e.g. aconsolidate_provenance-style reproducibility guard), that poisons the run. Do the update in a separate worktree, never the running checkout:git worktree add ../<repo>-macros -b macros-update origin/main.
2. Wire the include once, at the top of the manuscript
Include the macro definitions before any math renders:
Place it near the top of the top-level .qmd (the one that assembles the {{< include >}}d children), before the first section with math.
The definitions do not blindly override existing LaTeX, and the two definition forms behave differently: \def always redefines a name, but MathJax skips \providecommand for a name that is already defined — including a LaTeX built-in like \v (caron), \b, \u, or \c. So a \providecommand whose name shadows a built-in silently no-ops: the built-in meaning survives and the render breaks with no error. The library therefore uses \def / \renewcommand (not \providecommand) for built-in-shadowing names — e.g. \renewcommand{\v}{...}, \renewcommand{\vec}{...}. See the “MathJax ignores \providecommand” note in memories/preferences.md.
3. Read the macro inventory before rewriting
macros.qmd may define macros with any of \def, \providecommand, \newcommand, or \renewcommand (the built-in-shadowing names like \v, \vec use \renewcommand), so match all four forms:
Measured 2026-09-06 on d-morrison/rme, whose copy of this same macros library is mounted at latex-macros/ rather than at the inst/analyses/macros/ path used above. It is the same d-morrison/macros repo either way — rme’s .gitmodules gives url = https://github.com/d-morrison/macros.git — so the mount path is what varies between consumers, not the library. An earlier draft of this entry said “not d-morrison/macros”, which read as a claim that these are two unrelated macro libraries. A sweep pattern-matching only \newcommand{...}/\providecommand{...} reported \vX and \vbeta as undefined; both are \def-defined. Widening the grep to all four forms above found 30 distinct v-prefixed names defined in that library — counting distinct defined names rather than uses (the definition sites number 32, since \v is defined three times), and including non-vector names such as \var and \violet that share the prefix — and exactly 2 genuinely undefined (\vL, \vl). Same class as the built-in-shadowing case this step already names — a definition-site grep is only as complete as its list of definition mechanisms, and any macros file mixing TeX primitives can use all four.
- Do: run the four-form grep above (or an equivalent covering
\def,\providecommand,\newcommand,\renewcommand) before asserting any macro is undefined, in every.qmd-based macros file, whatever path the consumer mounts it at. - Don’t: conclude a macro is undefined from a grep that only matches
\newcommand/\providecommand— confirm against\defand\renewcommandtoo.
4. Rewrite the math — using only defined macros
Delegate the heavy rewrite to the codex CLI to conserve tokens (pass the macro inventory and the target file in the prompt), then verify:
codex exec -C "$PWD" -s read-only --skip-git-repo-check -o /tmp/macroized.out - <<'EOF'
Rewrite the math in <target>.qmd to use ONLY macros defined in
inst/analyses/macros/macros.qmd plus standard LaTeX. Preserve every formula's
meaning exactly and keep all {#eq-...}/{#tbl-...} labels and @eq- refs. Output
the rewritten .qmd, then a list of the custom macro names used.
EOF(Flags per codex exec --help — the exec subcommand, -s/--sandbox read-only, --skip-git-repo-check, and -o/--output-last-message all exist; verify against your installed codex-cli version, since flag names can shift between releases.)
Never invent a macro name — an undefined command silently breaks the Quarto/MathJax render. Verify every backslash-command resolves:
# every custom command in the rewrite must be defined in macros.qmd or be standard LaTeX
comm -23 \
<(grep -oE '\\[A-Za-z]+' <target>.qmd | sort -u) \
<(grep -oE '\\(def|providecommand|newcommand|renewcommand)\{?\\[A-Za-z]+' inst/analyses/macros/macros.qmd \
| grep -oE '\\[A-Za-z]+$' | sort -u)
# review the remainder: each must be standard LaTeX (\frac, \text, \sim, \hat, …)When the math being rewritten is a definition, expand both sides before believing it. The library aliases heavily, so a definition can name a concept on the left and define it in terms of a second macro on the right, while both expand to the same glyph. \score(\lambda) \eqdef \llik'(\lambda) reads as a definition in source and renders as a symbol defined as itself, because \def\score{\ell'} and \def\llik{\ell} collapse the two sides. This is the opposite failure from inventing a macro name: every command resolves, so the comm -23 check above passes cleanly and the render emits no warning.
Two greps settle it, and both are cheap:
The library’s own canonical definitions spell the operator out rather than restating an alias, so prefer that form: \deriv{\lambda}\llik(\lambda) for a score, \dderiv for a Hessian. An alias-only definition is a deviation from house style, not a shorthand for it. Verify the result by reading the rendered page, per fact-check-prose’s “A definition can resolve, render, and still say nothing” — source cannot show this, and neither can a check that the crossref resolves.
5. Fix the vignette spellcheck leak
Custom macro command-names (paren, Ep, expit, Prf, cb, …) leak into spelling::spell_check_package() for .qmd files under vignettes/ — the spelling LaTeX filter strips common commands like \text/\frac but not custom macros. Add every custom macro name used, plus genuine terms (expit, exchangeability), to inst/WORDLIST:
Files under inst/analyses/ are not spell-checked, so this only bites for math in vignettes/.
6. Add missing macros to the submodule when helpful
If a needed concept has no macro, add it to d-morrison/macros via a PR to that repo — do not define a one-off command inline in the manuscript:
Name the new macro for the concept it denotes, not for its typography, so it stays semantic. The macros repo carries a standing mwc grant, so merge the macro PR yourself once it is fully clean (see STANDING_MERGE_GRANT_REPOS in hooks/no-unauthorized-merge.py). Bump the submodule pointer in the manuscript repo once that macro PR merges.
7. Verify the render and spellcheck, then ship
Run the raw-notation lint over what you changed, pointing it at the library so its operator macros extend the rules:
It exits 1 and prints file:line: raw -> use macro for each hit. hooks/warn-raw-math-notation.py runs the lint’s built-in patterns at write time and warns, never blocks.
Commit the rewritten .qmd, the include line, the submodule pointer, and the WORDLIST on a branch, open a PR, and drive it to clean.
Relationship to other skills
convert-repo-format— scaffolds themacros/submodule when converting a repo to a Quarto book/website (git submodule add); this skill rewrites math onto an already-present submodule. Adjacent concerns.use-preferred-style/find-ai-tells— the prose counterparts: they polish and de-slop written prose; this skill polishes math notation.memorize/remember— the paired standing rule (“always use the macros submodule for math”) lives inmemories/preferences.md; this skill is the executable how.ardi/request-pr-review— used to ship and clean the PR this skill produces.
Macros that \renewcommand a standard command take a mandatory argument
macros.qmd redefines \exp (\renewcommand{\exp}[1]{\operatorname{exp}\cb{#1}}), \vec, and \v with one mandatory argument each. A bare $\exp$ (the function name, no argument) makes the macro swallow the closing math delimiter as its argument, which leaves \cb’s \left/\right unbalanced. MathJax renders that without complaint; lualatex fails with Missing \right. inserted. So an HTML render is no evidence that the PDF builds.
Measured 2026-09-28 on Morrison-Lab/pds: Quarto Publish run 36516449603 failed on $\exp$ in _subfiles/_sec-distributions.qmd. Fixed by Morrison-Lab/pds#34, which spelled out “the exponential function”; the zero-argument \expt also works. Tracked in Morrison-Lab/pds#33.
- Do: name the function in prose, or use a zero-argument form (
\expt), when no argument is meant. - Do: grep for bare uses, e.g.
grep -rnE '\\(exp|vec|v)([^a-zA-Z{]|$)' --include='*.qmd', and render the PDF target when touching math. The grep is a candidate finder: it also flags valid space-separated arguments (\vec \beta) and the definitions themselves, so read each hit. - Don’t: write a bare
$\exp$(or\vec,\v). - Don’t: treat a clean HTML render as evidence the PDF builds.
Common undefined shorthand and macro collisions in macros.qmd
- Undefined matrix and vector shorthands (
\mA,\vw): whilemacros.qmddefines\mX(\matr{X}),\mx,\vx(\vecf{x}),\va, etc., it does not define arbitrary shorthands like\mAor\vw. Bare\mAor\vware undefined and break LuaLaTeX during PDF compilation. Use\matr{A}for matrices and\vec{w}or\vecf{w}for vectors. - Latin vector collision with
\vb:macros.qmddefines\def\b{\beta}and\def\vb{\vec \b}, which expands to Greek\vec{\beta}rather than Latin vectorb. When denoting a Latin vector (such as an intercept or bias vector \(b\)), do not use\vb; use\vec{b}or\vecf{b}. (Morrison-Lab/mds#48, 2026-09-29.)
Anti-patterns
- ❌ Inventing a macro name not defined in
macros.qmd— it silently breaks the render. Verify every command resolves (step 4). - ❌ Using bare
\mAor\vwexpecting them to expand to matrices or vectors — they are undefined inmacros.qmdand break LuaLaTeX during PDF compilation. Use\matr{A}and\vec{w}/\vecf{w}instead. - ❌ Using
\vbfor Latin vector \(b\) (e.g. bias or intercept) —macros.qmddefines\vbas\vec{\beta}(Greek beta), colliding with Latin vector \(b\). Use\vec{b}or\vecf{b}instead. - ❌ Running
git submodule update --remotein a checkout that is running provenance-stamped SLURM jobs — it dirties the tree and poisons the run. Use a worktree. - ❌ Forgetting the
inst/WORDLISTadditions for macros used invignettes/math — spellcheck fails on the leaked command-names. - ❌ Defining a one-off
\newcommandinline instead of adding it to the shared submodule. - ❌ Defining a built-in-shadowing macro (
\v,\b,\u,\c, accents, …) with\providecommand— MathJax skips it because the built-in is already defined, so the built-in survives and the render breaks silently. Use\def/\renewcommandfor those names (see step 2 andmemories/preferences.md). - ❌ Defining a macro’d concept by restating another macro (
\score \eqdef \llik') — the aliases collapse, so the rendered page shows a symbol defined as itself while the source reads as a proper definition. Spell the operator out (\deriv,\dderiv), and check the rendered output rather than the source. - ❌ Changing a formula’s meaning while “condensing” — preserve the math exactly; only re-express it.