Skip to content

Commit aca1c57

Browse files
Fix equation rendering in paper web editions
The newer two-sides tex added \label{eq:*}/\eqref{eq:*}, which the sed-based web build passed through: KaTeX rendered the labels as literal red "\labeleq:..." and pandoc left dead [eq:schur] stubs. docs/_webtex.py now reproduces LaTeX's numbering: each equation environment increments a counter, \label{eq:X} becomes a KaTeX \tag{n}, and every \eqref{eq:X} resolves to (n) in the prose. Regenerated both web editions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 97c150d commit aca1c57

3 files changed

Lines changed: 94 additions & 61 deletions

File tree

docs/_webtex.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python3
2+
"""Preprocess a paper's LaTeX for the web edition (used by build_paper.sh).
3+
4+
Numbered equation environments work in the PDF but not in KaTeX: a \\label inside
5+
math renders as a literal red "\\labeleq:..." and \\eqref becomes a dead [eq:...]
6+
stub. This script reproduces LaTeX's numbering instead: each \\begin{equation}
7+
increments a counter; a \\label{eq:X} inside it becomes a KaTeX \\tag{n} and the
8+
mapping X -> n resolves every \\eqref{eq:X} in the prose to (n). Equation
9+
environments are then converted to \\[ \\] display math, which KaTeX renders.
10+
11+
Usage: _webtex.py input.tex output.tex
12+
"""
13+
import re
14+
import sys
15+
16+
17+
def main(src_path: str, out_path: str) -> None:
18+
tex = open(src_path).read()
19+
20+
numbers: dict[str, int] = {}
21+
counter = 0
22+
23+
def process_equation(m: re.Match) -> str:
24+
nonlocal counter
25+
counter += 1
26+
body = m.group(1)
27+
28+
def label_to_tag(lm: re.Match) -> str:
29+
numbers[lm.group(1)] = counter
30+
return rf"\tag{{{counter}}}"
31+
32+
body = re.sub(r"\\label\{(eq:[^}]*)\}", label_to_tag, body)
33+
return rf"\[{body}\]"
34+
35+
tex = re.sub(r"\\begin\{equation\}(.*?)\\end\{equation\}", process_equation,
36+
tex, flags=re.DOTALL)
37+
38+
def resolve_eqref(m: re.Match) -> str:
39+
key = m.group(1)
40+
if key not in numbers:
41+
raise SystemExit(f"_webtex.py: \\eqref{{{key}}} has no matching labeled equation")
42+
return f"({numbers[key]})"
43+
44+
tex = re.sub(r"\\eqref\{(eq:[^}]*)\}", resolve_eqref, tex)
45+
46+
open(out_path, "w").write(tex)
47+
48+
49+
if __name__ == "__main__":
50+
main(sys.argv[1], sys.argv[2])

