feat: land superpaper v1 notes scaffold
Add the ledger schema, class router, lint codes, ingest/build pipeline, and three work-tree examples: align derivation, superfig delegation, and supertensor delegation.
@@ -1,8 +1,16 @@
|
||||
work/
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.aux
|
||||
*.log
|
||||
*.out
|
||||
*.fls
|
||||
*.fdb_latexmk
|
||||
*.synctex.gz
|
||||
*.toc
|
||||
.DS_Store
|
||||
claims.md
|
||||
**/notes/notes.pdf
|
||||
**/notes/sections/symbols.tex
|
||||
**/out/
|
||||
|
||||
@@ -45,6 +45,13 @@ skills/supertensor -> superpaper/supertensor
|
||||
|
||||
## Status
|
||||
|
||||
The family contract lives in [`DESIGN.md`](DESIGN.md). Figure children
|
||||
are usable now. The notes scaffold (`scripts/`, ledger, template) is
|
||||
the SuperPaper v1 PR chain in that document — not in this commit.
|
||||
v1 notes scaffold is in this repo: ledger schema, router, lint, ingest,
|
||||
and three `--work` examples (`excerpt-toy`, `pipeline-delegate`,
|
||||
`tensor-delegate`). `superderive` is still phase 2.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
|
||||
./scripts/preflight.sh
|
||||
./scripts/test.sh
|
||||
./scripts/build.sh --work examples/excerpt-toy
|
||||
```
|
||||
|
||||
@@ -17,8 +17,8 @@ description: >-
|
||||
`ledger.yaml` + 结构化中文笔记。图一律委派给子仓库,不要在笔记里
|
||||
`\usepackage{superfig}` / `supertensor`。
|
||||
|
||||
本目录是家族母仓库。子仓库在 `superfig/` 与 `supertensor/`(git submodule)。
|
||||
完整合同见 `DESIGN.md`。笔记脚手架(`scripts/`、模板、schema)按 DESIGN 的 PR 1 落地;在此之前按下面路由手工委派。
|
||||
本目录是家族母仓库。子仓库在 `superfig/` 与 `supertensor/`。
|
||||
契约细节按需加载 `references/`;schema 在 `assets/ledger.schema.yaml`。
|
||||
|
||||
## When to use
|
||||
|
||||
@@ -43,12 +43,15 @@ video.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. 读 `DESIGN.md` 的输入契约与 ledger schema。
|
||||
2. 先写 `ledger.yaml`(claims / symbols / derivations / figure plan),再写笔记。
|
||||
3. 公式三拍:中文动机 → `\[` / `align` → 扁平符号表。
|
||||
4. 每张计划图写一份自包含 `F*.request.md`,开一个 figure agent,**只**把对应子仓库的 `SKILL.md` 给它。
|
||||
5. 笔记用 PDF `\includegraphics` 嵌入子仓库产物。禁止 `\input` standalone 源。
|
||||
6. 改术语或改图:先改 ledger,再改那一处。
|
||||
1. `./scripts/preflight.sh`。0 = 全路径,1 = 降级(说出口),2 = 无 LaTeX。
|
||||
2. `./scripts/ingest.sh --work <dir> …`(`references/input.md`)。
|
||||
3. 从 `assets/ledger.example.yaml` 写 `ledger.yaml`,再写 `outline.md`,再写正文。
|
||||
4. 公式三拍:中文动机 → `\[` / `align` → 扁平符号表(`references/pedagogy.md`)。
|
||||
5. 按 `references/router.md` 填 figure plan。每张重绘图一份 `F*.request.md`,figure agent 只读它和对应子仓库 `SKILL.md`。
|
||||
6. `./scripts/lint.py --work <dir>` 然后 `./scripts/build.sh --work <dir>`。
|
||||
7. 改术语或改图:先改 ledger,再改那一处。
|
||||
|
||||
Worked trees: `examples/excerpt-toy/`(`align` 推导)、`examples/pipeline-delegate/`、`examples/tensor-delegate/`。
|
||||
|
||||
## Spawn recipe
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: "example"
|
||||
title: ""
|
||||
authors: []
|
||||
notes_language: zh
|
||||
source:
|
||||
kind: excerpt
|
||||
coverage:
|
||||
mode: excerpt
|
||||
sections_in: []
|
||||
sections_skipped: []
|
||||
questions: []
|
||||
claims: []
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures: []
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,210 @@
|
||||
$schema: "http://json-schema.org/draft-07/schema#"
|
||||
$id: "https://local/superpaper.ledger/v1"
|
||||
type: object
|
||||
required: [schema, paper, coverage]
|
||||
additionalProperties: true
|
||||
properties:
|
||||
schema: {const: superpaper.ledger/v1}
|
||||
retired_ids:
|
||||
type: array
|
||||
items: {type: string, pattern: "^(C|Q|D|A|L|E|DER|F|SA)[1-9][0-9]*$"}
|
||||
paper:
|
||||
type: object
|
||||
required: [id, title, source]
|
||||
additionalProperties: true
|
||||
properties:
|
||||
id: {type: string, minLength: 1}
|
||||
title: {type: string}
|
||||
authors: {type: array, items: {type: string}}
|
||||
year: {type: integer}
|
||||
venue: {type: string}
|
||||
notes_language: {type: string, enum: [zh, en]}
|
||||
degraded:
|
||||
type: array
|
||||
items: {type: string, enum: [scanned, no-eprint, no-pdftotext]}
|
||||
source:
|
||||
type: object
|
||||
required: [kind]
|
||||
additionalProperties: true
|
||||
properties:
|
||||
kind: {enum: [arxiv, pdf, tex, excerpt, markdown]}
|
||||
arxiv: {type: string}
|
||||
local_pdf: {type: string}
|
||||
pages: {type: integer, minimum: 1}
|
||||
language: {type: string}
|
||||
coverage:
|
||||
type: object
|
||||
required: [mode]
|
||||
additionalProperties: true
|
||||
properties:
|
||||
mode: {enum: [full, excerpt, body-only]}
|
||||
sections_in: {type: array, items: {type: string}}
|
||||
sections_skipped: {type: array, items: {type: string}}
|
||||
skip_reasons:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [section, reason]
|
||||
properties:
|
||||
section: {type: string}
|
||||
reason: {type: string}
|
||||
questions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, text]
|
||||
properties:
|
||||
id: {type: string, pattern: "^Q[1-9][0-9]*$"}
|
||||
text: {type: string}
|
||||
source: {type: string}
|
||||
claims:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, text, kind, status]
|
||||
properties:
|
||||
id: {type: string, pattern: "^C[1-9][0-9]*$"}
|
||||
text: {type: string}
|
||||
kind: {enum: [contribution, theoretical, empirical, methodological]}
|
||||
status: {enum: [core, supporting, dropped]}
|
||||
supports: {type: array, items: {type: string}}
|
||||
depends_on: {type: array, items: {type: string}}
|
||||
evidence: {type: array, items: {type: string}}
|
||||
source: {type: string}
|
||||
definitions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, name, text]
|
||||
properties:
|
||||
id: {type: string, pattern: "^D[1-9][0-9]*$"}
|
||||
name: {type: string}
|
||||
text: {type: string}
|
||||
source: {type: string}
|
||||
assumptions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, text]
|
||||
properties:
|
||||
id: {type: string, pattern: "^A[1-9][0-9]*$"}
|
||||
text: {type: string}
|
||||
source: {type: string}
|
||||
used_by: {type: array, items: {type: string}}
|
||||
lemmas:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, text]
|
||||
properties:
|
||||
id: {type: string, pattern: "^L[1-9][0-9]*$"}
|
||||
text: {type: string}
|
||||
depends_on: {type: array, items: {type: string}}
|
||||
used_by: {type: array, items: {type: string}}
|
||||
source: {type: string}
|
||||
symbols:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [name, latex, meaning, kind]
|
||||
properties:
|
||||
name: {type: string}
|
||||
latex: {type: string}
|
||||
meaning: {type: string}
|
||||
domain: {type: string}
|
||||
kind:
|
||||
enum: [value, score, probability, index, rank, count, id, mask,
|
||||
permutation, "shape parameter", scalar, set]
|
||||
introduced: {type: string}
|
||||
used: {type: array, items: {type: string}}
|
||||
aliases: {type: array, items: {type: string}}
|
||||
derivations:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, claim, title, expand]
|
||||
properties:
|
||||
id: {type: string, pattern: "^DER[1-9][0-9]*$"}
|
||||
claim: {type: string}
|
||||
title: {type: string}
|
||||
source: {type: string}
|
||||
expand: {type: boolean}
|
||||
figure: {type: ["string", "null"]}
|
||||
steps:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, from, to, rule]
|
||||
properties:
|
||||
id: {type: string}
|
||||
from: {type: string}
|
||||
to: {type: string}
|
||||
rule:
|
||||
enum: [definition, substitute, cancel, factor, scale,
|
||||
take-limit, approx, cite, rearrange, introduce]
|
||||
cite: {type: string}
|
||||
justify: {type: string}
|
||||
figures:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, claim, title, grammar, toolkit, signals, status]
|
||||
properties:
|
||||
id: {type: string, pattern: "^F[1-9][0-9]*$"}
|
||||
claim: {type: string}
|
||||
title: {type: string}
|
||||
grammar:
|
||||
enum: [architecture, pipeline, data-flow, state, time, dependency,
|
||||
argument-map, tensor-face, derivation, screenshot, plot,
|
||||
notation, none]
|
||||
toolkit:
|
||||
enum: [superfig, supertensor, superderive, screenshot,
|
||||
matplotlib, none, align]
|
||||
signals: {type: array, items: {type: string}}
|
||||
source_fig: {type: string}
|
||||
source_pages: {type: array, items: {type: integer, minimum: 1}}
|
||||
crop_bbox:
|
||||
type: ["array", "null"]
|
||||
minItems: 4
|
||||
maxItems: 4
|
||||
items: {type: number}
|
||||
request: {type: ["string", "null"]}
|
||||
include: {type: ["string", "null"]}
|
||||
status: {enum: [planned, delegated, built, included, dropped]}
|
||||
drop_reason: {type: ["string", "null"]}
|
||||
evidence:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, kind, source, supports, handling]
|
||||
properties:
|
||||
id: {type: string, pattern: "^E[1-9][0-9]*$"}
|
||||
kind: {enum: [table, plot, ablation, theorem, example]}
|
||||
source: {type: string}
|
||||
supports: {type: array, items: {type: string}}
|
||||
handling:
|
||||
enum: [redraw-superfig, redraw-supertensor, redraw-superderive,
|
||||
screenshot, matplotlib, omit]
|
||||
terms:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [canonical]
|
||||
properties:
|
||||
canonical: {type: string}
|
||||
aliases: {type: array, items: {type: string}}
|
||||
first_defined: {type: string}
|
||||
source_assets:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, kind, handling]
|
||||
properties:
|
||||
id: {type: string, pattern: "^SA[1-9][0-9]*$"}
|
||||
kind: {enum: [figure, table]}
|
||||
paper_ref: {type: string}
|
||||
handling:
|
||||
enum: [redraw-superfig, redraw-supertensor, redraw-superderive,
|
||||
screenshot, matplotlib, omit]
|
||||
pages: {type: array, items: {type: integer}}
|
||||
note: {type: string}
|
||||
@@ -0,0 +1,54 @@
|
||||
% Shared notes preamble. TEXINPUTS must include superpaper/assets/.
|
||||
\usepackage[fontset=fandol]{ctex}
|
||||
\usepackage{amsmath,amssymb}
|
||||
\usepackage{graphicx}
|
||||
\usepackage[margin=2.5cm]{geometry}
|
||||
\usepackage[most]{tcolorbox}
|
||||
\usepackage{etoolbox}
|
||||
\usepackage{listings}
|
||||
\usepackage{booktabs}
|
||||
\usepackage{subcaption}
|
||||
\usepackage{float}
|
||||
\usepackage{tikz}
|
||||
\usepackage{hyperref}
|
||||
|
||||
\newtcolorbox{knowledgebox}[1]{
|
||||
enhanced, colback=blue!5!white, colframe=blue!75!black, colbacktitle=blue!75!black,
|
||||
coltitle=white, fonttitle=\bfseries, title=#1,
|
||||
attach boxed title to top left={yshift=-2mm, xshift=2mm},
|
||||
boxrule=1pt, sharp corners
|
||||
}
|
||||
\newtcolorbox{importantbox}[1]{
|
||||
enhanced, colback=yellow!10!white, colframe=yellow!80!black, colbacktitle=yellow!80!black,
|
||||
coltitle=black, fonttitle=\bfseries, title=#1, sharp corners
|
||||
}
|
||||
\newtcolorbox{warningbox}[1]{
|
||||
enhanced, colback=red!5!white, colframe=red!75!black, colbacktitle=red!75!black,
|
||||
coltitle=white, fonttitle=\bfseries, title=#1, sharp corners
|
||||
}
|
||||
\newtcolorbox{quotebox}[1]{
|
||||
enhanced, breakable,
|
||||
colback=black!3!white, colframe=black!55, colbacktitle=black!55,
|
||||
coltitle=white, fonttitle=\bfseries, title=#1, sharp corners
|
||||
}
|
||||
|
||||
\newcommand{\splabel}[1]{\hypertarget{sp:#1}{}\label{sp:#1}}
|
||||
\newcommand{\spref}[1]{\hyperlink{sp:#1}{\texttt{#1}}}
|
||||
\newcommand{\spsource}[1]{\footnote{来源:#1}}
|
||||
\newcommand{\spfig}[4][0.92\textwidth]{%
|
||||
\begin{figure}[H]\centering
|
||||
\includegraphics[width=#1]{figures/#2/build/#2.pdf}%
|
||||
\caption{#3\protect\footnotemark}\end{figure}
|
||||
\footnotetext{#4}}
|
||||
\newcommand{\spscreenshot}[4][0.92\textwidth]{%
|
||||
\begin{figure}[H]\centering
|
||||
\includegraphics[width=#1]{figures/#2/orig.png}%
|
||||
\caption{#3\protect\footnotemark}\end{figure}
|
||||
\footnotetext{#4}}
|
||||
|
||||
\newcommand{\notetitle}{论文笔记}
|
||||
\newcommand{\noteauthors}{}
|
||||
\newcommand{\notedate}{\today}
|
||||
\newcommand{\notepaper}{}
|
||||
\newcommand{\notevenue}{}
|
||||
\newcommand{\notearxiv}{}
|
||||
@@ -0,0 +1,43 @@
|
||||
\documentclass[a4paper]{article}
|
||||
\input{notes-macros}
|
||||
|
||||
% Locked skeleton (see references/pedagogy.md):
|
||||
% \section{这篇论文在问什么}
|
||||
% \section{主张与贡献} % \splabel{C*}
|
||||
% \section{预备:定义、假设、符号}
|
||||
% % --- outline-chosen mechanism sections ---
|
||||
% \section{实验与证据} % optional
|
||||
% \section{总结与延伸}
|
||||
% \appendix
|
||||
% \section{符号表} % \input{sections/symbols.tex}
|
||||
% \section{推导链一览}
|
||||
% \section{图表清单}
|
||||
%
|
||||
% Formula: Chinese motive, then \[ / align, then a flat symbol list.
|
||||
% Keep figures outside knowledgebox / importantbox / warningbox / quotebox.
|
||||
% Do not \usepackage{superfig|supertensor|superderive}.
|
||||
|
||||
\begin{document}
|
||||
|
||||
\begin{titlepage}
|
||||
\centering
|
||||
\vspace{1.2cm}
|
||||
{\huge\bfseries \notetitle\par}
|
||||
\vspace{0.8cm}
|
||||
{\large \noteauthors\par}
|
||||
\vspace{0.3cm}
|
||||
{\large \notedate\par}
|
||||
\vspace{1.2cm}
|
||||
\begin{tcolorbox}[width=0.9\textwidth, colback=black!2!white, colframe=black!60, sharp corners]
|
||||
\textbf{论文}:\notepaper\par
|
||||
\textbf{venue}:\notevenue\par
|
||||
\textbf{arXiv}:\notearxiv\par
|
||||
\end{tcolorbox}
|
||||
\end{titlepage}
|
||||
|
||||
\tableofcontents
|
||||
\newpage
|
||||
|
||||
\input{sections/sec-01.tex}
|
||||
|
||||
\end{document}
|
||||
@@ -0,0 +1,46 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: excerpt-toy
|
||||
title: A One-Step Predictor
|
||||
authors: ["Fixture"]
|
||||
year: 2026
|
||||
venue: Superpaper examples
|
||||
notes_language: zh
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
sections_in: ["1"]
|
||||
questions:
|
||||
- {id: Q1, text: "一次前向如何得到预测,损失该放在哪?", source: "excerpt"}
|
||||
claims:
|
||||
- id: C1
|
||||
text: "一次前向是 x → f_θ → ŷ;损失在预测之后单独计算"
|
||||
kind: contribution
|
||||
status: core
|
||||
supports: [Q1]
|
||||
source: "excerpt"
|
||||
definitions:
|
||||
- {id: D1, name: "one-step predictor", text: "ŷ = f_θ(x)", source: "excerpt"}
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols:
|
||||
- {name: x, latex: "x", meaning: "输入", kind: value, introduced: "excerpt"}
|
||||
- {name: yhat, latex: "\\hat y", meaning: "预测", kind: value}
|
||||
- {name: L, latex: "L", meaning: "损失", kind: scalar}
|
||||
- {name: d, latex: "d", meaning: "特征维", kind: "shape parameter"}
|
||||
derivations:
|
||||
- id: DER1
|
||||
claim: C1
|
||||
title: "缩放来自方差"
|
||||
source: "excerpt"
|
||||
expand: true
|
||||
figure: null
|
||||
steps:
|
||||
- {id: S1, from: "u^\\top v", to: "u^\\top v / \\sqrt{d}", rule: scale,
|
||||
justify: "点积方差随 d 增长"}
|
||||
figures: []
|
||||
evidence: []
|
||||
terms:
|
||||
- {canonical: "one-step predictor", aliases: ["一次前向"]}
|
||||
source_assets: []
|
||||
@@ -0,0 +1,29 @@
|
||||
\documentclass[a4paper]{article}
|
||||
\input{notes-macros}
|
||||
\renewcommand{\notetitle}{一次前向预测器}
|
||||
\renewcommand{\noteauthors}{Superpaper fixture}
|
||||
\renewcommand{\notepaper}{A One-Step Predictor}
|
||||
\renewcommand{\notevenue}{examples/excerpt-toy}
|
||||
\begin{document}
|
||||
\begin{titlepage}
|
||||
\centering
|
||||
\vspace{2cm}
|
||||
{\huge\bfseries \notetitle\par}
|
||||
\vspace{1cm}
|
||||
{\large \notepaper\par}
|
||||
\vspace{0.5cm}
|
||||
{\large \noteauthors\par}
|
||||
\end{titlepage}
|
||||
\tableofcontents
|
||||
\newpage
|
||||
\input{sections/sec-01.tex}
|
||||
\input{sections/sec-02.tex}
|
||||
\input{sections/sec-03.tex}
|
||||
\input{sections/sec-04.tex}
|
||||
\input{sections/sec-05.tex}
|
||||
\appendix
|
||||
\section{符号表}
|
||||
\input{sections/symbols.tex}
|
||||
\section{推导链一览}
|
||||
DER1:缩放来自方差,见 \spref{C1}。
|
||||
\end{document}
|
||||
@@ -0,0 +1,3 @@
|
||||
\section{这篇论文在问什么}
|
||||
要把输入变成预测,最简单的机制是什么?损失要不要走在前向主路上?
|
||||
这是摘录 fixture,不假装读完全文。
|
||||
@@ -0,0 +1,3 @@
|
||||
\section{主张与贡献}
|
||||
\splabel{C1}
|
||||
一次前向是 $x \to f_\theta \to \hat y$。损失 $L(\hat y,y)$ 在预测之后单独计算,不是主路上的一站。
|
||||
@@ -0,0 +1,2 @@
|
||||
\section{预备:定义、假设、符号}
|
||||
预测器定义为 $\hat y = f_\theta(x)$。符号见附录。
|
||||
@@ -0,0 +1,18 @@
|
||||
\section{一次前向与损失}
|
||||
内积的方差会随维数 $d$ 涨。为了不让后续非线性饱和,要把点积除掉 $\sqrt{d}$。
|
||||
|
||||
\begin{align}
|
||||
u^\top v &\longrightarrow \frac{u^\top v}{\sqrt{d}}.
|
||||
\end{align}
|
||||
|
||||
\begin{itemize}
|
||||
\item $u,v$ — 两个 $d$ 维向量
|
||||
\item $d$ — 特征维(shape parameter)
|
||||
\end{itemize}
|
||||
|
||||
\begin{importantbox}{主路与损失}
|
||||
损失比较 $\hat y$ 与 $y$,梯度再回到 $\theta$。不要把 $L$ 画成前向的一站。
|
||||
\end{importantbox}
|
||||
|
||||
\subsection{本章小结}
|
||||
前向只负责预测;缩放是改写,不是新算子。
|
||||
@@ -0,0 +1,2 @@
|
||||
\section{总结与延伸}
|
||||
摘录只保留一条机制:一次前向加侧路损失。更长的论文用 ledger 把 claim 钉住,再按路由出图。
|
||||
@@ -0,0 +1,17 @@
|
||||
# Outline: A One-Step Predictor
|
||||
|
||||
## Lecture map
|
||||
|
||||
| file | lecture_title | paper_sections | ledger_ids |
|
||||
|---|---|---|---|
|
||||
| sec-01.tex | 这篇论文在问什么 | 1 | Q1 |
|
||||
| sec-02.tex | 主张与贡献 | 1 | C1 |
|
||||
| sec-03.tex | 预备:定义、假设、符号 | 1 | D1 |
|
||||
| sec-04.tex | 一次前向与损失 | 1 | C1, DER1 |
|
||||
| sec-05.tex | 总结与延伸 | 1 | C1 |
|
||||
| sec-app-a.tex | 符号表 | — | |
|
||||
| sec-app-b.tex | 推导链一览 | — | DER1 |
|
||||
|
||||
## Locked
|
||||
- 首节标题必须是「这篇论文在问什么」
|
||||
- 末节(appendix 前)必须是「总结与延伸」
|
||||
@@ -0,0 +1,8 @@
|
||||
# A One-Step Predictor (fixture)
|
||||
|
||||
We predict $\hat y = f_\theta(x)$ in one forward pass. The loss
|
||||
$L(\hat y, y)$ is computed after the prediction; it is not a station
|
||||
on the forward path.
|
||||
|
||||
The scale $1/\sqrt{d}$ is introduced so that the variance of the
|
||||
inner product does not grow with $d$.
|
||||
@@ -0,0 +1,47 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: pipeline-delegate
|
||||
title: A One-Step Predictor
|
||||
authors: ["Fixture"]
|
||||
notes_language: zh
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
sections_in: ["1"]
|
||||
questions:
|
||||
- {id: Q1, text: "前向主路和损失如何分开?", source: "excerpt"}
|
||||
claims:
|
||||
- id: C1
|
||||
text: "一次前向是 x → f_θ → ŷ;损失不在主路上"
|
||||
kind: contribution
|
||||
status: core
|
||||
supports: [Q1]
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols:
|
||||
- {name: x, latex: "x", meaning: "输入", kind: value}
|
||||
- {name: yhat, latex: "\\hat y", meaning: "预测", kind: value}
|
||||
- {name: L, latex: "L", meaning: "损失", kind: scalar}
|
||||
derivations:
|
||||
- id: DER1
|
||||
claim: C1
|
||||
title: "前向定义"
|
||||
expand: true
|
||||
figure: null
|
||||
steps:
|
||||
- {id: S1, from: "x", to: "f_\\theta(x)=\\hat y", rule: definition}
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C1
|
||||
title: "一次前向与侧路损失"
|
||||
grammar: pipeline
|
||||
toolkit: superfig
|
||||
signals: [pipeline, what-eats-what]
|
||||
request: figures/F1/F1.request.md
|
||||
include: figures/F1/build/F1.pdf
|
||||
status: included
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1 @@
|
||||
一次前向 $x\to f_\theta\to\hat y$。损失挂在预测下方,不是主路车站。
|
||||
@@ -0,0 +1,27 @@
|
||||
# Figure request F1
|
||||
toolkit: superfig
|
||||
language: cjk
|
||||
claim: 一次前向是 x → f_θ → ŷ;损失不在主路上。
|
||||
grammar: pipeline
|
||||
work_rel_dir: figures/F1
|
||||
|
||||
## Roles
|
||||
- {role: input, color: sfTeal}
|
||||
- {role: model, color: sfOrange}
|
||||
- {role: loss, color: sfCoral}
|
||||
- {role: output, color: sfViolet}
|
||||
|
||||
## Flow (cursor). \sfconn 没有 endpoints。
|
||||
stage: {name: SA, text: "推理流程:一次前向"}
|
||||
row: {name: R1, height: 16mm}
|
||||
in_row:
|
||||
- {macro: sfnode, name: x, role: input, label: "输入 $x$", w: 16mm, h: 12mm}
|
||||
- {macro: sfconn, name: e1, label: 预处理}
|
||||
- {macro: sfnode, name: f, role: model, label: "模型 $f_\\theta$", w: 18mm, h: 12mm}
|
||||
- {macro: sfconn, name: e2, label: logits}
|
||||
- {macro: sfnode, name: y, role: output, label: "预测 $\\hat y$", w: 16mm, h: 12mm}
|
||||
|
||||
## Fixed topology
|
||||
- {macro: sfnode, name: s, role: loss, label: "损失 $L$", w: 14mm, h: 12mm,
|
||||
at: "($(y.south)+(0,-22mm)$)"}
|
||||
- {macro: sfarrowlabel, from: "y.south", to: "s.north", label: "$L(\\hat y, y)$"}
|
||||
@@ -0,0 +1,47 @@
|
||||
% superfig golden example 1 -- one horizontal paper-figure pipeline.
|
||||
% Main path is input -> model -> prediction. Loss is a side object, not a
|
||||
% station on the forward path.
|
||||
% ../scripts/build.sh pipeline.tex
|
||||
\documentclass[border=10pt]{standalone}
|
||||
\usepackage[cjk]{superfig}
|
||||
|
||||
\sfsetrole{input}{sfTeal}
|
||||
\sfsetrole{model}{sfOrange}
|
||||
\sfsetrole{loss}{sfCoral}
|
||||
\sfsetrole{output}{sfViolet}
|
||||
|
||||
\begin{document}
|
||||
\begin{tikzpicture}
|
||||
|
||||
\sfstage{SA}{推理流程:一次前向}
|
||||
\sfrow{R1}{16mm}
|
||||
\sfnode[role=input]{x}{输入 $x$}{16mm}{12mm}
|
||||
\sfconn{e1}{预处理}
|
||||
\sfnode[role=model]{f}{模型 $f_\theta$}{18mm}{12mm}
|
||||
\sfconn{e2}{logits}
|
||||
\sfnode[role=output]{y}{预测 $\hat y$}{16mm}{12mm}
|
||||
\sfrowend
|
||||
|
||||
% Loss compares the prediction with the target; it is not on the main path.
|
||||
% Hang it below ŷ with enough shaft that no caption sits on the arrow.
|
||||
\sfnode[role=loss, at={($(y.south)+(0,-22mm)$)}]{s}{损失 $L$}{14mm}{12mm}
|
||||
\sfarrowlabel{y.south}{s.north}{$L(\hat y,y)$}
|
||||
|
||||
\sflane{R1}
|
||||
\sfcaption{x}{$x$}{原始输入}
|
||||
\sfcaption{f}{$f_\theta$}{可学习参数}
|
||||
\sfnolane
|
||||
\sfcaption{s}{$L$}{与真值比较}
|
||||
|
||||
\sfbbox{all}
|
||||
\sftopformula{F}{%
|
||||
$x \;\xrightarrow{\;f_\theta\;}\; \hat y,\qquad
|
||||
\min_\theta\; L\bigl(f_\theta(x),\,y\bigr)$}
|
||||
\sfmeaningbox{mb}{96mm}{all}
|
||||
{一次从输入到预测的前向;损失在预测之后单独计算}
|
||||
{数据、模型参数、预测、损失}
|
||||
{预处理后送入模型;模型产生 logits 得到预测;损失比较预测与真值,梯度再回到参数}
|
||||
\sfsignature{推理流程示意}{mb}
|
||||
|
||||
\end{tikzpicture}
|
||||
\end{document}
|
||||
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 313 KiB |
@@ -0,0 +1,14 @@
|
||||
\documentclass[a4paper]{article}
|
||||
\input{notes-macros}
|
||||
\renewcommand{\notetitle}{一次前向与侧路损失}
|
||||
\renewcommand{\notepaper}{A One-Step Predictor}
|
||||
\begin{document}
|
||||
\tableofcontents
|
||||
\newpage
|
||||
\input{sections/sec-01.tex}
|
||||
\input{sections/sec-02.tex}
|
||||
\input{sections/sec-03.tex}
|
||||
\appendix
|
||||
\section{符号表}
|
||||
\input{sections/symbols.tex}
|
||||
\end{document}
|
||||
@@ -0,0 +1,2 @@
|
||||
\section{这篇论文在问什么}
|
||||
预测怎么从输入算出来,损失该不该站在前向主路上?
|
||||
@@ -0,0 +1,8 @@
|
||||
\section{主张与贡献}
|
||||
\splabel{C1}
|
||||
一次前向是 $x\to f_\theta\to\hat y$。损失在预测之后单独比较。
|
||||
|
||||
\spfig{F1}{一次前向与侧路损失。}{重绘自 fixture Figure~1;toolkit: \texttt{superfig};ledger id: F1。}
|
||||
|
||||
\subsection{本章小结}
|
||||
损失不是前向的一站。
|
||||
@@ -0,0 +1,2 @@
|
||||
\section{总结与延伸}
|
||||
这张图走 superfig,因为要画的是谁吃谁,不是轴长。
|
||||
@@ -0,0 +1,14 @@
|
||||
# Outline: A One-Step Predictor
|
||||
|
||||
## Lecture map
|
||||
|
||||
| file | lecture_title | paper_sections | ledger_ids |
|
||||
|---|---|---|---|
|
||||
| sec-01.tex | 这篇论文在问什么 | 1 | Q1 |
|
||||
| sec-02.tex | 主张与贡献 | 1 | C1, F1 |
|
||||
| sec-03.tex | 总结与延伸 | 1 | C1 |
|
||||
| sec-app-a.tex | 符号表 | — | |
|
||||
|
||||
## Locked
|
||||
- 首节标题必须是「这篇论文在问什么」
|
||||
- 末节(appendix 前)必须是「总结与延伸」
|
||||
@@ -0,0 +1,47 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: tensor-delegate
|
||||
title: Head scores
|
||||
authors: ["Fixture"]
|
||||
notes_language: zh
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions:
|
||||
- {id: Q1, text: "每头打分沿哪一维收缩?", source: "excerpt"}
|
||||
claims:
|
||||
- id: C1
|
||||
text: "每头打分沿 d_h 收缩;K^T 必须物理换面"
|
||||
kind: theoretical
|
||||
status: core
|
||||
supports: [Q1]
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols:
|
||||
- {name: Q, latex: "Q", meaning: "query", kind: value}
|
||||
- {name: K, latex: "K", meaning: "key", kind: value}
|
||||
- {name: S, latex: "S", meaning: "score", kind: score}
|
||||
- {name: dh, latex: "d_h", meaning: "头维", kind: "shape parameter"}
|
||||
derivations:
|
||||
- id: DER1
|
||||
claim: C1
|
||||
title: "打分"
|
||||
expand: true
|
||||
figure: null
|
||||
steps:
|
||||
- {id: S1, from: "Q K", to: "Q K^{\\top}", rule: rearrange}
|
||||
figures:
|
||||
- id: F2
|
||||
claim: C1
|
||||
title: "每头打分"
|
||||
grammar: tensor-face
|
||||
toolkit: supertensor
|
||||
signals: [axis, shape, transpose, contraction, face]
|
||||
request: figures/F2/F2.request.md
|
||||
include: figures/F2/build/F2.pdf
|
||||
status: included
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1 @@
|
||||
$S^{(i)}=Q^{(i)}K^{(i)\top}$。收缩维 $d_h$ 在两个操作数上同一边长;$K^\top$ 换面。
|
||||
@@ -0,0 +1,25 @@
|
||||
# Figure request F2
|
||||
toolkit: supertensor
|
||||
language: cjk
|
||||
claim: 每头打分沿 d_h 收缩;K^T 必须物理换面。
|
||||
grammar: tensor-face
|
||||
work_rel_dir: figures/F2
|
||||
|
||||
## Roles
|
||||
- {role: q, color: stTeal}
|
||||
- {role: k, color: stOrange}
|
||||
- {role: s, color: stCoral}
|
||||
|
||||
## Geometry
|
||||
- {axis: T, cells: 6}
|
||||
- {axis: dh, cells: 3}
|
||||
|
||||
## Flow
|
||||
stage: {name: SA, text: "每头打分:沿 $d_h$ 收缩"}
|
||||
row: {name: rowA, height: T}
|
||||
in_row:
|
||||
- {macro: ststack, name: Q, role: q, coord: "", rows: T, cols: dh, sheets: 3, bracket: true}
|
||||
- {macro: stglyph, name: mA, coord: "", glyph: "$\\times$"}
|
||||
- {macro: ststack, name: KT, role: k, coord: "", rows: dh, cols: T, sheets: 3, bracket: true}
|
||||
- {macro: stglyph, name: eA, coord: "", glyph: "$=$"}
|
||||
- {macro: ststack, name: S, role: s, coord: "", rows: T, cols: T, sheets: 3}
|
||||
@@ -0,0 +1,31 @@
|
||||
\documentclass[border=10pt]{standalone}
|
||||
\usepackage[cjk]{supertensor}
|
||||
|
||||
\stsetrole{q}{stTeal}
|
||||
\stsetrole{k}{stOrange}
|
||||
\stsetrole{s}{stCoral}
|
||||
|
||||
\stdim{T}{6}
|
||||
\stdim{dh}{3}
|
||||
|
||||
\begin{document}
|
||||
\begin{tikzpicture}
|
||||
\ststage{SA}{每头打分:沿 $d_h$ 收缩}
|
||||
\strow{rowA}{T}
|
||||
\ststack[role=q, bracket=true]{Q}{}{T}{dh}{3}
|
||||
\stglyph{mA}{$\times$}
|
||||
\ststack[role=k, bracket=true]{KT}{}{dh}{T}{3}
|
||||
\stglyph{eA}{$=$}
|
||||
\ststack[role=s]{S}{}{T}{T}{3}
|
||||
\strowend
|
||||
\stcaption{Q}{$\mathbf Q^{(i)}$}{$h\times T\times d_h$}
|
||||
\stcaption{KT}{$\mathbf K^{(i)\top}$}{$h\times d_h\times T$}
|
||||
\stcaption{S}{$\mathbf S^{(i)}$}{$h\times T\times T$}
|
||||
\sttopformula{F}{$S^{(i)}=Q^{(i)}K^{(i)\top}$}
|
||||
\stbbox{all}
|
||||
\stmeaningbox{mb}{120mm}{all}
|
||||
{$T$ 时间;$d_h$ 头维;$h$ 头数画成 stack 深度}
|
||||
{$Q/K$ 是 value;$S$ 是 score,不是 mask}
|
||||
{沿 $d_h$ 收缩;$K^\top$ 换面,收缩边等长}
|
||||
\end{tikzpicture}
|
||||
\end{document}
|
||||
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 213 KiB |
@@ -0,0 +1,14 @@
|
||||
\documentclass[a4paper]{article}
|
||||
\input{notes-macros}
|
||||
\renewcommand{\notetitle}{每头打分沿 $d_h$ 收缩}
|
||||
\renewcommand{\notepaper}{Head scores}
|
||||
\begin{document}
|
||||
\tableofcontents
|
||||
\newpage
|
||||
\input{sections/sec-01.tex}
|
||||
\input{sections/sec-02.tex}
|
||||
\input{sections/sec-03.tex}
|
||||
\appendix
|
||||
\section{符号表}
|
||||
\input{sections/symbols.tex}
|
||||
\end{document}
|
||||
@@ -0,0 +1,2 @@
|
||||
\section{这篇论文在问什么}
|
||||
每头的 $Q$ 和 $K$ 沿哪一条边收缩,$K^\top$ 要不要真的换面?
|
||||
@@ -0,0 +1,20 @@
|
||||
\section{主张与贡献}
|
||||
\splabel{C1}
|
||||
打分是 $(T\times d_h)(d_h\times T)\to(T\times T)$。$K^\top$ 必须物理换面,收缩边等长。
|
||||
|
||||
先用中文说完,再写式子:
|
||||
|
||||
\[
|
||||
S^{(i)}=Q^{(i)}K^{(i)\top}.
|
||||
\]
|
||||
|
||||
\begin{itemize}
|
||||
\item $Q^{(i)}$ — 第 $i$ 头 query,$T\times d_h$
|
||||
\item $K^{(i)\top}$ — 转置后的 key,$d_h\times T$
|
||||
\item $S^{(i)}$ — 分数,不是 mask
|
||||
\end{itemize}
|
||||
|
||||
\spfig{F2}{每头打分沿 $d_h$ 收缩。}{重绘自 fixture;toolkit: \texttt{supertensor};ledger id: F2。}
|
||||
|
||||
\subsection{本章小结}
|
||||
轴长是这张图的主张,所以走 supertensor,不走 superfig。
|
||||
@@ -0,0 +1,2 @@
|
||||
\section{总结与延伸}
|
||||
形状对齐的公式图只交给 supertensor。
|
||||
@@ -0,0 +1,14 @@
|
||||
# Outline: Head scores
|
||||
|
||||
## Lecture map
|
||||
|
||||
| file | lecture_title | paper_sections | ledger_ids |
|
||||
|---|---|---|---|
|
||||
| sec-01.tex | 这篇论文在问什么 | 1 | Q1 |
|
||||
| sec-02.tex | 主张与贡献 | 1 | C1, F2 |
|
||||
| sec-03.tex | 总结与延伸 | 1 | C1 |
|
||||
| sec-app-a.tex | 符号表 | — | |
|
||||
|
||||
## Locked
|
||||
- 首节标题必须是「这篇论文在问什么」
|
||||
- 末节(appendix 前)必须是「总结与延伸」
|
||||
@@ -0,0 +1,14 @@
|
||||
# Agents
|
||||
|
||||
Must split when pages > 12 **or** top sections > 4 **or** redraws ≥ 2 **or** the user asks to spawn. Otherwise one agent (including 9–12 pages with ≤4 tops and ≤1 redraw).
|
||||
|
||||
Writer jobs are lecture rows in `outline.md`, not `coverage.sections_in`. Outline writes `ledger.yaml`, `outline.md`, `notes.tex` inputs, and `F*.request.md`. Writers only touch their `sec-XX.tex`. Figure agents only see the request.
|
||||
|
||||
```
|
||||
$superpaper <源> 请 spawn 多 sub agents,隔离上下文:
|
||||
- 1 个 outline agent:ledger.yaml + outline.md
|
||||
- N 个 writer agents:sections/sec-XX.tex
|
||||
- 每个计划重绘图 1 个 figure agent:F*.request.md + sibling skill
|
||||
- 1 个 consistency agent:符号、术语、claim 覆盖
|
||||
完成后用 scripts/lint.py 与 build.sh 收口。
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
# Antipatterns
|
||||
|
||||
- Drawing a rewrite as `\sfnode` boxes (use `align`).
|
||||
- Sending a tensor-shape claim to superfig, or an architecture to supertensor.
|
||||
- Loading a figure `.sty` in the notes so sibling warnings leak into the article log.
|
||||
- `\input` of a standalone TikZ source instead of `\spfig`.
|
||||
- Reusing a `dropped` / retired id for a new object.
|
||||
- A decorative group or arrow that is not in the ledger.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Notes macros
|
||||
|
||||
Defined in `assets/notes-macros.tex` (loaded by `assets/notes-template.tex`).
|
||||
|
||||
| macro | args |
|
||||
|---|---|
|
||||
| `\splabel{C1}` | hypertarget + label |
|
||||
| `\spref{C1}` | clickable id |
|
||||
| `\spsource{§3.2}` | source footnote |
|
||||
| `\spfig[w]{F1}{caption}{provenance}` | `figures/F1/build/F1.pdf` |
|
||||
| `\spscreenshot[w]{F2}{caption}{provenance}` | `figures/F2/orig.png` |
|
||||
| `quotebox` | short quotation |
|
||||
|
||||
Scripts: `ingest.sh`, `lint.py`, `render_ledger.py`, `build.sh`, `screenshot.sh` all take `--work`.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Delivery checklist
|
||||
|
||||
- Ledger written first; every core claim has `\splabel`.
|
||||
- Formula three-beat present wherever display math appears.
|
||||
- Each figure toolkit matches `scripts/router.py`; mixed-class rows were split.
|
||||
- Notes do not `\usepackage{superfig|supertensor|superderive}`.
|
||||
- Vector figures are PDF includes; screenshots are `orig.png` with a source footnote.
|
||||
- `scripts/lint.py --work` and `scripts/build.sh --work` are green.
|
||||
- Overfull in the notes log is allowed; missing glyphs and undefined refs are not.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Fallback
|
||||
|
||||
`preflight.sh`: 0 full, 1 degraded, 2 no XeLaTeX/`article.cls`.
|
||||
|
||||
- Exit 2: deliver `ledger.yaml` + Markdown; say there is no PDF.
|
||||
- No `pdftotext` / `pdftoppm`: only `--tex` / `--excerpt`.
|
||||
- No `jsonschema`: create `.venv` (`python3 -m venv .venv && .venv/bin/pip install -r requirements.txt`).
|
||||
- Sibling `build.sh` fails: drop that figure to `align` or screenshot, keep the notes compiling.
|
||||
- No `magick`: `screenshot.sh` must fail if `crop_bbox` is set; do not silently ship the full page.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Figure request
|
||||
|
||||
Sibling figure agents see only `notes/figures/F*/F*.request.md` plus that sibling `SKILL.md`. They do not read `ledger.yaml`.
|
||||
|
||||
- superfig: `\sfnode` keys `role, level, gap, bracket, at=`. `\sfconn` has no endpoints. `at` must be TikZ calc with `($…$)`.
|
||||
- supertensor: every `ststack` / `stface` / `stglyph` has `coord: ""`.
|
||||
- `allow-raw-tikz` must also appear as `% superfig-lint: allow-raw-tikz` in the `.tex`.
|
||||
- Build: `superfig/scripts/build.sh F1.tex F1/build` (second arg is the outdir).
|
||||
- Screenshots: `scripts/screenshot.sh --work <work> --id F2` (`pdftoppm -r 200`, then optional `crop_bbox`).
|
||||
|
||||
Worked files: `examples/pipeline-delegate/notes/figures/F1/F1.request.md`, `examples/tensor-delegate/notes/figures/F2/F2.request.md`.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Input
|
||||
|
||||
`scripts/ingest.sh --work <dir> (--arxiv ID|--pdf FILE|--tex FILE|--excerpt FILE)`
|
||||
|
||||
| kind | ingest |
|
||||
|---|---|
|
||||
| `tex` | copy into `source/tex/`; do not compile |
|
||||
| `arxiv` | id regex `^(ar[Xx]iv:)?(\d{4}\.\d{4,5}(v\d+)?|[a-z-]+/\d{7})$`; PDF then optional e-print tar (read only) |
|
||||
| `pdf` | copy `source/paper.pdf`; `pdftotext`; pages `pg-%03d.png` at 120 dpi |
|
||||
| `excerpt` / `markdown` | `source/excerpt.md`; `coverage.mode=excerpt` |
|
||||
|
||||
Do not OCR as the main path. Architecture figures: redraw with superfig. Numeric plots: screenshot or matplotlib. See `scripts/screenshot.sh` for 200 dpi page crops.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Ledger
|
||||
|
||||
SSOT is `ledger.yaml`. Schema: `assets/ledger.schema.yaml`. Empty template: `assets/ledger.example.yaml`.
|
||||
|
||||
Write the ledger before any `sections/*.tex`. Ids are `C1`, `Q1`, `D1`, `A1`, `L1`, `E1`, `DER1`, `F1`, `SA1` — no `F1a`. Retired ids go in `retired_ids`.
|
||||
|
||||
`symbols[].kind` aliases (`activation` → `value`, `shape-parameter` → `shape parameter`, …) are normalized in `scripts/lint.py` **before** `SP001`. Canonical list is the schema enum.
|
||||
|
||||
v1 derivations use `figure: null` and `align` in the notes. Do not invent a figure for a four-step rewrite.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Pedagogy
|
||||
|
||||
Reuse `youtube-render-pdf` teaching order: motive → idea → mechanism → evidence → takeaway.
|
||||
|
||||
Paper-side changes:
|
||||
|
||||
- Cite `§` / `Eq.(n)` / Figure / Table / page, not timestamps.
|
||||
- Front page is a bibliography card, not a PDF cover screenshot.
|
||||
- Math is `\[` or `align`, never `$$`.
|
||||
- Formula three-beat: Chinese motive, display math, flat symbol list.
|
||||
- `quotebox` for a short quotation with a source; no long PDF paste.
|
||||
- End major sections with `\subsection{本章小结}`; end the notes with `\section{总结与延伸}`.
|
||||
- Figures stay outside boxes.
|
||||
|
||||
Locked first/last titles: 「这篇论文在问什么」…「总结与延伸」, then appendix 符号表 / 推导链一览 / 图表清单.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Router
|
||||
|
||||
Authority: `scripts/router.py`. `suggest()` returns a **class**, not a toolkit.
|
||||
|
||||
| class | signals (any hit) | accepted toolkits |
|
||||
|---|---|---|
|
||||
| `numeric` | loss-curve, bar, scatter, histogram, numeric-plot | screenshot, matplotlib |
|
||||
| `raster` | table, photo, apparatus, ui | screenshot |
|
||||
| `tensor` | axis, shape, transpose, broadcast, gather, shard, contraction, face | supertensor |
|
||||
| `fig` | architecture, pipeline, data-flow, state, time, dependency, what-eats-what, argument-map | superfig |
|
||||
| `derive` | rewrite-figure, cancel-visual, subst-visual | v1: align |
|
||||
| `none` | otherwise | none |
|
||||
|
||||
Mixed classes on one row → `SP011`. Split with the next integer ids. Default: do not draw.
|
||||
@@ -0,0 +1,2 @@
|
||||
jsonschema>=4.18
|
||||
PyYAML>=6.0
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
# Lint, project the ledger, compile notes. Overfull is logged, not fatal.
|
||||
# ./scripts/build.sh --work <dir>
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
WORK=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--work|--out) WORK="$2"; shift 2 ;;
|
||||
*) echo "usage: build.sh --work DIR" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$WORK" ]] || { echo "usage: build.sh --work DIR" >&2; exit 2; }
|
||||
WORK="$(cd "$WORK" && pwd)"
|
||||
NOTES="$WORK/notes"
|
||||
[[ -f "$WORK/ledger.yaml" ]] || { echo "no ledger.yaml in $WORK" >&2; exit 2; }
|
||||
[[ -f "$NOTES/notes.tex" ]] || { echo "no notes/notes.tex in $WORK" >&2; exit 2; }
|
||||
|
||||
echo "==> lint"
|
||||
"$ROOT/scripts/python" "$ROOT/scripts/lint.py" --work "$WORK"
|
||||
|
||||
echo "==> render ledger"
|
||||
"$ROOT/scripts/python" "$ROOT/scripts/render_ledger.py" --work "$WORK"
|
||||
|
||||
mkdir -p "$WORK/out"
|
||||
echo "==> xelatex"
|
||||
(
|
||||
cd "$NOTES"
|
||||
export TEXINPUTS="$ROOT/assets:$NOTES:"
|
||||
xelatex -halt-on-error -interaction=nonstopmode notes.tex >/dev/null
|
||||
xelatex -halt-on-error -interaction=nonstopmode notes.tex >/dev/null
|
||||
)
|
||||
|
||||
LOG="$NOTES/notes.log"
|
||||
status=0
|
||||
if grep -q "Missing character" "$LOG"; then
|
||||
echo "!! missing glyphs:" >&2
|
||||
grep -m5 "Missing character" "$LOG" >&2
|
||||
status=1
|
||||
fi
|
||||
if grep -qE "LaTeX Warning: (Reference|Citation|There were undefined references|Label\(s\) may have changed)" "$LOG"; then
|
||||
if grep -q "undefined" "$LOG"; then
|
||||
echo "!! undefined references:" >&2
|
||||
grep -m5 "undefined" "$LOG" >&2 || true
|
||||
status=1
|
||||
fi
|
||||
fi
|
||||
if grep -q "multiply-defined" "$LOG"; then
|
||||
echo "!! multiply-defined labels:" >&2
|
||||
grep -m5 "multiply-defined" "$LOG" >&2
|
||||
status=1
|
||||
fi
|
||||
if grep -qE "^(Overfull|Underfull) \\\\[hv]box" "$LOG"; then
|
||||
echo "==> overfull/underfull (recorded, not fatal)"
|
||||
grep -m5 -E "^(Overfull|Underfull) \\\\[hv]box" "$LOG" || true
|
||||
fi
|
||||
|
||||
cp -f "$NOTES/notes.pdf" "$WORK/out/notes.pdf"
|
||||
echo "==> $WORK/out/notes.pdf"
|
||||
exit $status
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
# Ingest a paper source into a --work tree. Never compiles e-print TeX.
|
||||
set -euo pipefail
|
||||
|
||||
WORK=""
|
||||
KIND=""
|
||||
SRC=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--work|--out) WORK="$2"; shift 2 ;;
|
||||
--arxiv) KIND=arxiv; SRC="$2"; shift 2 ;;
|
||||
--pdf) KIND=pdf; SRC="$2"; shift 2 ;;
|
||||
--tex) KIND=tex; SRC="$2"; shift 2 ;;
|
||||
--excerpt|--markdown) KIND=excerpt; SRC="$2"; shift 2 ;;
|
||||
*) echo "usage: ingest.sh --work DIR (--arxiv ID|--pdf FILE|--tex FILE|--excerpt FILE)" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$WORK" && -n "$KIND" ]] || { echo "need --work and a source" >&2; exit 2; }
|
||||
|
||||
ARXIV_RE='^(ar[Xx]iv:)?([0-9]{4}\.[0-9]{4,5}(v[0-9]+)?|[a-z-]+/[0-9]{7})$'
|
||||
|
||||
mkdir -p "$WORK/source/pages" "$WORK/notes/sections" "$WORK/out"
|
||||
meta="$WORK/source/meta.yaml"
|
||||
|
||||
sha_of() { sha256sum "$1" | awk '{print $1}'; }
|
||||
|
||||
write_meta() {
|
||||
local extra="$1"
|
||||
cat > "$meta" <<EOF
|
||||
kind: $KIND
|
||||
$extra
|
||||
EOF
|
||||
}
|
||||
|
||||
case "$KIND" in
|
||||
excerpt)
|
||||
cp -- "$SRC" "$WORK/source/excerpt.md"
|
||||
write_meta "excerpt: source/excerpt.md"
|
||||
;;
|
||||
tex)
|
||||
mkdir -p "$WORK/source/tex"
|
||||
if [[ -d "$SRC" ]]; then
|
||||
cp -R -- "$SRC/." "$WORK/source/tex/"
|
||||
else
|
||||
cp -- "$SRC" "$WORK/source/tex/"
|
||||
fi
|
||||
write_meta "tex: source/tex"
|
||||
;;
|
||||
pdf)
|
||||
cp -- "$SRC" "$WORK/source/paper.pdf"
|
||||
pdftotext -layout "$WORK/source/paper.pdf" "$WORK/source/paper.txt" || true
|
||||
pdftoppm -png -r 120 "$WORK/source/paper.pdf" "$WORK/source/pages/pg"
|
||||
python3 - <<'PY' "$WORK/source/pages"
|
||||
from pathlib import Path
|
||||
import sys, re
|
||||
d = Path(sys.argv[1])
|
||||
files = sorted(d.glob("pg*.png"))
|
||||
for i, p in enumerate(files, 1):
|
||||
dest = d / f"pg-{i:03d}.png"
|
||||
if p.resolve() != dest.resolve():
|
||||
p.rename(dest)
|
||||
PY
|
||||
pages=$(find "$WORK/source/pages" -name 'pg-*.png' | wc -l)
|
||||
write_meta "local_pdf: source/paper.pdf
|
||||
sha256: $(sha_of "$WORK/source/paper.pdf")
|
||||
pages: $pages"
|
||||
;;
|
||||
arxiv)
|
||||
id="$SRC"
|
||||
[[ "$id" =~ $ARXIV_RE ]] || { echo "bad arxiv id: $id" >&2; exit 2; }
|
||||
id="${id#arxiv:}"; id="${id#arXiv:}"
|
||||
mkdir -p "$WORK/source"
|
||||
pdf="$WORK/source/paper.pdf"
|
||||
if [[ ! -f "$pdf" ]]; then
|
||||
curl -fsSL "https://arxiv.org/pdf/${id}.pdf" -o "$pdf" \
|
||||
|| curl -fsSL "https://export.arxiv.org/pdf/${id}.pdf" -o "$pdf"
|
||||
fi
|
||||
pdftotext -layout "$pdf" "$WORK/source/paper.txt" || true
|
||||
pdftoppm -png -r 120 "$pdf" "$WORK/source/pages/pg"
|
||||
python3 - <<'PY' "$WORK/source/pages"
|
||||
from pathlib import Path
|
||||
import sys
|
||||
d = Path(sys.argv[1])
|
||||
files = sorted(d.glob("pg*.png"))
|
||||
for i, p in enumerate(files, 1):
|
||||
dest = d / f"pg-{i:03d}.png"
|
||||
if p.resolve() != dest.resolve():
|
||||
p.rename(dest)
|
||||
PY
|
||||
# best-effort e-print; never compile
|
||||
if [[ ! -d "$WORK/source/eprint" ]]; then
|
||||
tmp=$(mktemp)
|
||||
if curl -fsSL "https://arxiv.org/e-print/${id}" -o "$tmp"; then
|
||||
mkdir -p "$WORK/source/eprint"
|
||||
tar -xf "$tmp" -C "$WORK/source/eprint" --max-size=50M 2>/dev/null \
|
||||
|| tar -xf "$tmp" -C "$WORK/source/eprint" || true
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
fi
|
||||
pages=$(find "$WORK/source/pages" -name 'pg-*.png' | wc -l)
|
||||
write_meta "arxiv: $id
|
||||
local_pdf: source/paper.pdf
|
||||
sha256: $(sha_of "$pdf")
|
||||
pages: $pages"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "ingested $KIND -> $WORK"
|
||||
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Ledger and notes lint for superpaper. Codes live in DESIGN.md §10."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from jsonschema import Draft7Validator
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
from router import SIBLING, check_row, phase_from_env # noqa: E402
|
||||
|
||||
SCHEMA_PATH = ROOT / "assets" / "ledger.schema.yaml"
|
||||
KNOWN_TOP = {
|
||||
"schema", "retired_ids", "paper", "coverage", "questions", "claims",
|
||||
"definitions", "assumptions", "lemmas", "symbols", "derivations",
|
||||
"figures", "evidence", "terms", "source_assets",
|
||||
}
|
||||
ID_GROUPS = (
|
||||
"questions", "claims", "definitions", "assumptions", "lemmas",
|
||||
"derivations", "figures", "evidence", "source_assets",
|
||||
)
|
||||
KIND_ALIASES = {
|
||||
"activation": "value",
|
||||
"value / activation": "value",
|
||||
"logit": "score",
|
||||
"score / logit": "score",
|
||||
"coordinate": "index",
|
||||
"index / coordinate": "index",
|
||||
"order": "rank",
|
||||
"rank / order": "rank",
|
||||
"support": "mask",
|
||||
"mask / support": "mask",
|
||||
"shape-parameter": "shape parameter",
|
||||
"shape_parameter": "shape parameter",
|
||||
}
|
||||
STY_RE = re.compile(r"\\usepackage(?:\[[^\]]*\])?\{(superfig|supertensor|superderive)\}")
|
||||
LABEL_RE = re.compile(r"\\splabel\{(C[1-9][0-9]*)\}")
|
||||
|
||||
|
||||
def emit(path: Path, messages: list[str]) -> None:
|
||||
print(f"!! {path}", file=sys.stderr)
|
||||
for msg in messages:
|
||||
print(f" {msg}", file=sys.stderr)
|
||||
|
||||
|
||||
def normalize_kinds(data: dict) -> dict:
|
||||
out = deepcopy(data)
|
||||
for sym in out.get("symbols") or []:
|
||||
if isinstance(sym, dict) and "kind" in sym:
|
||||
kind = sym["kind"]
|
||||
if kind in KIND_ALIASES:
|
||||
sym["kind"] = KIND_ALIASES[kind]
|
||||
return out
|
||||
|
||||
|
||||
def collect_ids(data: dict) -> list[str]:
|
||||
ids: list[str] = []
|
||||
for key in ID_GROUPS:
|
||||
for row in data.get(key) or []:
|
||||
if isinstance(row, dict) and "id" in row:
|
||||
ids.append(str(row["id"]))
|
||||
return ids
|
||||
|
||||
|
||||
def notes_text(notes_paths: list[Path]) -> str:
|
||||
chunks: list[str] = []
|
||||
for path in notes_paths:
|
||||
if path.is_file():
|
||||
chunks.append(path.read_text(encoding="utf-8"))
|
||||
return "\n".join(chunks)
|
||||
|
||||
|
||||
def find_notes(work: Path | None, notes: Path | None) -> list[Path]:
|
||||
found: list[Path] = []
|
||||
if notes is not None:
|
||||
found.append(notes)
|
||||
if work is not None:
|
||||
nd = work / "notes"
|
||||
if (nd / "notes.tex").is_file():
|
||||
found.append(nd / "notes.tex")
|
||||
found.extend(sorted(nd.glob("sections/*.tex")))
|
||||
# unique, keep order
|
||||
seen: set[Path] = set()
|
||||
uniq: list[Path] = []
|
||||
for p in found:
|
||||
rp = p.resolve()
|
||||
if rp not in seen:
|
||||
seen.add(rp)
|
||||
uniq.append(p)
|
||||
return uniq
|
||||
|
||||
|
||||
def lint(
|
||||
ledger_path: Path,
|
||||
*,
|
||||
work: Path | None,
|
||||
extra_notes: Path | None,
|
||||
disk: bool,
|
||||
notes_scan: bool,
|
||||
) -> tuple[list[str], list[str]]:
|
||||
errors: list[str] = []
|
||||
warnings: list[str] = []
|
||||
raw = yaml.safe_load(ledger_path.read_text(encoding="utf-8"))
|
||||
if not isinstance(raw, dict):
|
||||
return ["SP001 ledger is not a mapping"], warnings
|
||||
|
||||
data = normalize_kinds(raw)
|
||||
schema = yaml.safe_load(SCHEMA_PATH.read_text(encoding="utf-8"))
|
||||
validator = Draft7Validator(schema)
|
||||
for err in validator.iter_errors(data):
|
||||
loc = ".".join(str(p) for p in err.absolute_path) or "$"
|
||||
errors.append(f"SP001 {loc}: {err.message}")
|
||||
if errors:
|
||||
return errors, warnings
|
||||
|
||||
for key in data:
|
||||
if key not in KNOWN_TOP:
|
||||
warnings.append(f"unknown top-level key {key!r}")
|
||||
|
||||
coverage = data.get("coverage") or {}
|
||||
if coverage.get("mode") == "full" and not coverage.get("sections_in"):
|
||||
warnings.append("coverage.mode=full and sections_in is empty")
|
||||
|
||||
ids = collect_ids(data)
|
||||
seen: set[str] = set()
|
||||
for i in ids:
|
||||
if i in seen:
|
||||
errors.append(f"SP002 duplicate id {i}")
|
||||
seen.add(i)
|
||||
|
||||
retired = set(data.get("retired_ids") or [])
|
||||
for row_key in ID_GROUPS:
|
||||
for row in data.get(row_key) or []:
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
rid = row.get("id")
|
||||
if rid in retired and row.get("status") != "dropped":
|
||||
errors.append(f"SP023 {rid} is in retired_ids")
|
||||
|
||||
claim_ids = {c["id"] for c in (data.get("claims") or []) if "id" in c}
|
||||
phase = phase_from_env()
|
||||
notes_paths = find_notes(work, extra_notes)
|
||||
body = notes_text(notes_paths) if notes_scan else ""
|
||||
|
||||
if notes_scan:
|
||||
for path in notes_paths:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if STY_RE.search(text):
|
||||
errors.append(f"SP003 {path.name} loads a figure package")
|
||||
|
||||
labels = set(LABEL_RE.findall(body)) if notes_scan else set()
|
||||
|
||||
for fig in data.get("figures") or []:
|
||||
fid = fig.get("id", "F?")
|
||||
signals = set(fig.get("signals") or [])
|
||||
toolkit = fig.get("toolkit")
|
||||
status = fig.get("status", "planned")
|
||||
include = fig.get("include", None)
|
||||
code = check_row(
|
||||
signals, toolkit, status=status, include=include,
|
||||
fig_id=fid, phase=phase,
|
||||
)
|
||||
if code:
|
||||
errors.append(f"{code} {fid}")
|
||||
if toolkit == "superderive" and phase < 2:
|
||||
errors.append(f"SP022 {fid} toolkit superderive is v2; use align")
|
||||
if toolkit in SIBLING and status != "dropped" and disk:
|
||||
req = fig.get("request")
|
||||
req_path = (work / "notes" / req) if (work and req) else None
|
||||
if not req or req_path is None or not req_path.is_file():
|
||||
errors.append(f"SP012 {fid} missing request file")
|
||||
if include and disk and work is not None:
|
||||
on_disk = work / "notes" / include
|
||||
if not on_disk.is_file():
|
||||
errors.append(f"SP021 {fid} include not on disk: {include}")
|
||||
claim = fig.get("claim")
|
||||
if claim and claim not in claim_ids:
|
||||
errors.append(f"SP024 {fid} claim {claim} is not a known C*")
|
||||
|
||||
for der in data.get("derivations") or []:
|
||||
claim = der.get("claim")
|
||||
if claim and claim not in claim_ids:
|
||||
errors.append(f"SP024 {der.get('id')} claim {claim} is not a known C*")
|
||||
|
||||
if notes_scan:
|
||||
for claim in data.get("claims") or []:
|
||||
if claim.get("status") == "core" and claim.get("id") not in labels:
|
||||
errors.append(f"SP020 core {claim['id']} has no \\splabel")
|
||||
for sym in data.get("symbols") or []:
|
||||
name = str(sym.get("name", ""))
|
||||
latex = str(sym.get("latex", ""))
|
||||
if name and name not in body and latex and latex not in body:
|
||||
warnings.append(f"symbol {name} never appears in notes")
|
||||
|
||||
return errors, warnings
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="lint a superpaper ledger / notes tree")
|
||||
parser.add_argument("--work", help="work tree root (ledger.yaml + notes/)")
|
||||
parser.add_argument("--out", dest="work_alias", help="alias of --work")
|
||||
parser.add_argument("--ledger", help="ledger.yaml (skips disk/notes codes unless --notes)")
|
||||
parser.add_argument("--notes", help="extra notes.tex for SP003/SP020")
|
||||
args = parser.parse_args()
|
||||
|
||||
work = Path(args.work or args.work_alias).resolve() if (args.work or args.work_alias) else None
|
||||
if work is not None:
|
||||
ledger = work / "ledger.yaml"
|
||||
disk = True
|
||||
notes_scan = True
|
||||
elif args.ledger:
|
||||
ledger = Path(args.ledger).resolve()
|
||||
disk = False
|
||||
notes_scan = args.notes is not None
|
||||
else:
|
||||
parser.error("need --work or --ledger")
|
||||
return 2
|
||||
|
||||
extra = Path(args.notes).resolve() if args.notes else None
|
||||
if not ledger.is_file():
|
||||
print(f"!! {ledger}", file=sys.stderr)
|
||||
print(" SP000 no such ledger", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
errors, warnings = lint(ledger, work=work, extra_notes=extra, disk=disk, notes_scan=notes_scan)
|
||||
if errors:
|
||||
emit(ledger, errors)
|
||||
return 1
|
||||
for warn in warnings:
|
||||
print(f"warning: {warn}", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# Notes toolchain preflight. 0 = full, 1 = degraded, 2 = no LaTeX engine.
|
||||
set -uo pipefail
|
||||
|
||||
QUIET=0
|
||||
[[ "${1:-}" == "--quiet" ]] && QUIET=1
|
||||
say() { [[ $QUIET -eq 1 ]] || echo -e "$*"; }
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ok=0; warn=0; fail=0
|
||||
check() {
|
||||
local name="$1"; shift
|
||||
if "$@" >/dev/null 2>&1; then say " ok $name"; ok=$((ok+1)); return 0
|
||||
else say " MISS $name"; return 1; fi
|
||||
}
|
||||
|
||||
say "superpaper preflight"
|
||||
say "--- engine ---"
|
||||
check "xelatex" command -v xelatex || fail=$((fail+1))
|
||||
check "article.cls" kpsewhich article.cls || fail=$((fail+1))
|
||||
|
||||
say "--- template / CJK ---"
|
||||
cjk=0
|
||||
for pkg in ctex.sty FandolSong-Regular.otf amsmath.sty amssymb.sty \
|
||||
tcolorbox.sty graphicx.sty hyperref.sty geometry.sty \
|
||||
listings.sty booktabs.sty subcaption.sty float.sty tikz.sty etoolbox.sty; do
|
||||
check "$pkg" kpsewhich "$pkg" || { warn=$((warn+1)); cjk=1; }
|
||||
done
|
||||
|
||||
say "--- python ---"
|
||||
PY="$ROOT/scripts/python"
|
||||
check "python3" command -v python3 || warn=$((warn+1))
|
||||
check "PyYAML" "$PY" -c "import yaml" || warn=$((warn+1))
|
||||
check "jsonschema" "$PY" -c "import jsonschema" || warn=$((warn+1))
|
||||
|
||||
say "--- ingest ---"
|
||||
check "pdftotext" command -v pdftotext || warn=$((warn+1))
|
||||
check "pdftoppm" command -v pdftoppm || warn=$((warn+1))
|
||||
|
||||
say "--- optional ---"
|
||||
check "magick" command -v magick || true
|
||||
check "pdftocairo" command -v pdftocairo || true
|
||||
|
||||
if [[ $fail -gt 0 ]]; then
|
||||
say ""
|
||||
say "RESULT: no LaTeX. Deliver ledger + Markdown; no PDF."
|
||||
exit 2
|
||||
fi
|
||||
if [[ $warn -gt 0 ]]; then
|
||||
say ""
|
||||
say "RESULT: degraded."
|
||||
[[ $cjk -eq 1 ]] && say " - missing CJK/template package -> English notes or later Missing character."
|
||||
exit 1
|
||||
fi
|
||||
say ""
|
||||
say "RESULT: full notes path (XeLaTeX + CJK + lint)."
|
||||
exit 0
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prefer the local venv so jsonschema is available without a system install.
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
if [[ -x "$ROOT/.venv/bin/python" ]]; then
|
||||
exec "$ROOT/.venv/bin/python" "$@"
|
||||
fi
|
||||
exec python3 "$@"
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Project ledger.yaml into notes/sections/symbols.tex and optional claims.md."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
def tex_escape(text: str) -> str:
|
||||
return (
|
||||
text.replace("\\", "\\textbackslash{}")
|
||||
.replace("&", "\\&")
|
||||
.replace("%", "\\%")
|
||||
.replace("#", "\\#")
|
||||
.replace("_", "\\_")
|
||||
)
|
||||
|
||||
|
||||
def render(work: Path) -> None:
|
||||
ledger = yaml.safe_load((work / "ledger.yaml").read_text(encoding="utf-8"))
|
||||
symbols = ledger.get("symbols") or []
|
||||
claims = ledger.get("claims") or []
|
||||
sec = work / "notes" / "sections"
|
||||
sec.mkdir(parents=True, exist_ok=True)
|
||||
lines = [
|
||||
"% generated by render_ledger.py — do not edit",
|
||||
"\\begin{itemize}",
|
||||
]
|
||||
if not symbols:
|
||||
lines.append("\\item (无符号)")
|
||||
for sym in symbols:
|
||||
name = tex_escape(str(sym.get("name", "")))
|
||||
meaning = tex_escape(str(sym.get("meaning", "")))
|
||||
kind = tex_escape(str(sym.get("kind", "")))
|
||||
latex = sym.get("latex") or name
|
||||
lines.append(f"\\item ${latex}$ — {meaning} ({kind};{name})")
|
||||
lines.append("\\end{itemize}")
|
||||
(sec / "symbols.tex").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
md = ["# Claims", ""]
|
||||
for c in claims:
|
||||
md.append(f"- `{c.get('id')}` ({c.get('status')}): {c.get('text')}")
|
||||
(work / "claims.md").write_text("\n".join(md) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--work", required=False)
|
||||
parser.add_argument("--out", dest="work")
|
||||
args = parser.parse_args()
|
||||
if not args.work:
|
||||
print("usage: render_ledger.py --work <dir>", file=sys.stderr)
|
||||
return 2
|
||||
work = Path(args.work).resolve()
|
||||
if not (work / "ledger.yaml").is_file():
|
||||
print(f"no ledger at {work / 'ledger.yaml'}", file=sys.stderr)
|
||||
return 2
|
||||
render(work)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Figure-class router. suggest() returns a class, never a toolkit."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
NUMERIC = frozenset({"loss-curve", "bar", "scatter", "histogram", "numeric-plot"})
|
||||
RASTER = frozenset({"table", "photo", "apparatus", "ui"})
|
||||
TENSOR = frozenset({
|
||||
"axis", "shape", "transpose", "broadcast", "gather",
|
||||
"shard", "contraction", "face",
|
||||
})
|
||||
DERIVE = frozenset({"rewrite-figure", "cancel-visual", "subst-visual"})
|
||||
FIG = frozenset({
|
||||
"architecture", "pipeline", "data-flow", "state", "time",
|
||||
"dependency", "what-eats-what", "argument-map",
|
||||
})
|
||||
|
||||
CLASS_SIGNALS = {
|
||||
"numeric": NUMERIC,
|
||||
"raster": RASTER,
|
||||
"tensor": TENSOR,
|
||||
"derive": DERIVE,
|
||||
"fig": FIG,
|
||||
}
|
||||
|
||||
VECTOR = frozenset({"superfig", "supertensor", "superderive", "matplotlib"})
|
||||
SIBLING = frozenset({"superfig", "supertensor", "superderive"})
|
||||
|
||||
|
||||
def classify(signals: set[str]) -> list[str]:
|
||||
s = set(signals)
|
||||
hit = [c for c, vocab in CLASS_SIGNALS.items() if s & vocab]
|
||||
return hit or ["none"]
|
||||
|
||||
|
||||
def suggest(signals: set[str], *, phase: int = 1) -> str:
|
||||
"""Return a class name, or 'SPLIT:a+b+...' in CLASS_SIGNALS order."""
|
||||
classes = classify(signals)
|
||||
if len(classes) > 1:
|
||||
return "SPLIT:" + "+".join(classes)
|
||||
return classes[0]
|
||||
|
||||
|
||||
def accepted(cls: str, *, phase: int = 1) -> frozenset[str]:
|
||||
if cls == "numeric":
|
||||
return frozenset({"screenshot", "matplotlib"})
|
||||
if cls == "raster":
|
||||
return frozenset({"screenshot"})
|
||||
if cls == "tensor":
|
||||
return frozenset({"supertensor"})
|
||||
if cls == "fig":
|
||||
return frozenset({"superfig"})
|
||||
if cls == "derive":
|
||||
return frozenset({"align", "superderive"} if phase >= 2 else {"align"})
|
||||
if cls == "none":
|
||||
return frozenset({"none"})
|
||||
raise KeyError(cls)
|
||||
|
||||
|
||||
def include_pdf(fig_id: str) -> str:
|
||||
return f"figures/{fig_id}/build/{fig_id}.pdf"
|
||||
|
||||
|
||||
def include_png(fig_id: str) -> str:
|
||||
return f"figures/{fig_id}/orig.png"
|
||||
|
||||
|
||||
def check_row(
|
||||
signals: set[str],
|
||||
toolkit: str,
|
||||
*,
|
||||
status: str = "planned",
|
||||
include: str | None = None,
|
||||
fig_id: str = "F1",
|
||||
phase: int = 1,
|
||||
) -> str | None:
|
||||
"""First matching code, or None. SP010 is not an include/status rule."""
|
||||
classes = classify(signals)
|
||||
if len(classes) > 1:
|
||||
return "SP011"
|
||||
if toolkit not in accepted(classes[0], phase=phase):
|
||||
return "SP010"
|
||||
if toolkit == "none" and status != "dropped":
|
||||
return "SP013"
|
||||
if toolkit in {"align", "none"} and include is not None:
|
||||
return "SP014"
|
||||
if status == "included" and toolkit in VECTOR and include != include_pdf(fig_id):
|
||||
return "SP015"
|
||||
if status == "included" and toolkit == "screenshot" and include != include_png(fig_id):
|
||||
return "SP016"
|
||||
return None
|
||||
|
||||
|
||||
def phase_from_env() -> int:
|
||||
import os
|
||||
raw = os.environ.get("SUPERPAPER_PHASE", "1")
|
||||
try:
|
||||
return int(raw)
|
||||
except ValueError:
|
||||
return 1
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
# Raster one ledger figure from paper.pdf via pdftoppm. crop_bbox is 200 dpi page pixels.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
WORK=""
|
||||
FID=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--work|--out) WORK="$2"; shift 2 ;;
|
||||
--id) FID="$2"; shift 2 ;;
|
||||
*) echo "usage: screenshot.sh --work DIR --id F2" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$WORK" && -n "$FID" ]] || { echo "need --work and --id" >&2; exit 2; }
|
||||
|
||||
"$ROOT/scripts/python" - "$WORK" "$FID" <<'PY'
|
||||
import sys, tempfile, subprocess, shutil
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
|
||||
work = Path(sys.argv[1])
|
||||
fid = sys.argv[2]
|
||||
ledger = yaml.safe_load((work / "ledger.yaml").read_text())
|
||||
fig = next((f for f in (ledger.get("figures") or []) if f.get("id") == fid), None)
|
||||
if fig is None:
|
||||
sys.exit(f"no figure {fid}")
|
||||
pages = fig.get("source_pages") or []
|
||||
if not pages:
|
||||
sys.exit("source_pages required")
|
||||
n = int(pages[0])
|
||||
pdf = work / "source" / "paper.pdf"
|
||||
if not pdf.is_file():
|
||||
sys.exit(f"missing {pdf}")
|
||||
dest_dir = work / "notes" / "figures" / fid
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
dest = dest_dir / "orig.png"
|
||||
bbox = fig.get("crop_bbox")
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
prefix = Path(td) / "pg"
|
||||
subprocess.run(
|
||||
["pdftoppm", "-png", "-r", "200", "-f", str(n), "-l", str(n), str(pdf), str(prefix)],
|
||||
check=True,
|
||||
)
|
||||
produced = sorted(Path(td).glob("pg*.png"))
|
||||
if not produced:
|
||||
sys.exit("pdftoppm produced no page")
|
||||
page = produced[0]
|
||||
if bbox is None:
|
||||
shutil.copy2(page, dest)
|
||||
else:
|
||||
if shutil.which("magick") is None:
|
||||
sys.exit("crop_bbox set but magick is missing")
|
||||
x0, y0, x1, y1 = bbox
|
||||
w, h = int(x1 - x0), int(y1 - y0)
|
||||
subprocess.run(
|
||||
["magick", str(page), "-crop", f"{w}x{h}+{int(x0)}+{int(y0)}", "+repage", str(dest)],
|
||||
check=True,
|
||||
)
|
||||
print(dest)
|
||||
PY
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
# Superpaper v1 checks: router unit tests, ledger codes, work-tree builds.
|
||||
set -uo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PY="$ROOT/scripts/python"
|
||||
LINT=("$PY" "$ROOT/scripts/lint.py")
|
||||
fail=0
|
||||
|
||||
ok() { echo " ok $*"; }
|
||||
bad() { echo " FAIL $*"; fail=$((fail+1)); }
|
||||
|
||||
echo "==> router_test"
|
||||
if "$PY" "$ROOT/tests/router_test.py"; then ok router_test.py; else bad router_test.py; fi
|
||||
|
||||
echo "==> ledger-valid"
|
||||
if "${LINT[@]}" --ledger "$ROOT/tests/ledger-valid.yaml" >/dev/null 2>&1; then
|
||||
ok ledger-valid.yaml
|
||||
else
|
||||
bad ledger-valid.yaml
|
||||
fi
|
||||
|
||||
expect_ledger() {
|
||||
local file="$1" code="$2"
|
||||
local out
|
||||
out=$("${LINT[@]}" --ledger "$file" 2>&1) || true
|
||||
if echo "$out" | grep -q "$code"; then
|
||||
ok "$(basename "$file") ($code)"
|
||||
else
|
||||
bad "$(basename "$file") (wanted $code)"
|
||||
echo "$out" | sed 's/^/ /'
|
||||
fi
|
||||
}
|
||||
|
||||
echo "==> ledger-invalid"
|
||||
for spec in \
|
||||
"missing-paper-id.yaml:SP001" \
|
||||
"bad-toolkit.yaml:SP001" \
|
||||
"duplicate-id.yaml:SP002" \
|
||||
"mixed-signals.yaml:SP011" \
|
||||
"toolkit-mismatch.yaml:SP010" \
|
||||
"none-not-dropped.yaml:SP013" \
|
||||
"align-has-include.yaml:SP014" \
|
||||
"included-null-include.yaml:SP015" \
|
||||
"screenshot-bad-include.yaml:SP016" \
|
||||
"superderive-v1.yaml:SP022" \
|
||||
"retired-reuse.yaml:SP023" \
|
||||
"dangling-claim.yaml:SP024"
|
||||
do
|
||||
f="${spec%%:*}"; c="${spec##*:}"
|
||||
expect_ledger "$ROOT/tests/ledger-invalid/$f" "$c"
|
||||
done
|
||||
|
||||
expect_work() {
|
||||
local dir="$1" code="$2"
|
||||
local out
|
||||
out=$("${LINT[@]}" --work "$dir" 2>&1) || true
|
||||
if echo "$out" | grep -q "$code"; then
|
||||
ok "$(basename "$dir") ($code)"
|
||||
else
|
||||
bad "$(basename "$dir") (wanted $code)"
|
||||
echo "$out" | sed 's/^/ /'
|
||||
fi
|
||||
}
|
||||
|
||||
echo "==> work-tree lint"
|
||||
expect_work "$ROOT/tests/loads-sty" SP003
|
||||
expect_work "$ROOT/tests/missing-request" SP012
|
||||
expect_work "$ROOT/tests/unlabeled-core" SP020
|
||||
expect_work "$ROOT/tests/missing-include" SP021
|
||||
|
||||
echo "==> compile"
|
||||
for dir in \
|
||||
"$ROOT/tests/notes-smoke" \
|
||||
"$ROOT/examples/excerpt-toy" \
|
||||
"$ROOT/examples/pipeline-delegate" \
|
||||
"$ROOT/examples/tensor-delegate"
|
||||
do
|
||||
name="$(basename "$dir")"
|
||||
if "$ROOT/scripts/build.sh" --work "$dir" >/dev/null 2>&1; then
|
||||
ok "$name (build)"
|
||||
else
|
||||
bad "$name (build)"
|
||||
"$ROOT/scripts/build.sh" --work "$dir" || true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $fail -gt 0 ]]; then
|
||||
echo "$fail failing check(s)" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "all v1 checks passed"
|
||||
@@ -0,0 +1,29 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C1
|
||||
title: align
|
||||
grammar: derivation
|
||||
toolkit: align
|
||||
signals: [rewrite-figure]
|
||||
include: figures/F1/build/F1.pdf
|
||||
status: included
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,28 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C1
|
||||
title: bad
|
||||
grammar: pipeline
|
||||
toolkit: not-a-toolkit
|
||||
signals: [pipeline]
|
||||
status: planned
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,28 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C99
|
||||
title: dangling
|
||||
grammar: pipeline
|
||||
toolkit: superfig
|
||||
signals: [pipeline]
|
||||
status: planned
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,21 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: one, kind: contribution, status: core}
|
||||
- {id: C1, text: two, kind: contribution, status: supporting}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures: []
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,30 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C1
|
||||
title: vector
|
||||
grammar: pipeline
|
||||
toolkit: superfig
|
||||
signals: [pipeline]
|
||||
include: null
|
||||
request: figures/F1/F1.request.md
|
||||
status: included
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,6 @@
|
||||
schema: superpaper.ledger/v1
|
||||
paper:
|
||||
title: Missing id
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
@@ -0,0 +1,28 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C1
|
||||
title: mixed
|
||||
grammar: architecture
|
||||
toolkit: superfig
|
||||
signals: [axis, architecture]
|
||||
status: planned
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,28 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C1
|
||||
title: none
|
||||
grammar: none
|
||||
toolkit: none
|
||||
signals: []
|
||||
status: planned
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,20 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: [C1]
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures: []
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,29 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F2
|
||||
claim: C1
|
||||
title: shot
|
||||
grammar: screenshot
|
||||
toolkit: screenshot
|
||||
signals: [table]
|
||||
include: figures/F2/wrong.png
|
||||
status: included
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,28 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C1
|
||||
title: derive
|
||||
grammar: derivation
|
||||
toolkit: superderive
|
||||
signals: [rewrite-figure]
|
||||
status: planned
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,28 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C1
|
||||
title: mismatch
|
||||
grammar: screenshot
|
||||
toolkit: superfig
|
||||
signals: [loss-curve]
|
||||
status: planned
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,20 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures: []
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,20 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures: []
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,7 @@
|
||||
\documentclass{article}
|
||||
\usepackage{amsmath}
|
||||
\begin{document}
|
||||
\usepackage{superfig}
|
||||
|
||||
hello
|
||||
\end{document}
|
||||
@@ -0,0 +1,30 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C1
|
||||
title: missing pdf
|
||||
grammar: pipeline
|
||||
toolkit: superfig
|
||||
signals: [pipeline]
|
||||
request: figures/F1/F1.request.md
|
||||
include: figures/F1/build/F1.pdf
|
||||
status: included
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1 @@
|
||||
toolkit: superfig
|
||||
@@ -0,0 +1,5 @@
|
||||
\documentclass{article}
|
||||
\begin{document}
|
||||
\splabel{C1}
|
||||
ok
|
||||
\end{document}
|
||||
@@ -0,0 +1,29 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures:
|
||||
- id: F1
|
||||
claim: C1
|
||||
title: missing req
|
||||
grammar: pipeline
|
||||
toolkit: superfig
|
||||
signals: [pipeline]
|
||||
request: figures/F1/F1.request.md
|
||||
status: planned
|
||||
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,5 @@
|
||||
\documentclass{article}
|
||||
\begin{document}
|
||||
\splabel{C1}
|
||||
ok
|
||||
\end{document}
|
||||
@@ -0,0 +1,21 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: smoke
|
||||
title: Smoke
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: smoke, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols:
|
||||
- {name: x, latex: "x", meaning: "input", kind: value}
|
||||
derivations: []
|
||||
figures: []
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,14 @@
|
||||
\documentclass[a4paper]{article}
|
||||
\input{notes-macros}
|
||||
\renewcommand{\notetitle}{Smoke}
|
||||
\begin{document}
|
||||
\section{这篇论文在问什么}
|
||||
一段动机。
|
||||
\section{主张与贡献}
|
||||
\splabel{C1} 这是核心主张。
|
||||
\section{总结与延伸}
|
||||
结束。
|
||||
\appendix
|
||||
\section{符号表}
|
||||
\input{sections/symbols.tex}
|
||||
\end{document}
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
||||
from router import accepted, check_row, suggest
|
||||
|
||||
|
||||
def expect(cond, msg):
|
||||
if not cond:
|
||||
raise SystemExit(f"FAIL: {msg}")
|
||||
|
||||
|
||||
expect(suggest({"loss-curve"}) == "numeric", "loss-curve class")
|
||||
expect(accepted("numeric") == frozenset({"screenshot", "matplotlib"}), "numeric accepted")
|
||||
expect(suggest({"table"}) == "raster", "table class")
|
||||
expect("matplotlib" not in accepted("raster"), "raster rejects matplotlib")
|
||||
expect(suggest({"axis", "architecture"}) == "SPLIT:tensor+fig", "split order")
|
||||
expect(
|
||||
check_row({"axis", "architecture"}, "superfig") == "SP011",
|
||||
"mixed row",
|
||||
)
|
||||
expect(check_row({"rewrite-figure"}, "align", phase=1) is None, "align ok")
|
||||
expect(check_row({"rewrite-figure"}, "superderive", phase=1) == "SP010", "derive v1")
|
||||
expect(suggest(set()) == "none", "empty")
|
||||
expect(suggest({"notation"}) == "none", "notation")
|
||||
expect(check_row(set(), "none", status="included") == "SP013", "none included")
|
||||
expect(
|
||||
check_row({"rewrite-figure"}, "align", include="figures/F1/build/F1.pdf") == "SP014",
|
||||
"align include",
|
||||
)
|
||||
expect(
|
||||
check_row({"architecture"}, "superfig", status="included", include=None) == "SP015",
|
||||
"vector null include",
|
||||
)
|
||||
expect(
|
||||
check_row({"table"}, "screenshot", status="included", include="nope.png", fig_id="F2")
|
||||
== "SP016",
|
||||
"screenshot path",
|
||||
)
|
||||
print("router_test ok")
|
||||
@@ -0,0 +1,20 @@
|
||||
schema: superpaper.ledger/v1
|
||||
retired_ids: []
|
||||
paper:
|
||||
id: toy
|
||||
title: Toy
|
||||
source: {kind: excerpt}
|
||||
coverage:
|
||||
mode: excerpt
|
||||
questions: []
|
||||
claims:
|
||||
- {id: C1, text: core claim, kind: contribution, status: core}
|
||||
definitions: []
|
||||
assumptions: []
|
||||
lemmas: []
|
||||
symbols: []
|
||||
derivations: []
|
||||
figures: []
|
||||
evidence: []
|
||||
terms: []
|
||||
source_assets: []
|
||||
@@ -0,0 +1,7 @@
|
||||
\documentclass{article}
|
||||
\usepackage{amsmath}
|
||||
\begin{document}
|
||||
% no label
|
||||
|
||||
hello
|
||||
\end{document}
|
||||