visiter 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.
- visiter-0.1.0/.claude/settings.json +35 -0
- visiter-0.1.0/.gitignore +14 -0
- visiter-0.1.0/CLAUDE.md +218 -0
- visiter-0.1.0/LICENSE +21 -0
- visiter-0.1.0/Makefile +46 -0
- visiter-0.1.0/PKG-INFO +140 -0
- visiter-0.1.0/README.md +111 -0
- visiter-0.1.0/demos/01_one_data_many_views.sh +31 -0
- visiter-0.1.0/demos/02_pipeline_to_pdf.sh +25 -0
- visiter-0.1.0/demos/03_validate_in_pipeline.sh +21 -0
- visiter-0.1.0/demos/04_string_iteration.sh +18 -0
- visiter-0.1.0/demos/README.md +23 -0
- visiter-0.1.0/demos/data/descent.expr +3 -0
- visiter-0.1.0/docs/manual.md +474 -0
- visiter-0.1.0/docs/tutorial.md +226 -0
- visiter-0.1.0/pyproject.toml +50 -0
- visiter-0.1.0/schemas/v1/graph.schema.json +82 -0
- visiter-0.1.0/src/visiter/__init__.py +57 -0
- visiter-0.1.0/src/visiter/cli.py +45 -0
- visiter-0.1.0/src/visiter/iteration.py +251 -0
- visiter-0.1.0/src/visiter/render_helpers.py +309 -0
- visiter-0.1.0/src/visiter/to_dot.py +215 -0
- visiter-0.1.0/src/visiter/validate.py +81 -0
- visiter-0.1.0/tests/__init__.py +0 -0
- visiter-0.1.0/tests/test_demos.py +57 -0
- visiter-0.1.0/tests/test_iteration.py +101 -0
- visiter-0.1.0/tests/test_schema.py +79 -0
- visiter-0.1.0/tests/test_to_dot.py +187 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(*)",
|
|
5
|
+
"Edit(*)",
|
|
6
|
+
"Write(*)",
|
|
7
|
+
"Read(*)",
|
|
8
|
+
"Glob(*)",
|
|
9
|
+
"Grep(*)",
|
|
10
|
+
"Agent(*)",
|
|
11
|
+
"NotebookEdit(*)",
|
|
12
|
+
"WebFetch(*)",
|
|
13
|
+
"WebSearch(*)",
|
|
14
|
+
"TodoWrite(*)"
|
|
15
|
+
],
|
|
16
|
+
"deny": [
|
|
17
|
+
"Bash(git push*)"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"hooks": {
|
|
21
|
+
"PreToolUse": [
|
|
22
|
+
{
|
|
23
|
+
"matcher": "Bash",
|
|
24
|
+
"hooks": [
|
|
25
|
+
{
|
|
26
|
+
"type": "command",
|
|
27
|
+
"if": "Bash(git commit *)",
|
|
28
|
+
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PreToolUse\", \"permissionDecision\": \"ask\", \"permissionDecisionReason\": \"git commit requires user approval\"}}'",
|
|
29
|
+
"statusMessage": "Checking git commit..."
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
visiter-0.1.0/.gitignore
ADDED
visiter-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# CLAUDE.md — VisIter project notes for Claude Code
|
|
2
|
+
|
|
3
|
+
This file collects project-specific conventions, design decisions, and
|
|
4
|
+
workflow rules that were agreed on during the initial extraction of
|
|
5
|
+
VisIter from the `_math` research project. Read this at the start of
|
|
6
|
+
every session before making non-trivial changes.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Project purpose
|
|
11
|
+
|
|
12
|
+
VisIter provides two composable pieces:
|
|
13
|
+
|
|
14
|
+
- **`iterate`** — builds an iteration graph from guard-and-op Rules via
|
|
15
|
+
BFS, records per-node depth, and emits pseudo-edges for structural
|
|
16
|
+
boundaries (`Rule.bound`, `max_depth`).
|
|
17
|
+
- **`to_dot`** — converts the graph dict into a `graphviz.Digraph` with
|
|
18
|
+
cropping, coloring, ghost stubs, and node annotations.
|
|
19
|
+
|
|
20
|
+
They are connected only by a documented dict shape (see
|
|
21
|
+
`docs/manual.md` §6). Either can be used without the other.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 2. Design decisions already made
|
|
26
|
+
|
|
27
|
+
These are the results of deliberate earlier discussions; do not
|
|
28
|
+
re-litigate without a clear reason.
|
|
29
|
+
|
|
30
|
+
### API shape
|
|
31
|
+
|
|
32
|
+
- `iterate(start, rules, *, default, ...)` — `default` is required
|
|
33
|
+
(keyword-only, no Python default value) to force the caller to make
|
|
34
|
+
the "no rule matched" case explicit. Pass `None` for "that value is
|
|
35
|
+
a leaf"; pass an `Op` for the else branch.
|
|
36
|
+
- `Rule(condition, op, bound=None)` — three-field namedtuple. The
|
|
37
|
+
`condition` asks "is this op applicable at all?" (mathematical
|
|
38
|
+
predicate). The optional `bound` asks "should we stop here anyway?"
|
|
39
|
+
(structural cutoff). When `condition` is True but `bound` is False,
|
|
40
|
+
a pseudo-edge is recorded and the renderer draws a ghost stub.
|
|
41
|
+
- `Op(func, label)` — the label is the identity key for color
|
|
42
|
+
assignment, ordering, and op_colors lookup. Two Op instances with
|
|
43
|
+
the same label are treated as the same op.
|
|
44
|
+
- iterate uses **BFS** (not DFS) so each node's recorded `depth` is
|
|
45
|
+
the minimum hop count from the nearest start.
|
|
46
|
+
- `on_limit` governs `max_nodes` and `time_limit` ("raise" or "stop").
|
|
47
|
+
`max_depth` is always a soft topological stop; it does not raise.
|
|
48
|
+
- The graph dict is the single data contract. `op_order` is part of it
|
|
49
|
+
and drives palette assignment; it follows rule-declaration order
|
|
50
|
+
(not traversal-first-seen). Preserve `op_order` across any
|
|
51
|
+
transformation that rewrites the graph (e.g. cropping in `to_dot`).
|
|
52
|
+
|
|
53
|
+
### Rendering model
|
|
54
|
+
|
|
55
|
+
- **Two-layer palette**: each palette slot is a `(fill, edge)` pair.
|
|
56
|
+
Light pastel for node fills (readable labels), saturated mid-tone
|
|
57
|
+
for edges (visible thin lines). The caller can override via
|
|
58
|
+
`op_colors` with a single hex (applies to both) or a tuple (fill,
|
|
59
|
+
edge).
|
|
60
|
+
- **Highlight** (the `"highlight"` tag): darkens fills in **HSL
|
|
61
|
+
space** (lightness-only reduction, hue+saturation preserved), not
|
|
62
|
+
RGB-toward-black. RGB-toward-black desaturates; HSL preserves the
|
|
63
|
+
color identity. Default factor 0.45.
|
|
64
|
+
- **Node fill comes from outgoing op labels**, not from node identity.
|
|
65
|
+
0 out-ops → no fill (leaf, default white). 1 out-op → solid fill.
|
|
66
|
+
N ≥ 2 → `style="wedged"` with colon-joined colors (pie segments).
|
|
67
|
+
- **Roots** are marked with `penwidth="3"` (bold border). Not with
|
|
68
|
+
`peripheries`, not with a distinct fill.
|
|
69
|
+
- **Ghost stubs** appear for three reasons, all rendered the same:
|
|
70
|
+
(a) outgoing cut (kept_src → outside_dst filtered),
|
|
71
|
+
(b) incoming cut (outside_src → kept_dst filtered),
|
|
72
|
+
(c) pseudo-edge (iterate recorded Rule.bound=False or max_depth).
|
|
73
|
+
All three fold into one `cut_edges` list with a `kept_is_src` flag.
|
|
74
|
+
Only case (a) and (c) contribute to `extra_out_ops` (fill lives on
|
|
75
|
+
outgoing edges only — incoming cuts don't need a fill counterpart).
|
|
76
|
+
|
|
77
|
+
### CLI shape
|
|
78
|
+
|
|
79
|
+
- **One** entry point `visiter` with subcommands. Subcommands are
|
|
80
|
+
registered in `src/visiter/cli.py::SUBCOMMANDS`. Adding a new
|
|
81
|
+
subcommand means writing a module with a `main()` function and
|
|
82
|
+
adding one entry there.
|
|
83
|
+
- **Each subcommand takes a single positional Python expression** as
|
|
84
|
+
its "argstring", spliced into the corresponding function call and
|
|
85
|
+
eval'd in a namespace where `Op`, `Rule`, `iterate`, `to_dot`, and
|
|
86
|
+
`graph` (for to-dot) are pre-bound. This deliberately avoids a
|
|
87
|
+
per-flag DSL — any Python-API kwarg is expressible on the shell,
|
|
88
|
+
and future API additions cost nothing in CLI glue.
|
|
89
|
+
- **Pipe composition is the idiom**: `visiter iterate '...' | visiter
|
|
90
|
+
to-dot '...' | dot -Tsvg > out.svg`. Input/output conventions are
|
|
91
|
+
stdin/stdout with `--input FILE` / `-o FILE` overrides.
|
|
92
|
+
- `eval` is appropriate: this is a local research tool, not a
|
|
93
|
+
network-exposed service. Errors propagate as normal Python
|
|
94
|
+
exceptions; no parser front-end to misdiagnose input.
|
|
95
|
+
|
|
96
|
+
### Why subcommands (not separate console scripts)
|
|
97
|
+
|
|
98
|
+
We explicitly chose the subcommand layout over multiple separate
|
|
99
|
+
`visiter-X` scripts because we expect more commands to be added
|
|
100
|
+
(e.g. `analyze` for jq-style queries over the graph JSON, `stats` for
|
|
101
|
+
depth-distribution/fan-out summaries, `diff` for comparing two
|
|
102
|
+
iteration graphs). One stable command name, `visiter --help` as a
|
|
103
|
+
single discovery point, and a minimal dispatcher makes that growth
|
|
104
|
+
cheap.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 3. Workflow rules
|
|
109
|
+
|
|
110
|
+
### Commits
|
|
111
|
+
|
|
112
|
+
- **Use conventional-commit prefixes** (`feat:`, `fix:`, `refactor:`,
|
|
113
|
+
`docs:`, `chore:`, `test:`). Match the ones already in the log.
|
|
114
|
+
- **Never add `Co-Authored-By` trailers.** Not here, not anywhere.
|
|
115
|
+
- **Always propose the commit message first and wait for user
|
|
116
|
+
approval** before executing the commit. The user says "ja" / "okay"
|
|
117
|
+
or asks for changes.
|
|
118
|
+
- **Message describes net diff, not session steps.** A mid-session
|
|
119
|
+
dead-end doesn't belong in the message.
|
|
120
|
+
- **Before every `git commit`, run `git status`** and cross-check
|
|
121
|
+
that the staged set matches the work. Nothing relevant should be
|
|
122
|
+
left Untracked or unstaged. Files referenced by the commit message
|
|
123
|
+
must actually be in the commit.
|
|
124
|
+
- **Don't ask "commit or review first?"** — the user reviews before
|
|
125
|
+
saying "commit". The question is a false dichotomy.
|
|
126
|
+
- **Never `git push`** without explicit user request. (Enforced by
|
|
127
|
+
the deny rule in `.claude/settings.json`.)
|
|
128
|
+
|
|
129
|
+
### Verification
|
|
130
|
+
|
|
131
|
+
- **Always verify.** `make test` after any logic change. `make build`
|
|
132
|
+
after any packaging change. For CLI changes, at least one
|
|
133
|
+
end-to-end pipe-through-dot smoke test.
|
|
134
|
+
- **Don't report success without confirming.** Build output, test
|
|
135
|
+
status, or rendered artefact is what confirms; "I think it works"
|
|
136
|
+
is not.
|
|
137
|
+
|
|
138
|
+
### Testing
|
|
139
|
+
|
|
140
|
+
- Tests live in `tests/`. They use only the public package API
|
|
141
|
+
(`from visiter import …`), not internal paths, so we'd notice if
|
|
142
|
+
re-exports drift.
|
|
143
|
+
- New features: add at least one test exercising the happy path and
|
|
144
|
+
one exercising a limit/error case (e.g. `max_nodes` raise vs stop).
|
|
145
|
+
- When a bug is found, the repro-first rule applies: write a failing
|
|
146
|
+
test, confirm it fails, then fix, then confirm it passes.
|
|
147
|
+
|
|
148
|
+
### Dependencies
|
|
149
|
+
|
|
150
|
+
- **Never `pip install` directly**. Add to `pyproject.toml` (core
|
|
151
|
+
deps or `[project.optional-dependencies].dev`), then `make setup`.
|
|
152
|
+
- Keep the dep list minimal. Currently `graphviz` and `sympy`;
|
|
153
|
+
anything new needs a clear justification.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 4. Open topics (not decided, worth revisiting)
|
|
158
|
+
|
|
159
|
+
### Versioning of the eval-based CLI
|
|
160
|
+
|
|
161
|
+
The CLI eval contract means any shell script that embeds an argstring
|
|
162
|
+
is effectively coupled to the **implementation version** of the
|
|
163
|
+
called function — its parameter names, `Rule`/`Op` fields, default
|
|
164
|
+
behavior, etc. If we change a parameter name or add a required field
|
|
165
|
+
to `Rule`, downstream shell scripts break silently-until-executed.
|
|
166
|
+
|
|
167
|
+
Unresolved:
|
|
168
|
+
|
|
169
|
+
- Should we document a stability guarantee (semver-style, where
|
|
170
|
+
parameter-name / signature breakage requires a major bump)?
|
|
171
|
+
- Should the CLI check some form of `visiter.__version__` in the
|
|
172
|
+
argstring context, so scripts can assert compatibility?
|
|
173
|
+
- Is there a less-coupled interface worth exposing alongside (e.g. a
|
|
174
|
+
JSON-in / JSON-out config for the common cases) without losing the
|
|
175
|
+
expressivity of eval?
|
|
176
|
+
|
|
177
|
+
This should be addressed before `v1.0`.
|
|
178
|
+
|
|
179
|
+
### Other candidates for future work
|
|
180
|
+
|
|
181
|
+
- `visiter analyze` subcommand: jq-style or xpath-like queries over
|
|
182
|
+
the graph JSON (e.g. "show all nodes with depth > 5 and fan-out >
|
|
183
|
+
1").
|
|
184
|
+
- `visiter stats`: depth distribution, op frequency, average fan-out,
|
|
185
|
+
cycle detection summary.
|
|
186
|
+
- `visiter diff`: compare two graphs structurally (added/removed
|
|
187
|
+
nodes/edges, shifted depths).
|
|
188
|
+
- Depth-based rendering affordances (`to_dot` `depth_gradient=True`,
|
|
189
|
+
or per-depth op_colors override).
|
|
190
|
+
- A proper Graphviz-side timeout that's integrated rather than left
|
|
191
|
+
to callers (the `with_timeout` wrapper in the notebook).
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 5. Pointers
|
|
196
|
+
|
|
197
|
+
- Package source: `src/visiter/`
|
|
198
|
+
- Public API surface: `src/visiter/__init__.py`
|
|
199
|
+
- Subcommand registry: `src/visiter/cli.py`
|
|
200
|
+
- Tests: `tests/`
|
|
201
|
+
- User-facing docs: `README.md` (quickstart) and `docs/manual.md`
|
|
202
|
+
(full reference, including the graph-dict shape)
|
|
203
|
+
- Build / test / publish pipeline: `Makefile`
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 6. Things that are NOT conventions
|
|
208
|
+
|
|
209
|
+
Don't propagate from other codebases without re-litigating:
|
|
210
|
+
|
|
211
|
+
- No Python typing (`type: ignore`, `TYPE_CHECKING`, `typing.Protocol`) is
|
|
212
|
+
used pervasively yet. If you start adding annotations, do it
|
|
213
|
+
consistently and run mypy — don't sprinkle them.
|
|
214
|
+
- No linter/formatter is configured yet. Don't reformat existing code
|
|
215
|
+
on unrelated changes.
|
|
216
|
+
- No GitHub Actions, no pre-commit hooks, no automated release
|
|
217
|
+
pipeline. All of these are reasonable next steps but should be
|
|
218
|
+
introduced deliberately, not opportunistically.
|
visiter-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jakob Stemberger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
visiter-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
VENV := .venv
|
|
2
|
+
VENV_BIN := $(VENV)/bin
|
|
3
|
+
VENV_STAMP := $(VENV)/.installed
|
|
4
|
+
|
|
5
|
+
.PHONY: setup test demo build publish test-publish clean help
|
|
6
|
+
|
|
7
|
+
help:
|
|
8
|
+
@echo "Targets:"
|
|
9
|
+
@echo " setup create venv and install package + dev deps"
|
|
10
|
+
@echo " test run pytest"
|
|
11
|
+
@echo " demo run all demos/*.sh (writes to demos/out/)"
|
|
12
|
+
@echo " build build sdist + wheel into dist/"
|
|
13
|
+
@echo " publish upload dist/ to PyPI (requires credentials)"
|
|
14
|
+
@echo " test-publish upload dist/ to TestPyPI"
|
|
15
|
+
@echo " clean remove build artefacts"
|
|
16
|
+
|
|
17
|
+
setup: $(VENV_STAMP)
|
|
18
|
+
|
|
19
|
+
$(VENV_STAMP): pyproject.toml
|
|
20
|
+
python3 -m venv $(VENV)
|
|
21
|
+
$(VENV_BIN)/pip install -U pip
|
|
22
|
+
$(VENV_BIN)/pip install -e ".[dev]"
|
|
23
|
+
@touch $@
|
|
24
|
+
|
|
25
|
+
test: setup
|
|
26
|
+
$(VENV_BIN)/pytest
|
|
27
|
+
|
|
28
|
+
demo: setup
|
|
29
|
+
@command -v dot >/dev/null || { echo "demos require 'dot' (Graphviz) on PATH"; exit 1; }
|
|
30
|
+
@for s in demos/*.sh; do \
|
|
31
|
+
echo "== $$s =="; \
|
|
32
|
+
PATH="$(CURDIR)/$(VENV_BIN):$$PATH" bash "$$s" || exit 1; \
|
|
33
|
+
done
|
|
34
|
+
@echo "all demos succeeded — see demos/out/"
|
|
35
|
+
|
|
36
|
+
build: setup clean
|
|
37
|
+
$(VENV_BIN)/python -m build
|
|
38
|
+
|
|
39
|
+
test-publish: build
|
|
40
|
+
$(VENV_BIN)/python -m twine upload --repository testpypi dist/*
|
|
41
|
+
|
|
42
|
+
publish: build
|
|
43
|
+
$(VENV_BIN)/python -m twine upload dist/*
|
|
44
|
+
|
|
45
|
+
clean:
|
|
46
|
+
rm -rf dist/ build/ src/visiter.egg-info src/*.egg-info
|
visiter-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: visiter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Build and visualize iteration graphs from rule-based state transitions.
|
|
5
|
+
Project-URL: Homepage, https://github.com/yaccob/visiter
|
|
6
|
+
Project-URL: Issues, https://github.com/yaccob/visiter/issues
|
|
7
|
+
Author-email: Jakob Stemberger <jakob.stemberger@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: bfs,graph,graphviz,iteration,state-transition,visualization
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Requires-Dist: graphviz>=0.20
|
|
20
|
+
Requires-Dist: sympy>=1.10
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: jsonschema>=4.18; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
25
|
+
Requires-Dist: twine>=4; extra == 'dev'
|
|
26
|
+
Provides-Extra: validate
|
|
27
|
+
Requires-Dist: jsonschema>=4.18; extra == 'validate'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# VisIter
|
|
31
|
+
|
|
32
|
+
Build and visualize iteration graphs from rule-based state transitions.
|
|
33
|
+
|
|
34
|
+
VisIter is a small library that does two complementary things:
|
|
35
|
+
|
|
36
|
+
1. **`iterate(start, rules, default=..., max_depth=..., ...)`** —
|
|
37
|
+
applies a list of guard-and-operation Rules to seed values via BFS,
|
|
38
|
+
producing a graph (nodes, edges, per-node depth, optional pseudo-edges
|
|
39
|
+
marking structural boundaries).
|
|
40
|
+
|
|
41
|
+
2. **`to_dot(graph, ...)`** — turns the graph into a Graphviz
|
|
42
|
+
Digraph for SVG/HTML/PDF rendering, with anchor/radius cropping,
|
|
43
|
+
per-rule edge coloring, wedged-pie node fills for branching nodes,
|
|
44
|
+
and dashed ghost stubs at every kind of cut boundary.
|
|
45
|
+
|
|
46
|
+
The two are independent: any graph dict that fits the documented shape
|
|
47
|
+
can be rendered, and `iterate` can be used purely for graph construction
|
|
48
|
+
without ever touching the renderer.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install visiter
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Graphviz must be available on `PATH` for image rendering (the Python
|
|
57
|
+
`graphviz` package wraps the system tool).
|
|
58
|
+
|
|
59
|
+
## Quickstart — descent with divisor rule
|
|
60
|
+
|
|
61
|
+
A rule that divides by three when applicable, with a `+2` fallback for
|
|
62
|
+
all other values. Every integer path eventually joins one of two small
|
|
63
|
+
cycles (`1 → 3 → 1`, `2 → 4 → 6 → 2`).
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from visiter import iterate, Op, Rule, to_dot
|
|
67
|
+
|
|
68
|
+
graph = iterate(
|
|
69
|
+
start=range(1, 30),
|
|
70
|
+
rules=[Rule(lambda x: x % 3 == 0, Op(lambda x: x // 3, "÷3"))],
|
|
71
|
+
default=Op(lambda x: x + 2, "+2"),
|
|
72
|
+
)
|
|
73
|
+
dot = to_dot(graph, anchor=1, radius=8, direction="backward")
|
|
74
|
+
dot.render("descent", format="svg")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Quickstart — reverse binary tree with bound
|
|
78
|
+
|
|
79
|
+
Generate every positive integer up to a ceiling as the binary tree of
|
|
80
|
+
`×2` / `×2+1` successors of 1. `Rule.bound` keeps the expansion inside
|
|
81
|
+
the ceiling; to_dot draws the frontier as dashed ghost stubs.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from visiter import iterate, Op, Rule, to_dot
|
|
85
|
+
|
|
86
|
+
ceiling = 64
|
|
87
|
+
|
|
88
|
+
graph = iterate(
|
|
89
|
+
start=[1],
|
|
90
|
+
rules=[
|
|
91
|
+
Rule(lambda x: True,
|
|
92
|
+
Op(lambda x: 2 * x, "×2"),
|
|
93
|
+
bound=lambda x: 2 * x <= ceiling),
|
|
94
|
+
Rule(lambda x: True,
|
|
95
|
+
Op(lambda x: 2 * x + 1, "×2+1"),
|
|
96
|
+
bound=lambda x: 2 * x + 1 <= ceiling),
|
|
97
|
+
],
|
|
98
|
+
default=None,
|
|
99
|
+
)
|
|
100
|
+
dot = to_dot(graph, show_binary=True)
|
|
101
|
+
dot.render("binary_tree", format="svg")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## CLI
|
|
105
|
+
|
|
106
|
+
A single `visiter` command with subcommands. Each subcommand takes its
|
|
107
|
+
function's keyword arguments as a single Python expression that is
|
|
108
|
+
`eval`'d in a namespace where `Op`, `Rule`, `iterate`, and `to_dot` are
|
|
109
|
+
pre-bound. Output is JSON (`iterate`) and DOT (`to-dot`) on stdout, so
|
|
110
|
+
they pipe directly:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
visiter iterate 'range(1, 30),
|
|
114
|
+
[Rule(lambda x: x%3==0, Op(lambda x: x//3, "÷3"))],
|
|
115
|
+
default=Op(lambda x: x+2, "+2")' \
|
|
116
|
+
| visiter to-dot 'anchor=1, radius=8, direction="backward"' \
|
|
117
|
+
| dot -Tsvg > descent.svg
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The `validate` subcommand checks a graph JSON document against the
|
|
121
|
+
bundled JSON Schema (`schemas/v1/graph.schema.json`, Draft 2020-12):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pip install visiter[validate]
|
|
125
|
+
visiter iterate '...' | visiter validate
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Documentation
|
|
129
|
+
|
|
130
|
+
- [docs/tutorial.md](docs/tutorial.md) — gentle introduction: what
|
|
131
|
+
problem the tool solves, smallest example, what each piece does,
|
|
132
|
+
what the dashed arrows mean. Start here.
|
|
133
|
+
- [docs/manual.md](docs/manual.md) — reference: every parameter,
|
|
134
|
+
every data field, the rendering model in full, design decisions.
|
|
135
|
+
- [demos/](demos/) — runnable end-to-end examples: `make demo` writes
|
|
136
|
+
SVG/PDF/DOT into `demos/out/`.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
visiter-0.1.0/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# VisIter
|
|
2
|
+
|
|
3
|
+
Build and visualize iteration graphs from rule-based state transitions.
|
|
4
|
+
|
|
5
|
+
VisIter is a small library that does two complementary things:
|
|
6
|
+
|
|
7
|
+
1. **`iterate(start, rules, default=..., max_depth=..., ...)`** —
|
|
8
|
+
applies a list of guard-and-operation Rules to seed values via BFS,
|
|
9
|
+
producing a graph (nodes, edges, per-node depth, optional pseudo-edges
|
|
10
|
+
marking structural boundaries).
|
|
11
|
+
|
|
12
|
+
2. **`to_dot(graph, ...)`** — turns the graph into a Graphviz
|
|
13
|
+
Digraph for SVG/HTML/PDF rendering, with anchor/radius cropping,
|
|
14
|
+
per-rule edge coloring, wedged-pie node fills for branching nodes,
|
|
15
|
+
and dashed ghost stubs at every kind of cut boundary.
|
|
16
|
+
|
|
17
|
+
The two are independent: any graph dict that fits the documented shape
|
|
18
|
+
can be rendered, and `iterate` can be used purely for graph construction
|
|
19
|
+
without ever touching the renderer.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install visiter
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Graphviz must be available on `PATH` for image rendering (the Python
|
|
28
|
+
`graphviz` package wraps the system tool).
|
|
29
|
+
|
|
30
|
+
## Quickstart — descent with divisor rule
|
|
31
|
+
|
|
32
|
+
A rule that divides by three when applicable, with a `+2` fallback for
|
|
33
|
+
all other values. Every integer path eventually joins one of two small
|
|
34
|
+
cycles (`1 → 3 → 1`, `2 → 4 → 6 → 2`).
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from visiter import iterate, Op, Rule, to_dot
|
|
38
|
+
|
|
39
|
+
graph = iterate(
|
|
40
|
+
start=range(1, 30),
|
|
41
|
+
rules=[Rule(lambda x: x % 3 == 0, Op(lambda x: x // 3, "÷3"))],
|
|
42
|
+
default=Op(lambda x: x + 2, "+2"),
|
|
43
|
+
)
|
|
44
|
+
dot = to_dot(graph, anchor=1, radius=8, direction="backward")
|
|
45
|
+
dot.render("descent", format="svg")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quickstart — reverse binary tree with bound
|
|
49
|
+
|
|
50
|
+
Generate every positive integer up to a ceiling as the binary tree of
|
|
51
|
+
`×2` / `×2+1` successors of 1. `Rule.bound` keeps the expansion inside
|
|
52
|
+
the ceiling; to_dot draws the frontier as dashed ghost stubs.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from visiter import iterate, Op, Rule, to_dot
|
|
56
|
+
|
|
57
|
+
ceiling = 64
|
|
58
|
+
|
|
59
|
+
graph = iterate(
|
|
60
|
+
start=[1],
|
|
61
|
+
rules=[
|
|
62
|
+
Rule(lambda x: True,
|
|
63
|
+
Op(lambda x: 2 * x, "×2"),
|
|
64
|
+
bound=lambda x: 2 * x <= ceiling),
|
|
65
|
+
Rule(lambda x: True,
|
|
66
|
+
Op(lambda x: 2 * x + 1, "×2+1"),
|
|
67
|
+
bound=lambda x: 2 * x + 1 <= ceiling),
|
|
68
|
+
],
|
|
69
|
+
default=None,
|
|
70
|
+
)
|
|
71
|
+
dot = to_dot(graph, show_binary=True)
|
|
72
|
+
dot.render("binary_tree", format="svg")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## CLI
|
|
76
|
+
|
|
77
|
+
A single `visiter` command with subcommands. Each subcommand takes its
|
|
78
|
+
function's keyword arguments as a single Python expression that is
|
|
79
|
+
`eval`'d in a namespace where `Op`, `Rule`, `iterate`, and `to_dot` are
|
|
80
|
+
pre-bound. Output is JSON (`iterate`) and DOT (`to-dot`) on stdout, so
|
|
81
|
+
they pipe directly:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
visiter iterate 'range(1, 30),
|
|
85
|
+
[Rule(lambda x: x%3==0, Op(lambda x: x//3, "÷3"))],
|
|
86
|
+
default=Op(lambda x: x+2, "+2")' \
|
|
87
|
+
| visiter to-dot 'anchor=1, radius=8, direction="backward"' \
|
|
88
|
+
| dot -Tsvg > descent.svg
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The `validate` subcommand checks a graph JSON document against the
|
|
92
|
+
bundled JSON Schema (`schemas/v1/graph.schema.json`, Draft 2020-12):
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install visiter[validate]
|
|
96
|
+
visiter iterate '...' | visiter validate
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Documentation
|
|
100
|
+
|
|
101
|
+
- [docs/tutorial.md](docs/tutorial.md) — gentle introduction: what
|
|
102
|
+
problem the tool solves, smallest example, what each piece does,
|
|
103
|
+
what the dashed arrows mean. Start here.
|
|
104
|
+
- [docs/manual.md](docs/manual.md) — reference: every parameter,
|
|
105
|
+
every data field, the rendering model in full, design decisions.
|
|
106
|
+
- [demos/](demos/) — runnable end-to-end examples: `make demo` writes
|
|
107
|
+
SVG/PDF/DOT into `demos/out/`.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Generate one iteration graph, then render it three different ways:
|
|
3
|
+
# (a) full backward neighborhood of the cycle attractor at 1
|
|
4
|
+
# (b) a tight 2-hop crop around 1 (shows the ghost-stub mechanism)
|
|
5
|
+
# (c) full graph with custom op_colors
|
|
6
|
+
# All three views share the same input data (descent.json), proving
|
|
7
|
+
# that iterate and to_dot are decoupled by the documented graph dict.
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
cd "$(dirname "$0")"
|
|
10
|
+
mkdir -p out
|
|
11
|
+
|
|
12
|
+
DATA="out/descent.json"
|
|
13
|
+
EXPR="$(cat data/descent.expr)"
|
|
14
|
+
|
|
15
|
+
visiter iterate "$EXPR" > "$DATA"
|
|
16
|
+
echo "wrote $DATA"
|
|
17
|
+
|
|
18
|
+
visiter to-dot 'anchor=1, radius=10, direction="backward"' \
|
|
19
|
+
< "$DATA" > out/descent_full.dot
|
|
20
|
+
dot -Tsvg out/descent_full.dot -o out/descent_full.svg
|
|
21
|
+
echo "wrote out/descent_full.svg"
|
|
22
|
+
|
|
23
|
+
visiter to-dot 'anchor=1, radius=2, direction="backward"' \
|
|
24
|
+
< "$DATA" > out/descent_tight.dot
|
|
25
|
+
dot -Tsvg out/descent_tight.dot -o out/descent_tight.svg
|
|
26
|
+
echo "wrote out/descent_tight.svg (tight crop — note the ghost stubs)"
|
|
27
|
+
|
|
28
|
+
visiter to-dot 'op_colors={"÷3": "#a83232", "+2": "#3266a8"}' \
|
|
29
|
+
< "$DATA" > out/descent_recolored.dot
|
|
30
|
+
dot -Tsvg out/descent_recolored.dot -o out/descent_recolored.svg
|
|
31
|
+
echo "wrote out/descent_recolored.svg (custom palette)"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Full pipe composition: build a graph, render to DOT, hand to `dot` for
|
|
3
|
+
# layout + format conversion. Two output formats demonstrate that the
|
|
4
|
+
# downstream stage is just a Graphviz invocation.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
cd "$(dirname "$0")"
|
|
7
|
+
mkdir -p out
|
|
8
|
+
|
|
9
|
+
# Reverse binary tree: from 1, doubling and double-plus-one until 64.
|
|
10
|
+
EXPR='start=[1], rules=[
|
|
11
|
+
Rule(lambda x: True, Op(lambda x: 2*x, "×2"),
|
|
12
|
+
bound=lambda x: 2*x <= 64),
|
|
13
|
+
Rule(lambda x: True, Op(lambda x: 2*x+1, "×2+1"),
|
|
14
|
+
bound=lambda x: 2*x+1 <= 64),
|
|
15
|
+
], default=None'
|
|
16
|
+
|
|
17
|
+
visiter iterate "$EXPR" \
|
|
18
|
+
| visiter to-dot 'show_binary=True' \
|
|
19
|
+
| tee out/binary_tree.dot \
|
|
20
|
+
| dot -Tsvg -o out/binary_tree.svg
|
|
21
|
+
echo "wrote out/binary_tree.svg"
|
|
22
|
+
|
|
23
|
+
# Same data, PDF instead of SVG.
|
|
24
|
+
dot -Tpdf out/binary_tree.dot -o out/binary_tree.pdf
|
|
25
|
+
echo "wrote out/binary_tree.pdf"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Insert schema validation as a pipeline checkpoint. `tee` forks the
|
|
3
|
+
# graph JSON to `visiter validate` (which writes its verdict to stderr
|
|
4
|
+
# / a side file) while the main pipe continues into `to-dot`. If the
|
|
5
|
+
# graph ever drifted from the documented shape — say, after a future
|
|
6
|
+
# breaking change to iterate — this would catch it before rendering.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
cd "$(dirname "$0")"
|
|
9
|
+
mkdir -p out
|
|
10
|
+
|
|
11
|
+
EXPR="$(cat data/descent.expr)"
|
|
12
|
+
|
|
13
|
+
VALIDATE_LOG="out/validate.log"
|
|
14
|
+
visiter iterate "$EXPR" \
|
|
15
|
+
| tee >(visiter validate > "$VALIDATE_LOG" 2>&1) \
|
|
16
|
+
| visiter to-dot 'anchor=1, radius=10, direction="backward"' \
|
|
17
|
+
| dot -Tsvg -o out/descent_validated.svg
|
|
18
|
+
|
|
19
|
+
# Surface the validator's verdict.
|
|
20
|
+
echo "validator said: $(cat "$VALIDATE_LOG")"
|
|
21
|
+
echo "wrote out/descent_validated.svg"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Iteration on strings, not numbers. Rule: if the word ends in a vowel,
|
|
3
|
+
# drop the last character. Default: none — words ending in a consonant
|
|
4
|
+
# are leaves. Demonstrates that `iterate`'s value type can be any
|
|
5
|
+
# hashable, str()-able Python object.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
cd "$(dirname "$0")"
|
|
8
|
+
mkdir -p out
|
|
9
|
+
|
|
10
|
+
EXPR='start=["banana", "garage", "queue", "iterator"],
|
|
11
|
+
rules=[Rule(lambda s: len(s) > 0 and s[-1] in set("aeiou"),
|
|
12
|
+
Op(lambda s: s[:-1], "drop-vowel"))],
|
|
13
|
+
default=None'
|
|
14
|
+
|
|
15
|
+
visiter iterate "$EXPR" \
|
|
16
|
+
| visiter to-dot '' \
|
|
17
|
+
| dot -Tsvg -o out/words.svg
|
|
18
|
+
echo "wrote out/words.svg (string-valued iteration)"
|