docs/build_paper.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ root="$(dirname "$here")"
1111
# build_one <tex-basename> <out-slug> <title> <subtitle>
1212
# The PDF lives next to the .tex in papers/ and is linked from the web header.
1313
# The source keeps numbered equation environments (for the arXiv/PDF edition); for the
14-
# web we strip them to unnumbered \[ \] display math, which KaTeX renders cleanly. The
15-
# prose refers to equations descriptively, so no cross-references break in the process.
14+
# web, docs/_webtex.py reproduces LaTeX's equation numbering: \label{eq:X} becomes a
15+
# KaTeX \tag{n}, every \eqref{eq:X} in the prose resolves to (n), and the equation
16+
# environments become \[ \] display math, which KaTeX renders cleanly.
1617
build_one() {
1718
local tex="$1" slug="$2" title="$3" subtitle="$4"
1819
local pdfurl="https://github.com/microprediction/precise/blob/main/papers/${tex}.pdf"
1920
local websrc; websrc="$(mktemp)"
20-
sed -e 's/\\begin{equation}/\\[/g' -e 's/\\end{equation}/\\]/g' "$root/papers/${tex}.tex" > "$websrc"
21+
python3 "$here/_webtex.py" "$root/papers/${tex}.tex" "$websrc"
2122
mkdir -p "$here/papers/$slug"
2223
pandoc "$websrc" \
2324
--from=latex \

docs/papers/two-sides-of-schur-damping/index.html

Lines changed: 40 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ <h2 id="the-shared-object-one-schur-complement-two-readings">The shared
118118
partition the variables into a block <span class="math inline">k</span>
119119
and a conditioning set <span class="math inline">c</span> (its spatial
120120
neighbours, or the other assets). The Schur complement <span
121-
class="math display">\label{eq:schur}
121+
class="math display">\tag{1}
122122
\mathsf S_k \;=\; R_{kk}-R_{kc}R_{cc}^{-1}R_{ck}</span> carries the
123123
same algebra into two meanings.</p>
124124
<p><strong>Weather (a pseudo-likelihood term).</strong> <span
@@ -151,20 +151,17 @@ <h2 id="the-shared-object-one-schur-complement-two-readings">The shared
151151
statistician, a hedged residual risk for the investor.</p>
152152
<h5 id="a-two-variable-example.">A two-variable example.</h5>
153153
<p>Take two standardized variables with correlation <span
154-
class="math inline">\rho</span>. The Schur complement <a
155-
href="#eq:schur" data-reference-type="eqref"
156-
data-reference="eq:schur">[eq:schur]</a> is the scalar <span
157-
class="math inline">\mathsf S=1-\rho^2</span>. To the statistician it is
158-
the variance of variable 2 conditional on variable 1, <span
154+
class="math inline">\rho</span>. The Schur complement (1) is the scalar
155+
<span class="math inline">\mathsf S=1-\rho^2</span>. To the statistician
156+
it is the variance of variable 2 conditional on variable 1, <span
159157
class="math inline">\operatorname{Var}(y_2\mid y_1)=1-\rho^2</span>—the
160158
term the pseudo-likelihood scores. To the investor it is the variance of
161159
variable 2 <em>net of its hedge</em> by variable 1: with optimal hedge
162160
ratio <span class="math inline">b=\rho</span>, the residual <span
163161
class="math inline">y_2-b\,y_1</span> has variance <span
164162
class="math inline">1-\rho^2</span>, the risk that remains after the
165-
hedge. Identical number, two stories. Damping <a href="#eq:damp"
166-
data-reference-type="eqref" data-reference="eq:damp">[eq:damp]</a> gives
167-
<span class="math inline">\mathsf S(\gamma)=(1-\gamma)\cdot
163+
hedge. Identical number, two stories. Damping (2) gives <span
164+
class="math inline">\mathsf S(\gamma)=(1-\gamma)\cdot
168165
1+\gamma(1-\rho^2)=1-\gamma\,\rho^2</span>: at <span
169166
class="math inline">\gamma=1</span> the full hedge / full conditioning,
170167
at <span class="math inline">\gamma=0</span> none (the marginal
@@ -181,7 +178,7 @@ <h2 id="the-shared-cure-damping-by-reliability">The shared cure: damping
181178
class="math inline">\mathsf S_k</span> overfit—and both apply the same
182179
cure, a convex damping by <span
183180
class="math inline">\gamma\in[0,1]</span>: <span
184-
class="math display">\label{eq:damp}
181+
class="math display">\tag{2}
185182
b_k(\gamma)=\gamma\,b_k,\qquad \mathsf
186183
S_k(\gamma)=(1-\gamma)\,R_{kk}+\gamma\,\mathsf S_k.</span> At <span
187184
class="math inline">\gamma=1</span> this is full conditioning—the exact
@@ -190,14 +187,13 @@ <h2 id="the-shared-cure-damping-by-reliability">The shared cure: damping
190187
block-diagonal composite likelihood, hierarchical risk parity.
191188
Intermediate <span class="math inline">\gamma</span> trusts the
192189
estimated cross-coupling only partway. The weather and portfolio
193-
extremes are the <em>same</em> two endpoints of <a href="#eq:damp"
194-
data-reference-type="eqref" data-reference="eq:damp">[eq:damp]</a>.</p>
190+
extremes are the <em>same</em> two endpoints of (2).</p>
195191
<p>The optimal <span class="math inline">\gamma</span> has a closed
196192
form. For a single coupling of conditional <span
197193
class="math inline">R^2</span> equal to <span
198194
class="math inline">\rho^2</span> estimated from <span
199195
class="math inline">n</span> points, minimizing expected error gives the
200-
<em>reliability</em> <span class="math display">\label{eq:gstar}
196+
<em>reliability</em> <span class="math display">\tag{3}
201197
\gamma^\star=\frac{(n-2)\rho^2}{(n-2)\rho^2+(1-\rho^2)},</span> a
202198
Wiener/James–Stein shrinkage <span class="citation"
203199
data-cites="james1961 ledoit2012">(James and Stein 1961; Ledoit and Wolf
@@ -214,9 +210,7 @@ <h2 id="the-shared-cure-damping-by-reliability">The shared cure: damping
214210
<em>operation</em> it scales. It is <em>not</em> the raw off-diagonal
215211
entries shrunk elementwise toward zero; it is the conditional regression
216212
<span class="math inline">b_k</span> and its Schur complement <span
217-
class="math inline">\mathsf S_k</span>, damped through <a
218-
href="#eq:damp" data-reference-type="eqref"
219-
data-reference="eq:damp">[eq:damp]</a>—a structured,
213+
class="math inline">\mathsf S_k</span>, damped through (2)—a structured,
220214
positive-definiteness-preserving shrinkage of the <em>conditional</em>.
221215
Damping <span class="math inline">b_k</span> by <span
222216
class="math inline">\gamma</span> scales the cross-covariance by <span
@@ -243,16 +237,15 @@ <h2 id="the-shared-cure-damping-by-reliability">The shared cure: damping
243237
precursor <span class="citation" data-cites="cotton2025psl">(Cotton
244238
2025)</span>, which did cite the Vecchia factorization—but only as a
245239
computational device for the block-conditional likelihood, without
246-
recognizing that the damping <a href="#eq:damp"
247-
data-reference-type="eqref" data-reference="eq:damp">[eq:damp]</a> is a
248-
<em>regularized</em> Vecchia conditioning, nor its place in the
249-
scalable-Gaussian-process literature, nor that the same reliability is
250-
precisely the knob of Schur-complementary allocation. (We have since
251-
learned that ShrinkTM <span class="citation"
252-
data-cites="chakraborty2025">(Chakraborty and Katzfuss 2025)</span> was
253-
independently arriving at the conditional shrinkage at the same time,
254-
from the spatial side.) That these were one object went unnoticed when
255-
the first note was written; surfacing it is the purpose of this one.</p>
240+
recognizing that the damping (2) is a <em>regularized</em> Vecchia
241+
conditioning, nor its place in the scalable-Gaussian-process literature,
242+
nor that the same reliability is precisely the knob of
243+
Schur-complementary allocation. (We have since learned that ShrinkTM
244+
<span class="citation" data-cites="chakraborty2025">(Chakraborty and
245+
Katzfuss 2025)</span> was independently arriving at the conditional
246+
shrinkage at the same time, from the spatial side.) That these were one
247+
object went unnoticed when the first note was written; surfacing it is
248+
the purpose of this one.</p>
256249
<h2 id="what-each-side-already-solved">What each side already
257250
solved</h2>
258251
<p>Read as one operation, the two literatures are complementary rather
@@ -261,9 +254,8 @@ <h2 id="what-each-side-already-solved">What each side already
261254
data-reference="tab:sides">1</a>).</p>
262255
<div id="tab:sides">
263256
<table>
264-
<caption>The same Schur damping <a href="#eq:damp"
265-
data-reference-type="eqref" data-reference="eq:damp">[eq:damp]</a>, as
266-
developed on each side.</caption>
257+
<caption>The same Schur damping (2), as developed on each
258+
side.</caption>
267259
<thead>
268260
<tr>
269261
<th style="text-align: left;"></th>
@@ -314,10 +306,9 @@ <h2 id="what-each-side-already-solved">What each side already
314306
<em>learning</em> the damping toward a fitted base <span
315307
class="citation" data-cites="chakraborty2025">(Chakraborty and Katzfuss
316308
2025)</span>. The allocation side contributes the <em>closed form</em>
317-
<a href="#eq:gstar" data-reference-type="eqref"
318-
data-reference="eq:gstar">[eq:gstar]</a> and the recognition that the
319-
same <span class="math inline">\gamma</span> is an investment decision,
320-
not only a regularizer. Neither side had both.</p>
309+
(3) and the recognition that the same <span
310+
class="math inline">\gamma</span> is an investment decision, not only a
311+
regularizer. Neither side had both.</p>
321312
<p>The two sides also shrink toward different <em>targets</em>, and the
322313
asymmetry is not arbitrary: each shrinks toward the structure it can
323314
trust. A spatial field has a credible parametric model—a smooth Matérn
@@ -330,13 +321,9 @@ <h2 id="what-each-side-already-solved">What each side already
330321
class="math inline">\gamma=0</span> / HRP limit). Where weather has
331322
structure to believe, finance has structure to doubt—so it is
332323
unsurprising, in hindsight, that the spatial road centers on a base GP
333-
while the allocation road errs toward zero coupling. The operation <a
334-
href="#eq:damp" data-reference-type="eqref"
335-
data-reference="eq:damp">[eq:damp]</a> and its optimal intensity <a
336-
href="#eq:gstar" data-reference-type="eqref"
337-
data-reference="eq:gstar">[eq:gstar]</a> are the same; only the prior
338-
they lean on differs, in the direction each domain’s experience
339-
warrants.</p>
324+
while the allocation road errs toward zero coupling. The operation (2)
325+
and its optimal intensity (3) are the same; only the prior they lean on
326+
differs, in the direction each domain’s experience warrants.</p>
340327
<h2 id="an-example-importing-vecchia-into-allocation">An example:
341328
importing Vecchia into allocation</h2>
342329
<p>The clearest way to show the connection is useful is to carry a tool
@@ -349,9 +336,8 @@ <h2 id="an-example-importing-vecchia-into-allocation">An example:
349336
<p>We apply Vecchia conditioning to daily asset returns—a setting with
350337
no parametric base, using a correlation-based neighbour ordering of the
351338
kind the spatial literature adopts when Euclidean distance is
352-
unavailable—and compare three settings of the damping <a href="#eq:damp"
353-
data-reference-type="eqref" data-reference="eq:damp">[eq:damp]</a>:
354-
undamped (<span class="math inline">\gamma=1</span>, plain Vecchia /
339+
unavailable—and compare three settings of the damping (2): undamped
340+
(<span class="math inline">\gamma=1</span>, plain Vecchia /
355341
minimum-variance conditioning), the closed-form reliability <span
356342
class="math inline">\gamma^\star</span>, and a single intensity tuned on
357343
a held-out split (the spatial community’s <em>fit-the-shrinkage</em>
@@ -466,9 +452,8 @@ <h2 id="an-example-importing-vecchia-into-allocation">An example:
466452
actually operates.</p>
467453
<h2 id="the-reverse-import-robustifying-the-hedge">The reverse import:
468454
robustifying the hedge</h2>
469-
<p>The transfer also runs the other way. The damping <a href="#eq:damp"
470-
data-reference-type="eqref" data-reference="eq:damp">[eq:damp]</a> can
471-
be used as an <em>estimator</em>— form each block’s hedge <span
455+
<p>The transfer also runs the other way. The damping (2) can be used as
456+
an <em>estimator</em>— form each block’s hedge <span
472457
class="math inline">b_k</span> and Schur complement <span
473458
class="math inline">\mathsf S_k</span>, damp by <span
474459
class="math inline">\gamma^\star</span>, and reassemble the implied
@@ -506,18 +491,15 @@ <h2 id="discussion">Discussion</h2>
506491
class="math inline">\gamma=0</span> and <span
507492
class="math inline">\gamma=1</span> ends of the very damping that a
508493
weather model applies to stay estimable is not a metaphor—it is the same
509-
Schur complement <a href="#eq:schur" data-reference-type="eqref"
510-
data-reference="eq:schur">[eq:schur]</a> and the same convex combination
511-
<a href="#eq:damp" data-reference-type="eqref"
512-
data-reference="eq:damp">[eq:damp]</a>, read once as a conditional
513-
variance and once as a residual risk. Practical consequences run both
514-
ways. Allocation can borrow the spatial machinery: neighbour/cluster
515-
orderings and empirical-Bayes-fitted damping in place of a fixed <span
516-
class="math inline">\gamma</span>. Spatial modeling can borrow the
517-
allocation reading: the closed-form reliability as a tuning-free
518-
initializer, and the reminder that the damping is a decision with a
519-
cost, not merely a prior. And both sit on one online primitive—a Schur
520-
complement of a few neighbour blocks, damped by a
494+
Schur complement (1) and the same convex combination (2), read once as a
495+
conditional variance and once as a residual risk. Practical consequences
496+
run both ways. Allocation can borrow the spatial machinery:
497+
neighbour/cluster orderings and empirical-Bayes-fitted damping in place
498+
of a fixed <span class="math inline">\gamma</span>. Spatial modeling can
499+
borrow the allocation reading: the closed-form reliability as a
500+
tuning-free initializer, and the reminder that the damping is a decision
501+
with a cost, not merely a prior. And both sit on one online primitive—a
502+
Schur complement of a few neighbour blocks, damped by a
521503
reliability—maintainable incrementally in <span
522504
class="math inline">O(p\,m^2)</span>. This is the form in which the
523505
online covariance library <code>precise</code><a href="#fn1"

0 commit comments

Comments
 (0)