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 (\Ep for a bare expectation operator, \E{x} for one subscripted by x, \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; see memories/preferences.md).

Procedure

1. Locate and check out the macros submodule

It is typically vendored at inst/analyses/macros (URL in .gitmodules):

grep -A2 'submodule.*macros' .gitmodules      # confirm path + d-morrison/macros URL
git submodule update --init inst/analyses/macros   # checkout at recorded commit

To bring it up to date with d-morrison/macros:

git submodule update --remote inst/analyses/macros

--remote bumps the tracked gitlink, which dirties git diff HEAD. If the checkout is running SLURM/simulation jobs that stamp git provenance at completion (e.g. a consolidate_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:

# working example — inst/analyses/paper.qmd:
#   {{< include macros/macros.qmd >}}
# from vignettes/articles/ the relative path is:
#   {{< include ../../inst/analyses/macros/macros.qmd >}}
grep -rn 'include .*macros/macros.qmd' <manuscript-dir>   # check if already wired

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:

grep -cE '^\\(def|providecommand|newcommand|renewcommand)' inst/analyses/macros/macros.qmd   # ~600+ macros
grep -nE '\\(def|providecommand|newcommand|renewcommand)\{?\\(Ep|Prf|paren|sb|cb|expit|logit|Var|hp|S|h)\b' \
  inst/analyses/macros/macros.qmd

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 \def and \renewcommand too.

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:

# what each side of the definition actually expands to
grep -nE '^\\def\\(score|hess|llik)\b' <macros-dir>/macros.qmd

# whether the library already defines this concept canonically
grep -nE '^\\def\\def[A-Z]' <macros-dir>/macros.qmd

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:

printf 'expit\nexchangeability\nparen\nEp\nPrf\n' >> inst/WORDLIST   # + the names step 4 listed
LC_ALL=C sort -u -o inst/WORDLIST 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:

cd inst/analyses/macros
git checkout -b add-<concept>-macro
# edit macros.qmd, then push + open a PR to d-morrison/macros

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:

python3 <ai-config>/scripts/check-raw-math.py --macros inst/analyses/macros/macros.qmd <changed files or dirs>

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.

quarto render <manuscript>.qmd            # must render with the macros include
Rscript -e 'print(spelling::spell_check_package())'   # must be 0 rows

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 the macros/ 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 in memories/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): while macros.qmd defines \mX (\matr{X}), \mx, \vx (\vecf{x}), \va, etc., it does not define arbitrary shorthands like \mA or \vw. Bare \mA or \vw are 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.qmd defines \def\b{\beta} and \def\vb{\vec \b}, which expands to Greek \vec{\beta} rather than Latin vector b. 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 \mA or \vw expecting them to expand to matrices or vectors — they are undefined in macros.qmd and break LuaLaTeX during PDF compilation. Use \matr{A} and \vec{w} / \vecf{w} instead.
  • ❌ Using \vb for Latin vector \(b\) (e.g. bias or intercept) — macros.qmd defines \vb as \vec{\beta} (Greek beta), colliding with Latin vector \(b\). Use \vec{b} or \vecf{b} instead.
  • ❌ Running git submodule update --remote in a checkout that is running provenance-stamped SLURM jobs — it dirties the tree and poisons the run. Use a worktree.
  • ❌ Forgetting the inst/WORDLIST additions for macros used in vignettes/ math — spellcheck fails on the leaked command-names.
  • ❌ Defining a one-off \newcommand inline 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 / \renewcommand for those names (see step 2 and memories/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.
Back to top