Figures were positioned by hand-written offsets. Every gap was a magic number tuned against the content that happened to be there, so a label that grew two characters landed on the next tensor, and two stages started from two different x shared no rail. Both failures compile cleanly. Replace it with a cursor. Objects placed with an empty coordinate argument reserve their own width -- including a stack's offset sheets and a bracket's overhang -- and gaps are declared once (\stgutter, \strowgap, \stblockgap). The gap belongs to the object that follows it and the first object in a band gets none, so every band starts flush on a shared rail and gap=0pt states that two shards tile exactly. \stlink makes a connector's label a flow object, which is what removes the label-wider-than-its-arrow failure entirely. \stcol is a vertical sub-flow for a split along the contracted axis. \strow declares its height, so an object that does not fit -- or a column that does not add up to what it declared, and is therefore drawn off-center -- becomes a package warning, which build.sh fails on. Absolute placement is unchanged: passing a coordinate takes the original code path, and \sttrack folds a hand-placed node back into the cursor. All three golden examples and the new tests/flow.tex are converted and build clean.
191 lines
8.3 KiB
Markdown
191 lines
8.3 KiB
Markdown
# supertensor.sty API
|
||
|
||
```tex
|
||
\documentclass[border=10pt]{standalone}
|
||
\usepackage[cjk]{supertensor} % cjk: ctex + fandol (XeLaTeX). en: English rail labels.
|
||
```
|
||
|
||
Build with `./scripts/build.sh fig.tex` — it puts `assets/` on `TEXINPUTS`, so the package
|
||
does not need to be installed into your texmf tree.
|
||
|
||
## Ledgers
|
||
|
||
```tex
|
||
\stsetrole{q}{stTeal} % role -> color. Macros take a ROLE, never a color.
|
||
\stdim{T}{6} % symbolic axis -> physical edge length in cells
|
||
\stsetauthor{...} % default 五道口纳什
|
||
\stsetlabels{A}{O}{M} % override the three meaning-box rail labels
|
||
\stsetrail{3.2em} % width of the bold label rail
|
||
```
|
||
|
||
Colors: `stTeal stOrange stCoral stViolet stGray stInk`. An unknown role falls back to gray
|
||
**and emits a package warning**, which `build.sh` turns into a failed build.
|
||
|
||
Lengths: `\stunit` (one cell, 4.6 mm) and `\sttilegap` (white gutter, 0.5 mm).
|
||
|
||
## Flow layout
|
||
|
||
**This is the default. Leave the coordinate argument empty and the object is placed by a
|
||
cursor.** Hand-written offsets are the main source of layout bugs in these figures: every
|
||
gap becomes a tuned magic number, so a label that grows by two characters silently lands on
|
||
the next tensor, and two stages started from two different `x` share no rail.
|
||
|
||
```tex
|
||
\ststage{SA}{stage heading} % on the left rail, below the previous band
|
||
\strow{rowA}{T} % open a band, declared height T
|
||
\stface[role=q]{Q}{}{T}{d} % empty coord = place at the cursor
|
||
\stglyph{mA}{$\times$} % operator glyph; reserves its own width
|
||
\stface[role=w]{W}{}{d}{d}
|
||
\stlink{lA}{softmax} % connector whose LABEL is a flow object
|
||
\stface[role=s]{S}{}{T}{d}
|
||
\strowend % fit the band, arm the caption lanes
|
||
\stcaption{Q}{$\mathbf Q$}{$T\times d$}
|
||
```
|
||
|
||
| macro | does |
|
||
|---|---|
|
||
| `\ststage{name}{text}` | stage heading on the left rail, below all ink so far |
|
||
| `\strow{name}{height}` | open a band; `height` is an axis name or an integer |
|
||
| `\strowend` | `fit` the band into `name`, then `\stlane` it |
|
||
| `\stcol{name}{height}` … `\stcolend` | vertical sub-flow filling one slot of the band |
|
||
| `\stglyph{name}{$\times$}` | operator glyph (not `\stop` — plain TeX owns that name) |
|
||
| `\stcomm{name}{All-Reduce}` | collective node |
|
||
| `\stnode[style]{name}{text}` | any node, placed and measured by the cursor |
|
||
| `\stlink{name}{label}` | connector; empty label reserves `\stlinklen` of bare arrow |
|
||
| `\stgap{4mm}` / `\stvgap{4mm}` | one-off extra space, horizontal / vertical |
|
||
| `\stbbox{all}` | everything drawn so far, as one node, for `\stmeaningbox` |
|
||
| `\sttopformula{F}{math}` | the formula line, centered on what was actually drawn |
|
||
| `\sttrack{node}` | fold a hand-placed node into the bbox and the vertical cursor |
|
||
| `\stleftrail{x}` / `\stlayoutreset` | move the rail / start over |
|
||
|
||
Gaps are declared once: `\stgutter` (6 mm, between objects in a band), `\strowgap` (3.5 mm,
|
||
above a band), `\stblockgap` (9 mm, above a stage heading), `\stlinklen` (10 mm).
|
||
|
||
Four properties follow by construction, and each of them is a bug class removed:
|
||
|
||
- **The gap belongs to the object that *follows* it**, and the first object in a band gets
|
||
none — so every band starts flush on the same rail, and `gap=0pt` means *exactly
|
||
adjacent*, which is how shards are made to tile their parent.
|
||
- **Every object reserves its own width**, including a stack's offset sheets and a bracket's
|
||
overhang. `right=6mm of X` reserves nothing, so the next face is free to land on top.
|
||
- **A connector's label is a flow object**, so a label can never be wider than its arrow.
|
||
- **A band declares its height**, so an object that does not fit is a *build failure*
|
||
(`Package supertensor Warning`), not something the reader discovers.
|
||
|
||
Call `\sttopformula` **after** the bands. A formula placed first can only be centered on a
|
||
figure whose width is not yet known — that is how the top line ends up visibly off-center.
|
||
|
||
`\stcol` is what a split along the contracted axis looks like:
|
||
|
||
```tex
|
||
\stcol{W2}{dff} % declared total height
|
||
\stface[role=r1]{W2a}{}{dffl}{d}
|
||
\stface[role=r2, gap=0pt]{W2b}{}{dffl}{d} % tiles W2a exactly
|
||
\stcolend
|
||
```
|
||
|
||
A column that consumes a height other than the one it declares is drawn off-center, so that
|
||
is a warning too.
|
||
|
||
Absolute placement still works everywhere — pass a coordinate instead of `{}`. Mix freely,
|
||
but wrap hand-placed nodes in `\sttrack` so the cursor knows about them.
|
||
|
||
## Faces
|
||
|
||
```tex
|
||
\stface[keys]{name}{}{rows}{cols} % flow
|
||
\stface[keys]{name}{(coord)}{rows}{cols} % absolute
|
||
\ststack[keys]{name}{(coord)}{rows}{cols}{sheets}
|
||
\stindexface[keys]{name}{(coord)}{rows}{cols}{entries}
|
||
```
|
||
|
||
`rows`/`cols` accept a declared axis name or a raw integer. A non-empty `(coord)` must
|
||
include its own parentheses — `{(0,0)}`, `{($(A.east)+(1.5,0)$)}`. `name` becomes a TikZ
|
||
node you can anchor against; `\ststack` also defines `name-front`.
|
||
|
||
Keys:
|
||
|
||
| key | default | meaning |
|
||
|---|---|---|
|
||
| `role=` | `neutral` | hue, via `\stsetrole` |
|
||
| `pattern=` | `dense` | `solid dense diag band lower upper causal empty data` |
|
||
| `data=` | — | with `pattern=data`: comma-separated rows, one digit per cell, `0`–`3` = level |
|
||
| `level=` | `2` | level for `pattern=solid` and the flat level of a mask |
|
||
| `bracket=` | `false` | thin neutral matrix brackets |
|
||
| `border=` | `true` | outer `black!60` border |
|
||
| `tiles=` | `true` | `false` = one flat filled rectangle |
|
||
| `gap=` | `\stgutter` | flow only: space *before* this object. `gap=0pt` = exactly adjacent |
|
||
|
||
`\stindexface` entries are row-major, `rows*cols` of them; `.` leaves a cell blank.
|
||
It deliberately has no lightness ramp — see `semantics.md`.
|
||
|
||
```tex
|
||
\stface[role=w, pattern=data, level=3,
|
||
data={3300,0330,0033,3003,3030,0303}]{D}{(0,0)}{T}{E}
|
||
\stindexface[role=idx]{I}{(b)}{T}{k}{0,1, 1,2, 2,3, 3,0, 0,2, 1,3}
|
||
```
|
||
|
||
## Captions
|
||
|
||
`\strowend` calls `\stlane` for you, so in flow mode captions just follow the band:
|
||
|
||
```tex
|
||
\strowend
|
||
\stcaption{A}{$\mathbf A$}{$T\times d$} % symbol lane, shape lane
|
||
\stcaptiontop{A}{\stshapefont{token}} % occasional label above a face
|
||
```
|
||
|
||
Arming the lane by hand (absolute placement):
|
||
|
||
```tex
|
||
\node[inner sep=0pt, fit=(A)(B)(C)] (rowA) {};
|
||
\stlane{rowA}
|
||
\stcaption{A}{$\mathbf A$}{$T\times d$}
|
||
\stnolane
|
||
```
|
||
|
||
`\stcaption` defines `name-sym` and `name-shape` nodes; anchor the next stage heading
|
||
against `name-shape.south`.
|
||
|
||
## Operators, connectors, nodes
|
||
|
||
Prefer `\stglyph` / `\stcomm` / `\stlink` (above). The raw forms are for absolute placement:
|
||
|
||
```tex
|
||
\node[st op, right=6mm of A] (m) {$\times$}; % reserves nothing -- see layout.md
|
||
\node[st comm, right=9mm of P] (ar) {All-Reduce};
|
||
\starrow{P.east}{ar.west}
|
||
\starrowlabel{M.east}{A.west}{softmax}
|
||
```
|
||
|
||
Styles: `st sym st shape st stage st op st note st arrow st comm st brace`.
|
||
Text helpers: `\stformula \ststagelabel \stoperator \stsymfont \stshapefont \stprose`.
|
||
Connectors route on the background layer automatically.
|
||
|
||
## Bottom
|
||
|
||
```tex
|
||
\stbbox{all} % flow: everything drawn so far
|
||
\stmeaningbox{mb}{16.6cm}{all}{axes text}{objects text}{mechanism text}
|
||
\stsignature{因果多头注意力(掩码 + 拼接投影)}{mb}
|
||
```
|
||
|
||
Arg 2 is the total box width; arg 3 is the node it hangs below. `\stbbox` already contains
|
||
every caption and top label; if you build the `fit` by hand, include them yourself or the
|
||
box will overlap them. An empty `{}` row is dropped.
|
||
|
||
## Gotchas
|
||
|
||
- `\ststack` never re-enters `\stface`; if you extend the package, do not pass
|
||
`role=\st@role` back through pgfkeys — it defines the macro in terms of itself and hangs.
|
||
- A coordinate expression inside `fit=` needs braces: `fit={(a) ($(b)+(1,0)$)}`.
|
||
- `\foreach {\macro,...,1}` cannot infer its direction from an unexpanded macro.
|
||
- `\strole` is expandable on purpose (it is used inside `\edef`); the warning lives in
|
||
`\stcheckrole`.
|
||
- Neither the TikZ path parser nor the `calc` library expands a macro sitting where it
|
||
expects `(`. Every cursor-computed coordinate therefore reaches the parser as literal
|
||
text, via `\edef ... \noexpand`. Same class of trap: pgfmath cannot digest `\stresolve`'s
|
||
`\ifcsname`, so a ledger lookup must be pre-resolved with `\edef` before it is measured.
|
||
- `\sttopformula` puts a group around its argument, which breaks TikZ's own `\\`. For more
|
||
than one line, wrap the math in amsmath's `gathered`.
|