headway 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- headway-0.1.0/PKG-INFO +8 -0
- headway-0.1.0/README.md +211 -0
- headway-0.1.0/pyproject.toml +22 -0
- headway-0.1.0/setup.cfg +4 -0
- headway-0.1.0/src/headway/GUIDE.md +375 -0
- headway-0.1.0/src/headway/__init__.py +12 -0
- headway-0.1.0/src/headway/api.py +135 -0
- headway-0.1.0/src/headway/bench.py +264 -0
- headway-0.1.0/src/headway/check.py +146 -0
- headway-0.1.0/src/headway/cli.py +140 -0
- headway-0.1.0/src/headway/diff.py +265 -0
- headway-0.1.0/src/headway/page.py +208 -0
- headway-0.1.0/src/headway/resolve.py +131 -0
- headway-0.1.0/src/headway/serve.py +221 -0
- headway-0.1.0/src/headway/spec.py +187 -0
- headway-0.1.0/src/headway/trace.py +147 -0
- headway-0.1.0/src/headway/viewer.html +1998 -0
- headway-0.1.0/src/headway.egg-info/PKG-INFO +8 -0
- headway-0.1.0/src/headway.egg-info/SOURCES.txt +22 -0
- headway-0.1.0/src/headway.egg-info/dependency_links.txt +1 -0
- headway-0.1.0/src/headway.egg-info/entry_points.txt +2 -0
- headway-0.1.0/src/headway.egg-info/requires.txt +4 -0
- headway-0.1.0/src/headway.egg-info/top_level.txt +1 -0
- headway-0.1.0/tests/test_headway.py +415 -0
headway-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: headway
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: See how a method works and how much each change to it gains: a checked computation graph of the code, its versions and their benchmark results
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: numpy>=1.26
|
|
7
|
+
Provides-Extra: test
|
|
8
|
+
Requires-Dist: pytest>=8; extra == "test"
|
headway-0.1.0/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# headway
|
|
2
|
+
|
|
3
|
+
See how a method works, and how much headway each change to it makes. A spec module declares a method's indices,
|
|
4
|
+
symbols (with their dimensions), steps (with their equations) and groups, each pointing into the code. headway
|
|
5
|
+
checks it against the code, so the picture cannot quietly go stale, and draws it as an interactive computation
|
|
6
|
+
graph, with a legend of indices, symbols and equations and the code behind every step. Versions of the method
|
|
7
|
+
(commits, experiments, competing methods) open side by side, and their benchmark results show as a heatmap, so
|
|
8
|
+
you can see what each change gained.
|
|
9
|
+
|
|
10
|
+
**[GUIDE.md](src/headway/GUIDE.md)** (also `headway guide`) is the full description: how headway works and how
|
|
11
|
+
to write a headway for a codebase. It is written to be handed to a coding agent working in another repository.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
pip install -e /path/to/headway # into the environment that imports the method
|
|
15
|
+
headway check my_pkg.graphspec # exit 1 if the spec and the code disagree
|
|
16
|
+
headway build my_pkg.graphspec -o headway.html # one HTML file; open it in a browser
|
|
17
|
+
[--collapse "round 0"] [--hide setup] [--json headway.json] [--no-verify] [--offline]
|
|
18
|
+
[--name v3] [--compare v1.json --compare v2.html] [--results results.json]
|
|
19
|
+
headway serve my_pkg.graphspec [--open] # the viewer at http://127.0.0.1:8765/, rebuilt live
|
|
20
|
+
[--compare old.json]
|
|
21
|
+
headway diff v1.json v2.json my_pkg.graphspec # what changed from each version to the next
|
|
22
|
+
headway attach v3.json results.json # put benchmark results into a built headway (or build --results)
|
|
23
|
+
headway results v1.json v2.json v3.json # the versions' results on their common benchmark suite
|
|
24
|
+
headway guide # the guide
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
From Python, e.g. at the end of a benchmark script in the method's own repository:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import headway
|
|
31
|
+
headway.build("my_pkg.graphspec", "headways/v5.html", json="headways/v5.json", name="v5",
|
|
32
|
+
results=[{"suite": "my_bench.suite", "values": values, "run": {"seeds": [0, 1, 2]}}],
|
|
33
|
+
compare=["headways/v4.json"])
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`serve` watches the spec's package and every file its code references point to. On a save it rebuilds in a fresh
|
|
37
|
+
Python process (about a second, including the equation checks) and the open page takes the new graph without
|
|
38
|
+
losing its view: the selection, the zoom, the open groups. Steps that changed pulse, and a note says how many
|
|
39
|
+
changed. A spec that fails to build leaves the last good graph up, with the error over it. Each step, value and
|
|
40
|
+
group in the side panel links to its declaration in the spec ("✎ declared at …").
|
|
41
|
+
|
|
42
|
+
## Editing from the page
|
|
43
|
+
|
|
44
|
+
The page stays a static view; edits happen in the spec, and the page takes you there.
|
|
45
|
+
|
|
46
|
+
- **✎ links** open the spec in your editor at the exact argument: the equation, the description, the inputs, the
|
|
47
|
+
code references. They are in the side panel, and on the right of each legend row when you hover over it. For an
|
|
48
|
+
object declared inside a helper function of the spec, the panel says so: editing the helper changes everything
|
|
49
|
+
it declares.
|
|
50
|
+
- **`--editor`** (on `build` and `serve`, or `$HEADWAY_EDITOR`): `vscode` (the default), `cursor`, `pycharm`,
|
|
51
|
+
`file`, or a URL template with `{file}`, `{line}` and `{col}`.
|
|
52
|
+
- **Ask an AI**, in the side panel, writes a prompt to paste into any assistant that can read the repository. It
|
|
53
|
+
carries the object's declaration and location, its inputs and outputs, the code it points to and its equation
|
|
54
|
+
check; with nothing selected, it is about the spec as a whole. It has two modes:
|
|
55
|
+
- **Change the spec**: the request, with instructions to keep the references resolving, keep the `@G.verify`
|
|
56
|
+
function in line with any changed equation, flag needed code changes, and run `headway check`. Preset requests
|
|
57
|
+
fill in the box, worded for what is selected (a step, the steps around a symbol, a group, or the whole spec):
|
|
58
|
+
**define local symbols** (put the symbols an equation uses but the graph does not define in `where=`), **add
|
|
59
|
+
explanations** (a sentence or two in the spec on what each computation and output is, and what it is for),
|
|
60
|
+
**check descriptions** (against the code), **reconcile with the code** (after the code changed), **standardize
|
|
61
|
+
notation** (the same operation written the same way; fixed numbers as constants) and **disambiguate symbols**
|
|
62
|
+
(one meaning per symbol across the spec).
|
|
63
|
+
- **Explain**: a question, answered as a separate product; the assistant is told to change nothing, to take the
|
|
64
|
+
code as the ground truth and to say which reasons are documented and which are its own reading. Presets: what is
|
|
65
|
+
it for, read the equation, why this way, how it fits in, code vs. math, check my understanding. **Answer as**
|
|
66
|
+
sets how elaborate the answer is: a quick answer, a full explanation, a tutoring session, a worked example,
|
|
67
|
+
study notes to keep, or an interactive page (shown the way the assistant's environment shows pages, or as one
|
|
68
|
+
HTML file).
|
|
69
|
+
|
|
70
|
+
The mode and the answer format are remembered per browser; what is typed is kept per object and per mode.
|
|
71
|
+
|
|
72
|
+
The built page is a static file: open it from disk, send it, or put it on any web server. It loads KaTeX, elkjs and
|
|
73
|
+
highlight.js from CDNs; with `--offline` they are written into the file instead (about 3 MB), and the page needs no
|
|
74
|
+
network at all. `--offline` downloads them once, into `~/.cache/headway`. The ✎ links point to the spec on the
|
|
75
|
+
machine that built the page.
|
|
76
|
+
|
|
77
|
+
The viewer fills the window: the graph, the side panel on the right and
|
|
78
|
+
the legend below. Drag a divider to resize; drag it nearly shut, double-click it or use its arrow to collapse a
|
|
79
|
+
pane. The sizes are remembered per browser. Click a step or a value for its equation, inputs, outputs and
|
|
80
|
+
code (with a link that opens your editor at the line); double-click a group to collapse or expand it; show only what is
|
|
81
|
+
upstream or downstream of the selection; hide groups; show the settings as nodes. Steps done once per fold or per
|
|
82
|
+
cell are drawn as stacked cards; ✓ marks an equation checked against the code.
|
|
83
|
+
|
|
84
|
+
## A spec
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from headway import Headway
|
|
88
|
+
G = Headway("my method")
|
|
89
|
+
j = G.index("j", "j", "bin")
|
|
90
|
+
g = G.index("g", "g", "group")
|
|
91
|
+
m = G.symbol("m", "m_{jg}", "calls", dims=(j, g), role="input")
|
|
92
|
+
R = G.symbol("R", "R_{jg}", "residuals", dims=(j, g))
|
|
93
|
+
kr = G.setting("kr", r"\kappa_r", "pseudocounts", value=5, code="my_pkg.settings:Settings.pseudo")
|
|
94
|
+
G.op("residuals", "residuals", r"R_{jg} = \frac{m_{jg} + \kappa_r q}{n_{jg} + \kappa_r} - q",
|
|
95
|
+
inputs=[m, kr], outputs=[R], group="model",
|
|
96
|
+
code="my_pkg.model:residuals", # or "my_pkg.model:fit#step 5" / "#from text..to text"
|
|
97
|
+
bind={"methylated": m, "return": R}, # for the shape trace
|
|
98
|
+
where={"q": "the genome-wide fraction"}) # symbols used only in this step's equation
|
|
99
|
+
|
|
100
|
+
@G.verify("residuals") # the equation, written plainly, against the code
|
|
101
|
+
def residuals_as_written(): ...
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`where` is for symbols an equation uses that are not values of the graph: a step's own intermediates, functions
|
|
105
|
+
like the logistic σ. The viewer shows them under the equation, as a paper would ("where q is …"). A value another
|
|
106
|
+
step also uses should be a symbol, listed in both steps' inputs or outputs, so it shows as a node and an edge.
|
|
107
|
+
headway does not read the TeX, so it cannot tell you which symbols lack a definition; it only checks that a
|
|
108
|
+
where-entry is not written exactly as a declared symbol the step should list instead.
|
|
109
|
+
|
|
110
|
+
## Comparing versions
|
|
111
|
+
|
|
112
|
+
A version is a built headway: the JSON from `build --json`, or a page from `build`. Keep one wherever it is useful
|
|
113
|
+
(at a tag, before an experiment, next to a set of results) and compare any of them, with each other or with the
|
|
114
|
+
spec as it is now. headway does not manage the versions or need git; it records the spec's file and, if it is in
|
|
115
|
+
a git repository, the commit, branch and whether the work tree had changes, and shows them with the version.
|
|
116
|
+
|
|
117
|
+
- **In the viewer**, `--compare` (on `build` and `serve`) or the **Compare…** button opens other versions, each in a
|
|
118
|
+
lane of its own. The lanes share one layout, so a step sits in the same place in every lane: stacked one above
|
|
119
|
+
another when the graph flows right, side by side when it flows down. A marker on each node says what changed
|
|
120
|
+
against the lane before it (or against a lane pinned as the baseline): **+** added, **Δ** changed, **−** gone
|
|
121
|
+
(drawn dashed where it was), **⋔** split or merged. The lanes start in the order the versions were built (a
|
|
122
|
+
version opened later takes its place in that order); drag them to reorder, × to close one, any number of them.
|
|
123
|
+
Opening a version that is open already (the same build, or one given the same `--name`) reloads it. **One at a
|
|
124
|
+
time** draws only the version you are reading, with its markers, and keeps the others open: click another to
|
|
125
|
+
switch. Click a lane to read that version in the side panel and the legend. Selecting an object selects it in every
|
|
126
|
+
lane; the panel lists it version by version and shows what changed, with the equations one above the other
|
|
127
|
+
and a diff of the code. Ask-AI prompts say which version they are about and what changed in it; Explain has
|
|
128
|
+
**What changed?**. **Collapse unchanged** leaves open only the groups where something changed.
|
|
129
|
+
- **`headway diff`** prints the same comparison as text, version after version (`--wording` for labels and
|
|
130
|
+
descriptions too). A spec module is built as it is now.
|
|
131
|
+
|
|
132
|
+
Objects correspond by key. When a key changes, say so with `was=`, on symbols, settings, constants, steps and
|
|
133
|
+
groups:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
G.op("weighted_score", "module score", ..., was="score") # renamed
|
|
137
|
+
G.op("sizefactor", "size factors", ..., was="normalize") # split: both halves name
|
|
138
|
+
G.op("scale_counts", "scale counts", ..., was="normalize") # the step they came from
|
|
139
|
+
G.symbol("S", "S_i", "module score", ..., was=["score", "cell_score"]) # renamed twice
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
A merged object lists every key it replaces. `was` is matched across all the open versions at once, so a rename two
|
|
143
|
+
versions back still lines up; `headway check` rejects a `was` naming a key the spec still declares. What counts
|
|
144
|
+
as a change is read from the built versions, never from the TeX: inputs and outputs, group, dimensions (structure),
|
|
145
|
+
the equation's text, the code the references resolve to (so a code change under an unchanged equation shows), a
|
|
146
|
+
setting's value, the equation check, and the wording (labels, descriptions; hidden unless asked for). Code no
|
|
147
|
+
reference points to is not seen.
|
|
148
|
+
|
|
149
|
+
`examples/cellscore` is a small method developed over four versions (gene centering; size factors, splitting a
|
|
150
|
+
step; gene weights, renaming the score), with `build.sh` building each version and the comparison page.
|
|
151
|
+
|
|
152
|
+
## Benchmarks
|
|
153
|
+
|
|
154
|
+
A benchmark suite is declared like a method, as a `headway.Suite` (a Headway): each metric is a step, with its
|
|
155
|
+
notation, equation, description, code reference and `@verify` check, so it is annotated and checked the same way.
|
|
156
|
+
A suite adds its datasets (some can be held out), each metric's direction and range, and objectives, which combine
|
|
157
|
+
metrics into one number to rank by and are declared apart from them.
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
B = Suite("my benchmark", version="1")
|
|
161
|
+
S = B.symbol("S", "S_i", "score", dims=(i,), role="input")
|
|
162
|
+
B.dataset("deep", "deep sequencing", "...", values={"depth_sd": 0.3}, code="my_bench.data:simulate")
|
|
163
|
+
B.dataset("atlas", "cell atlas", "...", source="GSE123456", facts={"cells": "12,000", "platform": "10x v3"},
|
|
164
|
+
heldout=True, code="my_bench.data:load_atlas")
|
|
165
|
+
B.metric("auroc", r"\mathrm{AUROC}", "separation", "How often ...", equation=r"...", inputs=[S, y],
|
|
166
|
+
better="higher", range=(0, 1), code="my_bench.metrics:auroc", source="Mann-Whitney U, scaled")
|
|
167
|
+
B.objective("overall", "overall", metrics={"auroc": 2, "depth_dep": 1}) # how="mean" (absolute) or "rank"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Datasets are annotated too. When the suite makes its data (a simulation, a preprocessing), declare those steps in
|
|
171
|
+
the suite like any other, with their equations, code and checks, and give each dataset its `values` of their
|
|
172
|
+
settings: the steps are written once, and the datasets differ in their values (`headway check` rejects a value
|
|
173
|
+
for anything but a declared setting). A dataset can also have a `source`, `facts` (anything else worth knowing)
|
|
174
|
+
and `code` (where it is made or loaded).
|
|
175
|
+
|
|
176
|
+
headway computes no metric. Your benchmark writes a results file, `{"suite": "my_bench.suite", "values":
|
|
177
|
+
{dataset: {metric: [one value per replicate]}}, "run": {...}}`, and `build --results` or `attach` puts it into a
|
|
178
|
+
version's headway with the suite, built and checked. Versions with results on the same suite (title and version) are
|
|
179
|
+
compared, wherever their files are.
|
|
180
|
+
|
|
181
|
+
In the viewer, **Graph | Results** switches between the computation graph and the results of the same open
|
|
182
|
+
versions: a heatmap of versions × metrics × datasets (grouped by metric or by dataset), each value the mean over
|
|
183
|
+
replicates ± their s.d., shaded from worst to best among the versions (or across the metric's range), with its
|
|
184
|
+
difference from the version it is compared with. Missing results are hatched, never filled in; an objective missing
|
|
185
|
+
any value it needs is "incomplete". Click a column for what the metric or objective is (its equation, code, check
|
|
186
|
+
and source) and the replicates of every version, plotted; click it again to sort by it. Click a dataset (under
|
|
187
|
+
the table, or its column when grouped by dataset) for what it is: its settings, how it is made, its facts and code,
|
|
188
|
+
and every version's results on it. In the suite's own page, the legend lists the datasets, and a setting shows
|
|
189
|
+
its value in each. Click a version for what
|
|
190
|
+
changed in it, and to go to it in the graph. The Ask-AI prompts there carry the results, the metrics' definitions
|
|
191
|
+
and what changed between the versions: **Why do they differ?** and **What to try next?**. `headway results`
|
|
192
|
+
prints the same table as text. The objectives are the only computation headway does on results (bench.py; the
|
|
193
|
+
viewer computes them the same way).
|
|
194
|
+
|
|
195
|
+
## What is checked
|
|
196
|
+
|
|
197
|
+
| check | catches | where |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| references | a renamed, moved or deleted function; an anchor comment that is gone | `headway check` |
|
|
200
|
+
| structure | undeclared symbols or indices, two producers of one value, cycles, unused values | `headway check` |
|
|
201
|
+
| equations (`@G.verify`) | code that no longer computes the equation as written | `headway check`, marked ✓/✗ in the viewer |
|
|
202
|
+
| shapes (`headway.trace`) | arrays whose axes do not match the declared indices, an index with two sizes | your test suite |
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
with headway.trace(G) as t:
|
|
206
|
+
run_the_method_on_small_data()
|
|
207
|
+
t.assert_ok()
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Not checked: that the graph's edges are all the data flow there is (a step could use a value the spec does not
|
|
211
|
+
list), or that an equation without a `@verify` matches its code.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "headway"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "See how a method works and how much each change to it gains: a checked computation graph of the code, its versions and their benchmark results"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = ["numpy>=1.26"]
|
|
11
|
+
|
|
12
|
+
[project.optional-dependencies]
|
|
13
|
+
test = ["pytest>=8"]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
headway = "headway.cli:main"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
where = ["src"]
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.package-data]
|
|
22
|
+
headway = ["viewer.html", "GUIDE.md"]
|
headway-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# The headway guide
|
|
2
|
+
|
|
3
|
+
This guide is for people and for coding agents. It says what headway is, how to write a headway for a codebase,
|
|
4
|
+
how to keep it true as the code changes, and how to put benchmark results into it. It assumes nothing beyond this
|
|
5
|
+
file. `headway guide` prints it.
|
|
6
|
+
|
|
7
|
+
## What headway is
|
|
8
|
+
|
|
9
|
+
A **headway** describes a method (a computational or statistical procedure in some codebase) as a computation
|
|
10
|
+
graph: the values it works with, the steps that turn some values into others, each step's equation, and a
|
|
11
|
+
reference into the code that computes it. You write it as a Python module, the **spec**, kept next to the code.
|
|
12
|
+
headway then:
|
|
13
|
+
|
|
14
|
+
- **checks the spec against the code**: every code reference resolves; the graph is well formed; each equation you
|
|
15
|
+
attach a check to reproduces what the code computes; on a real run, arrays have the dimensions the spec says.
|
|
16
|
+
So the description cannot quietly go stale.
|
|
17
|
+
- **draws it** as one interactive HTML page: the graph (groups collapse and expand), a legend of indices, symbols
|
|
18
|
+
and equations, and for each step its equation, description and the code itself, with links that open your
|
|
19
|
+
editor at the line. It also writes prompts for an AI assistant about whatever is selected.
|
|
20
|
+
- **compares versions**: built versions of a headway (one per commit, experiment or competing method) open side
|
|
21
|
+
by side in lanes, registered on one layout, with markers for what was added, changed or removed.
|
|
22
|
+
- **shows benchmark results**: a benchmark suite is declared the same way (its metrics are steps with equations
|
|
23
|
+
and checks; its datasets are annotated), and each version's results are stored in its headway. The viewer shows
|
|
24
|
+
a heatmap of versions × metrics × datasets, so you can see what each change gained or lost.
|
|
25
|
+
|
|
26
|
+
What headway does **not** do: it does not run your method, compute metrics, manage git or experiments, or read
|
|
27
|
+
equations. Equations are TeX strings shown as written; headway never parses them. Whether an equation matches the
|
|
28
|
+
code is established only by a check function you write (`@G.verify`).
|
|
29
|
+
|
|
30
|
+
## Installing it in another codebase
|
|
31
|
+
|
|
32
|
+
headway is a Python package (Python 3.12+, numpy). Install it into the environment that can import the method:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install -e /path/to/headway # or: uv pip install -e /path/to/headway
|
|
36
|
+
headway guide # this file
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The spec imports headway; the method's own code never imports the spec or headway. A suggested layout:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
mypkg/ the method
|
|
43
|
+
mypkg/graphspec.py the spec: one Headway object, conventionally named G
|
|
44
|
+
bench/mybench/suite.py a benchmark suite (optional): one headway.Suite, conventionally named B
|
|
45
|
+
bench/run.py runs the method on the benchmark and builds a headway with the results
|
|
46
|
+
(run with bench/ on PYTHONPATH, so that `mybench.suite` imports)
|
|
47
|
+
headways/ built versions: v1.json, v1.html, ...
|
|
48
|
+
tests/test_headway.py the check and the shape trace, in the test suite
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## The pieces of a spec
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from headway import Headway
|
|
55
|
+
G = Headway("my method", "One sentence on what it computes, from what.")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Indices** name the axes of arrays: `G.index(key, tex, name, desc="", *, varies=False)`.
|
|
59
|
+
`i = G.index("i", "i", "cell")`. `varies=True` if the axis may have different sizes in different calls (a subset).
|
|
60
|
+
|
|
61
|
+
**Symbols** are the values: data, intermediate results, outputs, settings and constants.
|
|
62
|
+
`G.symbol(key, tex, name, desc="", *, dims=(), per=None, role="value", value=None, code=(), was=())`
|
|
63
|
+
|
|
64
|
+
- `dims`: the indices, in order (`dims=(i, g)` is an i × g array; `()` a scalar).
|
|
65
|
+
- `role`: `"input"` (data given to the method), `"value"` (computed inside), `"output"` (a result of the method),
|
|
66
|
+
`"setting"` (configurable), `"constant"` (a number fixed in the code).
|
|
67
|
+
- `per`: an index the symbol has one copy of per value of. The copies themselves have `dims` without it: one
|
|
68
|
+
model per fold is `dims=(g,), per=fold`; one error per candidate penalty, computed by one call each, is
|
|
69
|
+
`dims=(), per=a`, not `dims=(a,)` (the shape trace checks each call's value against `dims`).
|
|
70
|
+
- `code`: optional. For an input, where it is read, loaded or defined, if there is such a place; for a setting,
|
|
71
|
+
its config field; for a constant, the line that sets it (a constant without one is a warning).
|
|
72
|
+
- Shortcuts: `G.setting(key, tex, name, desc="", *, value=None, code=(), was=())` (point `code` at the config
|
|
73
|
+
field, e.g. `"mypkg.config:Settings.alpha"`) and `G.constant(key, tex, name, desc="", *, value, code=(), was=())`.
|
|
74
|
+
|
|
75
|
+
**Steps** (ops) turn input symbols into output symbols:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
G.op(key, label, tex="", desc="", *, inputs=(), outputs=(), code=(), group="", per=None, bind=None, where=None,
|
|
79
|
+
was=())
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `tex`: the equation, as the code computes it (not as a paper states it, if they differ). Display TeX, KaTeX
|
|
83
|
+
dialect (`\begin{aligned}` works).
|
|
84
|
+
- `desc`: one or two sentences: what the step does and why it is there. `$...$` in any description is TeX.
|
|
85
|
+
- `inputs`, `outputs`: symbols (objects or keys). Each symbol is produced by at most one step.
|
|
86
|
+
- `code`: one reference or a list (see "Code references").
|
|
87
|
+
- `group`: a path, `"round 1/model"`; groups nest and collapse in the viewer. `G.group(path, label=None, desc="",
|
|
88
|
+
*, collapsed=False, was=())` gives one a label, a description or a collapsed start; it is optional.
|
|
89
|
+
- `per`: the step is done once per value of this index (drawn as stacked cards); its per-value outputs are
|
|
90
|
+
symbols with the same `per`.
|
|
91
|
+
- `where`: `{TeX: definition}` for symbols the equation uses that are not values of the graph (a step's own
|
|
92
|
+
intermediates, functions like σ). Shown under the equation, as a paper would. A value two steps share should be
|
|
93
|
+
a symbol listed in both instead.
|
|
94
|
+
- `bind`: for the shape trace, `{code name: symbol}`, naming the parameters of the function in the step's first
|
|
95
|
+
code reference, and `"return"`, `"return.0"` (a tuple's first item) or `"return.attr"` (an attribute of the
|
|
96
|
+
result). Optional; see "Shape trace" for which steps can be bound.
|
|
97
|
+
|
|
98
|
+
**Equation checks**: a function that computes the equation as written, plainly (numpy on small made-up data),
|
|
99
|
+
and asserts the code gives the same. Registered for one or more steps:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
@G.verify("center", "score")
|
|
103
|
+
def as_written() -> None:
|
|
104
|
+
X = np.random.default_rng(0).normal(size=(6, 4))
|
|
105
|
+
expected = (X - X.mean(0)).sum(1) # the equations, written out
|
|
106
|
+
assert np.allclose(mypkg.core.score(X), expected) # the code
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A step whose check passes is marked ✓ in the viewer; one that fails, ✗ (and `headway check` fails).
|
|
110
|
+
|
|
111
|
+
**Shape trace**, in your tests: run the method on small data with the bound functions wrapped; every call's
|
|
112
|
+
bound values must have as many axes as their symbols have `dims`, and each index one size (within a call, and
|
|
113
|
+
across calls unless the index `varies`). How it works, and so what to bind:
|
|
114
|
+
|
|
115
|
+
- The trace wraps the function named by a bound step's **first** code reference (an anchor is ignored: the whole
|
|
116
|
+
function is wrapped). So bind a step only when its first reference is a function that computes that step. A
|
|
117
|
+
step whose code is a block inside a larger function cannot be traced; leave it unbound. Unbound steps are not
|
|
118
|
+
traced and nothing warns.
|
|
119
|
+
- A function several steps bind (the same helper used in two places): each call is given to a step whose bind
|
|
120
|
+
fits it, in order. A function called on subsets of the rows (inside cross-validation folds, say) sees an index
|
|
121
|
+
at several sizes: declare that index `varies=True`, or leave that step unbound if the looser check is not worth it.
|
|
122
|
+
- Every bound step must be called at least once by the traced run.
|
|
123
|
+
|
|
124
|
+
The test file, in full:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
import headway
|
|
128
|
+
import mypkg
|
|
129
|
+
from mypkg import graphspec
|
|
130
|
+
|
|
131
|
+
def test_spec_matches_code():
|
|
132
|
+
report = headway.check(graphspec.G)
|
|
133
|
+
assert report.ok, report.text()
|
|
134
|
+
|
|
135
|
+
def test_shapes():
|
|
136
|
+
with headway.trace(graphspec.G) as t:
|
|
137
|
+
mypkg.run(small_data()) # the method's entry point, on a few rows
|
|
138
|
+
t.assert_ok()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Code references
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
"pkg.mod:func" the whole function (or class, method or property: "pkg.mod:Class.method")
|
|
145
|
+
"pkg.mod:Class.field" one line of a class body (a dataclass field)
|
|
146
|
+
"pkg.mod:CONSTANT" a module-level assignment, the whole statement
|
|
147
|
+
"pkg.mod:func#text" from the line holding `text` to the line before the next comment line at the
|
|
148
|
+
same or a smaller indentation, or to the end of the enclosing block if there
|
|
149
|
+
is none (so "#step 2" takes the block under "# step 2: ...")
|
|
150
|
+
"pkg.mod:func#from text..to text" from the line holding `from text` to the next line holding `to text`, inclusive
|
|
151
|
+
"pkg.mod:CONSTANT#text..text" the same, within a module-level statement (one entry of a dict)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The anchor text must occur on exactly one line. Anchor on something that would change if the step changed: for a
|
|
155
|
+
constant, the literal value (`"pkg.mod:fit#np.clip(p, 1e-3..np.clip(p, 1e-3"`), so changing it in the code
|
|
156
|
+
fails the check until the spec is updated.
|
|
157
|
+
|
|
158
|
+
## What `headway check` reports
|
|
159
|
+
|
|
160
|
+
Errors (exit 1): a reference that does not resolve; an undeclared symbol or index; a symbol produced by two steps;
|
|
161
|
+
a step producing an input, setting or constant; a value or output no step produces; a cycle; a `where` entry
|
|
162
|
+
whose TeX is exactly a declared symbol's TeX (list the symbol instead; the comparison is of whole strings, so an
|
|
163
|
+
entry defining several symbols at once, `"n_1, n_0"`, is fine); a failed `@verify`; a `was` naming a key the spec
|
|
164
|
+
still declares; and, in a suite, the errors listed under "Benchmarks". Warnings: a symbol no step uses (outputs
|
|
165
|
+
excepted), a step with no code reference, a constant with no code reference, a group with no steps.
|
|
166
|
+
|
|
167
|
+
Not checked: that the graph lists all the data flow there is, and that a step without a `@verify` matches its
|
|
168
|
+
code. Say in a step's description when its equation is a simplification of the code.
|
|
169
|
+
|
|
170
|
+
## Writing a headway for a codebase: the procedure
|
|
171
|
+
|
|
172
|
+
1. **Read the method end to end.** Find the entry point and follow the data from the inputs to the outputs. Read
|
|
173
|
+
the docs and the code; where they disagree, the code is the ground truth, and the spec describes the code.
|
|
174
|
+
2. **Choose the steps.** One step per meaningful mathematical operation: usually a function, or a commented block
|
|
175
|
+
inside a long one. Glue (I/O, logging, reshaping) is not a step unless it changes the meaning of the data. A
|
|
176
|
+
method of moderate size has 8–40 steps; group them by stage (`"preprocessing"`, `"model"`, `"round 1/fit"`).
|
|
177
|
+
3. **Declare the indices and the symbols**, with notation that follows the project's paper or docs if there is
|
|
178
|
+
one. One meaning per symbol across the spec; the same operation written the same way everywhere. Every setting
|
|
179
|
+
points to its config field; every number fixed in the code that matters is a constant pointing to its line.
|
|
180
|
+
4. **Declare each step**: its equation as the code computes it, a description of what it does and why, its inputs
|
|
181
|
+
and outputs, its code references, `where` for its local symbols, and `bind` if you will trace shapes.
|
|
182
|
+
5. **Write `@verify` checks** for the steps that carry the math (at least the ones whose equations someone will
|
|
183
|
+
rely on). Small random data; the equation written out plainly; compared with the code's function.
|
|
184
|
+
6. **Run `headway check pkg.graphspec`** until there are no errors, and fix the warnings or know why they stay.
|
|
185
|
+
7. **Add the test** (`headway.check(G).ok` and a shape trace) so the spec breaks loudly when the code changes.
|
|
186
|
+
8. **Build and look**: `headway build pkg.graphspec -o headway.html`, or `headway serve pkg.graphspec --open` to
|
|
187
|
+
rebuild live while editing. Read the graph as a newcomer would: does every step say what it is for?
|
|
188
|
+
|
|
189
|
+
When the code changes later, rerun `headway check`; it names what went stale. Update the spec in the same change
|
|
190
|
+
as the code.
|
|
191
|
+
|
|
192
|
+
## Versions: seeing what changed
|
|
193
|
+
|
|
194
|
+
A **version** is a built headway: the JSON from `headway build --json`, or the HTML page itself. Keep one wherever
|
|
195
|
+
it is useful (each commit you care about, each experiment, each competing method), with `--name` to label it.
|
|
196
|
+
headway records the spec's file, the build time, and, in a git repository, the commit, branch and whether the work
|
|
197
|
+
tree had changes.
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
headway build pkg.graphspec --name v5 -o headways/v5.html --json headways/v5.json \
|
|
201
|
+
--compare headways/v3.json --compare headways/v4.json # v3 and v4 in lanes beside v5
|
|
202
|
+
headway diff headways/v4.json headways/v5.json # the same comparison, as text
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
In the viewer, **Compare…** opens more versions. A version built without `--name` is called by its file name; lanes are ordered by build time and can be dragged, closed or
|
|
206
|
+
pinned as the baseline. Markers: **+** added, **Δ** changed, **−** gone, **⋔** split or merged. **One at a time**
|
|
207
|
+
shows one version, keeping the others open.
|
|
208
|
+
|
|
209
|
+
Objects correspond across versions by key. When you rename a key, say so in the new version with `was=`:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
G.op("weighted_score", "module score", ..., was="score") # renamed
|
|
213
|
+
G.op("sizefactor", ..., was="normalize") # split: each part names
|
|
214
|
+
G.op("scale_counts", ..., was="normalize") # the step it came from
|
|
215
|
+
G.symbol("S", ..., was=["score", "cell_score"]) # renamed twice, or two merged into one
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
What counts as a change: inputs, outputs, group, dimensions or role (structure); the equation text; the code the
|
|
219
|
+
references resolve to (so a code change under an unchanged equation shows); a setting's value; the check result;
|
|
220
|
+
and labels and descriptions (shown only with "wording" / `--wording`).
|
|
221
|
+
|
|
222
|
+
## Benchmarks
|
|
223
|
+
|
|
224
|
+
A benchmark suite is a `headway.Suite`, which is a Headway: you declare it the same way, and it adds datasets,
|
|
225
|
+
metrics and objectives.
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from headway import Suite
|
|
229
|
+
B = Suite("my benchmark", "What it measures.", version="1")
|
|
230
|
+
i = B.index("i", "i", "cell")
|
|
231
|
+
S = B.symbol("S", "S_i", "score", "The method's output.", dims=(i,), role="input")
|
|
232
|
+
y = B.symbol("y", "y_i", "truth", dims=(i,), role="input")
|
|
233
|
+
|
|
234
|
+
B.metric("auroc", r"\mathrm{AUROC}", "separation", "How often a positive scores above a negative.",
|
|
235
|
+
equation=r"\mathrm{AUROC} = \frac{1}{n_1 n_0} \sum_{i: y_i=1} \sum_{j: y_j=0} [S_i > S_j]",
|
|
236
|
+
inputs=[S, y], better="higher", range=(0, 1), code="mybench.metrics:auroc",
|
|
237
|
+
where={"n_1, n_0": "the numbers of positives and negatives"}, source="Mann-Whitney U, scaled")
|
|
238
|
+
|
|
239
|
+
B.dataset("pbmc", "PBMC", "Blood cells with sorted labels.", source="GEO GSE123456",
|
|
240
|
+
facts={"cells": "12,000", "platform": "10x v3"}, code="mybench.data:load_pbmc")
|
|
241
|
+
B.dataset("check", "held-out mix", "Not looked at during development.", heldout=True,
|
|
242
|
+
code="mybench.data:load_check")
|
|
243
|
+
|
|
244
|
+
B.objective("overall", "overall", "Separation counts twice.", metrics={"auroc": 2, "stability": 1})
|
|
245
|
+
B.objective("rank", "mean rank", metrics=["auroc", "stability"], how="rank")
|
|
246
|
+
|
|
247
|
+
@B.verify("auroc")
|
|
248
|
+
def auroc_as_written(): ...
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**How a suite meets the method.** A suite describes the benchmark, not the method. Its `role="input"` symbols are
|
|
252
|
+
what the method hands to the metrics (its outputs: scores, predictions, coefficients); write them with the same
|
|
253
|
+
notation as the method's spec, since a reader goes between the two (nothing links them automatically). If the
|
|
254
|
+
suite also makes the data the method runs on, declare a step for the method itself in the suite: inputs the data,
|
|
255
|
+
outputs the method's results, `code` pointing to the method's entry function, with a description saying it is
|
|
256
|
+
the method being benchmarked. The graph then reads data → method → metrics, and no data symbol is left unused.
|
|
257
|
+
|
|
258
|
+
- **`B.metric(key, tex, name, desc="", *, equation="", inputs=(), better, range=None, unit="", source="",
|
|
259
|
+
code=(), where=None, group="metrics", was=())`** declares the output symbol `key` and the step `key` that
|
|
260
|
+
computes it, so a metric has a notation, an equation, code and a check like any step. `better` is `"higher"` or
|
|
261
|
+
`"lower"`; `range` is its possible extent (needed by `how="mean"` objectives). For a metric with no natural
|
|
262
|
+
bound (a mean squared error), either use a bounded version of it (R², or the error relative to a baseline's) or
|
|
263
|
+
leave out `range` and combine it only in `how="rank"` objectives; a guessed range warns when values fall outside.
|
|
264
|
+
- **`B.dataset(key, name, desc="", *, source="", heldout=False, values=None, facts=None, code=())`**. If the suite
|
|
265
|
+
makes its data (a simulation, a preprocessing), declare those steps in the suite like any other (with their
|
|
266
|
+
settings, equations and checks) and give each dataset its `values` of those settings: the steps are written
|
|
267
|
+
once and the datasets differ in their values. `values` may only name declared settings. Keep one copy of the
|
|
268
|
+
values: either the benchmark code holds them and the suite reads them (`values=data.DATASETS["pbmc"]`), or the
|
|
269
|
+
suite holds them and the benchmark code reads them back (`B.datasets["pbmc"].values`, a dict). Replicates (seeds,
|
|
270
|
+
folds) are not dataset values; record them in the results' `run`. `facts` is anything
|
|
271
|
+
else worth knowing; `source`, where the data come from; `code`, where they are made or loaded. A held-out
|
|
272
|
+
dataset is hidden in the viewer unless asked for.
|
|
273
|
+
- **`B.objective(key, name, desc="", *, metrics, how="mean", datasets=None, tex="")`** combines metrics into one
|
|
274
|
+
number, kept apart from the metrics. `metrics` is `{metric: weight}` or a list (weight 1). `how="mean"`: each
|
|
275
|
+
metric's mean scaled to 0..1 by its range, turned so 1 is best, weighted and averaged over the datasets
|
|
276
|
+
(absolute). `how="rank"`: the weighted mean rank among the versions shown, 1 best (relative). `datasets`:
|
|
277
|
+
default every dataset not held out. A version missing any value an objective needs gets no score ("incomplete").
|
|
278
|
+
|
|
279
|
+
Suite errors: an empty range, an objective naming an undeclared metric or dataset, a `how="mean"` objective over a
|
|
280
|
+
metric without a range, a weight that is not positive, a dataset value for anything but a declared setting.
|
|
281
|
+
|
|
282
|
+
### Results
|
|
283
|
+
|
|
284
|
+
headway computes no metrics. Your benchmark runs the method, computes the metrics, and hands headway the values:
|
|
285
|
+
one per replicate (seed, fold), for each dataset and metric it ran. Missing combinations are fine (shown hatched).
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{"suite": "mybench.suite",
|
|
289
|
+
"values": {"pbmc": {"auroc": [0.91, 0.90, 0.93], "stability": [0.8, 0.82, 0.79]}},
|
|
290
|
+
"run": {"seeds": [0, 1, 2], "date": "2026-10-27"}}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
`suite` names the module holding the Suite (or `"module:attribute"`); in Python it can be the Suite itself. `run`
|
|
294
|
+
is anything worth keeping. Results are stored in the version's headway together with the built suite; versions
|
|
295
|
+
are compared on results with the same suite title and version, so bump `version` when a change to the suite makes
|
|
296
|
+
old results incomparable. Values for a dataset or metric the suite does not declare are left out with a warning.
|
|
297
|
+
|
|
298
|
+
From the command line:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
headway build pkg.graphspec --name v5 --results results/v5.json -o headways/v5.html --json headways/v5.json
|
|
302
|
+
headway attach headways/v4.json results/v4.json # add results to a built version (JSON or page)
|
|
303
|
+
headway results headways/v*.json # the table, as text
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
From Python, in the codebase's own benchmark script (the usual way: run, then build, in one go):
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
import headway
|
|
310
|
+
from mybench import suite, metrics, data
|
|
311
|
+
|
|
312
|
+
values = {}
|
|
313
|
+
for name in ("pbmc", "check"):
|
|
314
|
+
for seed in range(3):
|
|
315
|
+
d = data.load(name, seed)
|
|
316
|
+
s = mypkg.score(d.counts)
|
|
317
|
+
values.setdefault(name, {}).setdefault("auroc", []).append(metrics.auroc(s, d.truth))
|
|
318
|
+
|
|
319
|
+
headway.build("mypkg.graphspec", "headways/v5.html", json="headways/v5.json", name="v5",
|
|
320
|
+
results=[{"suite": suite.B, "values": values, "run": {"seeds": [0, 1, 2]}}],
|
|
321
|
+
compare=["headways/v4.json"])
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
In the viewer, **Graph | Results** switches to the heatmap of the open versions: objectives first, then metric ×
|
|
325
|
+
dataset columns (group by metric or by dataset), each cell the mean ± s.d. of the replicates and its difference
|
|
326
|
+
from the version it is compared with. Click a column for the metric's definition and every version's replicates;
|
|
327
|
+
click a dataset for what it is; click a version for what changed in it.
|
|
328
|
+
|
|
329
|
+
## Command line
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
headway check SPEC [--no-verify] exit 1 if the spec and the code disagree
|
|
333
|
+
headway build SPEC [-o headway.html] [--json FILE] [--name NAME] [--results FILE ...] [--compare [NAME=]FILE ...]
|
|
334
|
+
[--collapse GROUP ...] [--hide GROUP ...] [--no-verify] [--offline] [--editor EDITOR]
|
|
335
|
+
headway serve SPEC [--port 8765] [--open] [--compare ...] [--collapse ...] [--hide ...] [--editor EDITOR]
|
|
336
|
+
headway diff VERSION VERSION ... [--wording] a VERSION is a built file, or a spec module (built as it is now)
|
|
337
|
+
headway attach VERSION RESULTS.json [-o OUT]
|
|
338
|
+
headway results VERSION VERSION ... [--heldout]
|
|
339
|
+
headway guide
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
SPEC is an importable module holding one Headway (or `module:attribute`); run from a directory where it imports.
|
|
343
|
+
`--offline` writes the page's libraries into it (about 3 MB) so it opens without internet. `--editor` (or
|
|
344
|
+
`$HEADWAY_EDITOR`): `vscode` (default), `cursor`, `pycharm`, `file`, or a URL template with `{file}`, `{line}`,
|
|
345
|
+
`{col}`. `serve` rebuilds when the spec or any file its references point to changes, and the open page keeps its
|
|
346
|
+
view.
|
|
347
|
+
|
|
348
|
+
## Python API
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
import headway
|
|
352
|
+
headway.Headway, headway.Suite # declaring
|
|
353
|
+
headway.check(G) -> Report # .ok, .errors, .warnings, .text()
|
|
354
|
+
headway.trace(G) # context manager; .assert_ok()
|
|
355
|
+
headway.build(spec, out="headway.html", *, json=None, name=None, results=(), compare=(), collapse=(), hide=(),
|
|
356
|
+
verify=True, offline=False, editor=None, strict=False, log=None) -> dict
|
|
357
|
+
headway.attach(path, results, out=None, log=None) -> dict
|
|
358
|
+
headway.read_results(results, log=None) -> dict # a results dict or file -> the entry stored in a headway
|
|
359
|
+
headway.load("pkg.mod[:attr]") -> Headway
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
`spec` is a module name or a Headway object; `out` or `json` may be None to skip that file; `results` items are
|
|
363
|
+
results dicts or paths to results files; `strict=True` raises if the check finds errors (otherwise they are in
|
|
364
|
+
the page); `log` receives progress messages (e.g. `log=print`).
|
|
365
|
+
|
|
366
|
+
## Rules of thumb
|
|
367
|
+
|
|
368
|
+
- The spec describes the code as it is. If the code and a paper differ, write the code's equation and say so in
|
|
369
|
+
the description.
|
|
370
|
+
- Keep the spec and the code in the same change; run `headway check` in the tests.
|
|
371
|
+
- Prefer a symbol over a `where` entry when a value is used by more than one step.
|
|
372
|
+
- Name things for a reader new to the method: labels are short and plain ("size factors", not "sf"); descriptions
|
|
373
|
+
say what and why, not how the code is organized.
|
|
374
|
+
- Do not compute metrics, or anything else, in headway; it shows precomputed results against the methods.
|
|
375
|
+
- Do not make headway parse TeX; equations are checked only by `@verify` functions.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""headway: see how a method works, and how much each change to it gains. A spec module declares the method's
|
|
2
|
+
steps (equations, inputs, outputs, references into the code); headway checks it against the code and draws it as
|
|
3
|
+
an interactive computation graph, compares versions of it, and shows their benchmark results.
|
|
4
|
+
`headway guide` (or GUIDE.md in this package) describes it all."""
|
|
5
|
+
from headway.api import attach, build, load, read_results
|
|
6
|
+
from headway.bench import Suite
|
|
7
|
+
from headway.check import Report, check
|
|
8
|
+
from headway.spec import Group, Headway, Index, Op, Symbol
|
|
9
|
+
from headway.trace import Trace, trace
|
|
10
|
+
|
|
11
|
+
__all__ = ["Group", "Headway", "Index", "Op", "Report", "Suite", "Symbol", "Trace", "attach", "build", "check", "load",
|
|
12
|
+
"read_results", "trace"]
|