From 4b81abec34ca8ceb35147090c27010ad928d403e Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Mon, 7 Sep 2026 08:58:07 +0800 Subject: [PATCH 1/3] Only consult matching-variant entries when computing source size compute_source_size called every registered entry for a problem name and relied on catch_unwind to swallow the unwrap panics from entries whose source variant did not match the instance. The default panic hook still printed those panics to stderr, so `pred solve` on a default-variant instance emitted spurious 'thread main panicked' lines before succeeding. Filter entries by source variant as well as name and drop the catch_unwind. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Heb7aLYWxgQE4v6TPNAQE4 --- docs/src/agent-find-problem.md | 29 ----------- docs/src/agent-find-solver.md | 26 ---------- docs/src/agent-pipeline.md | 31 ------------ docs/src/agent-propose.md | 24 --------- docs/src/cli-automation.md | 37 -------------- docs/src/cli-catalog.md | 22 --------- docs/src/cli-completions.md | 16 ------ docs/src/cli-create.md | 26 ---------- docs/src/cli-demo.md | 35 ------------- docs/src/cli-examples.md | 42 ---------------- docs/src/cli-inspect.md | 31 ------------ docs/src/cli-paths.md | 34 ------------- docs/src/cli-random.md | 13 ----- docs/src/cli-reduce.md | 31 ------------ docs/src/cli-solve.md | 37 -------------- docs/src/cli-variants.md | 20 -------- docs/src/contributing.md | 15 ------ docs/src/design-paths.md | 32 ------------ docs/src/design-problem.md | 23 --------- docs/src/design-reductions.md | 36 -------------- docs/src/design-serialization.md | 14 ------ docs/src/design-variant-registration.md | 66 ------------------------- docs/src/design-variants.md | 33 ------------- docs/src/install.md | 29 ----------- docs/src/mcp-tools.md | 31 ------------ docs/src/mcp-walkthrough.md | 30 ----------- docs/src/mcp.md | 31 ------------ docs/src/rust-paths.md | 35 ------------- docs/src/rust-reduction.md | 30 ----------- docs/src/rust-solvers.md | 27 ---------- src/rules/graph.rs | 34 +++++++------ src/solvers/ilp/solver.rs | 2 +- src/unit_tests/rules/graph.rs | 22 ++++++++- 33 files changed, 40 insertions(+), 904 deletions(-) delete mode 100644 docs/src/agent-find-problem.md delete mode 100644 docs/src/agent-find-solver.md delete mode 100644 docs/src/agent-pipeline.md delete mode 100644 docs/src/agent-propose.md delete mode 100644 docs/src/cli-automation.md delete mode 100644 docs/src/cli-catalog.md delete mode 100644 docs/src/cli-completions.md delete mode 100644 docs/src/cli-create.md delete mode 100644 docs/src/cli-demo.md delete mode 100644 docs/src/cli-examples.md delete mode 100644 docs/src/cli-inspect.md delete mode 100644 docs/src/cli-paths.md delete mode 100644 docs/src/cli-random.md delete mode 100644 docs/src/cli-reduce.md delete mode 100644 docs/src/cli-solve.md delete mode 100644 docs/src/cli-variants.md delete mode 100644 docs/src/contributing.md delete mode 100644 docs/src/design-paths.md delete mode 100644 docs/src/design-problem.md delete mode 100644 docs/src/design-reductions.md delete mode 100644 docs/src/design-serialization.md delete mode 100644 docs/src/design-variant-registration.md delete mode 100644 docs/src/design-variants.md delete mode 100644 docs/src/install.md delete mode 100644 docs/src/mcp-tools.md delete mode 100644 docs/src/mcp-walkthrough.md delete mode 100644 docs/src/mcp.md delete mode 100644 docs/src/rust-paths.md delete mode 100644 docs/src/rust-reduction.md delete mode 100644 docs/src/rust-solvers.md diff --git a/docs/src/agent-find-problem.md b/docs/src/agent-find-problem.md deleted file mode 100644 index 8b5feff57..000000000 --- a/docs/src/agent-find-problem.md +++ /dev/null @@ -1,29 +0,0 @@ -# Extend a solver's reach - -Find source problems that can reduce to a solver you already have. - -**Before you start:** complete [agent setup](skills.md). Specify the solver's accepted model, topology, weights, and practical size limit. - -## Prompt - -```text -Read .claude/skills/find-problem/SKILL.md and follow it. -My solver accepts: [exact model and variant]. -Its time complexity and practical size limit: [details]. -Find incoming reduction routes and rank useful source problems. -Show the assumptions and composed size overhead for each recommendation. -Write the findings to docs/solutions/. -``` - -## Check the direction - -A solver for a target can handle sources that reduce **to** it. For example: - -```bash -pred to QUBO -pred path MIS QUBO -``` - -A route in the opposite direction does not establish that capability. Confirm each candidate's exact variants and measure the constructed target size on a small instance. - -Next: [reduce an instance](cli-reduce.md). diff --git a/docs/src/agent-find-solver.md b/docs/src/agent-find-solver.md deleted file mode 100644 index 1e54b9b82..000000000 --- a/docs/src/agent-find-solver.md +++ /dev/null @@ -1,26 +0,0 @@ -# Find a solver - -Map a concrete problem to a library model and a usable solver route. - -**Before you start:** complete [agent setup](skills.md). Describe the inputs, constraints, objective, and expected instance sizes. - -## Prompt - -```text -Read .claude/skills/find-solver/SKILL.md and follow it. -My problem: [describe inputs, constraints, and objective]. -Typical size: [counts and ranges]. -Available solvers or hardware: [list, or no preference]. -Check the exact model variant and current reduction paths with pred. -Separate implemented routes from suggestions that need new work. -``` - -## Expected result - -A solution document in `docs/solutions/` with the proposed model, assumptions, reduction route, size overheads, and solver recommendation. - -## Check the recommendation - -Ask the agent to construct a small instance, solve it, and evaluate the recovered configuration on the original problem. If several models fit, resolve the modeling differences before scaling up. - -Next: [first solve](cli.md) or [inspect a route](cli-paths.md). diff --git a/docs/src/agent-pipeline.md b/docs/src/agent-pipeline.md deleted file mode 100644 index 7c3e757e6..000000000 --- a/docs/src/agent-pipeline.md +++ /dev/null @@ -1,31 +0,0 @@ -# Implement and review - -Move a prepared issue through the repository's agent workflow. These commands act on GitHub issues, project status, and pull requests; run them in a configured maintainer checkout. - -## Implement one issue - -```bash -make run-issue N=42 -``` - -Replace `42` with the issue number. The task follows `.claude/skills/issue-to-pr/SKILL.md`. Model and rule skills define the source, tests, examples, and paper changes required. - -To pick one eligible Ready issue from the project board: - -```bash -make run-pipeline -``` - -## Review one pull request - -```bash -make run-review N=570 -``` - -Replace `570` with the PR number. The review workflow checks structure, quality, and user-facing behavior, then moves the PR to Final review. Maintainer review controls acceptance. - -## Evidence to retain - -Keep the mathematical argument, constructor and adversarial checks, closed-loop tests, canonical example, and review findings with the work. A passing test suite is evidence for tested instances, not a proof for all inputs. - -For exact workflow rules, read [the repository instructions](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/CLAUDE.md). diff --git a/docs/src/agent-propose.md b/docs/src/agent-propose.md deleted file mode 100644 index c936b496c..000000000 --- a/docs/src/agent-propose.md +++ /dev/null @@ -1,24 +0,0 @@ -# Propose a model or rule - -Turn a mathematical idea or published construction into a precise proposal for the atlas. - -**Before you start:** complete [agent setup](skills.md). Bring a definition, a source reference, or a candidate source → target connection. - -## Prompt - -```text -Read .claude/skills/propose/SKILL.md and follow it. -I want to propose: [model or source-to-target reduction]. -Reference or construction: [details]. -Check the current catalog for existing models and exact variant endpoints. -Clarify the objective, solution mapping, correctness argument, and overhead. -Prepare the proposal for the issue workflow. -``` - -## Expected result - -A model or rule proposal with explicit assumptions and enough mathematical detail to assess usefulness and correctness. The skill guides issue creation. - -For a rule, distinguish a construction supported by literature from a new conjecture. Record proof gaps and counterexamples explicitly. - -Next: [implementation and review](agent-pipeline.md) once the issue is ready. diff --git a/docs/src/cli-automation.md b/docs/src/cli-automation.md deleted file mode 100644 index b2624f6be..000000000 --- a/docs/src/cli-automation.md +++ /dev/null @@ -1,37 +0,0 @@ -# JSON and automation - -Use structured output for agent tools and scripts. - -```bash -pred list --json -pred show MIS --json -pred path MIS QUBO --json -pred solve problem.json -o solution.json -``` - -| Global flag | Effect | -|---|---| -| `--json` | Request JSON output for data commands | -| `-o, --output ` | Save JSON to a file | -| `-q, --quiet` | Suppress informational messages on stderr | - -Check the process exit status before consuming a result. Preserve `type` and `variant` with each instance; an alias alone is insufficient to replay an exact endpoint. - -## Pipe instances - -```bash -pred create MIS --graph 0-1,1-2,2-3 | pred solve - --json -pred create MIS --graph 0-1,1-2,2-3 | pred reduce - --to QUBO | pred solve - --solver brute-force --json -``` - -`-` reads a problem or bundle from stdin. In shell automation, enable `set -o pipefail` so a failed earlier command fails the pipeline. - -## Registry exports - -```bash -pred export-graph -o reduction_graph.json -``` - -The docs build publishes [reduction_graph.json](reductions/reduction_graph.json) and [problem_schemas.json](reductions/problem_schemas.json). The graph includes exact variants, directed edges, capabilities, and symbolic overheads; schemas describe model fields. - -Use the [Markdown index](markdown/index.md) for task instructions and these JSON files for structured registry queries. The exports describe the website's build; local CLI output describes the installed version. diff --git a/docs/src/cli-catalog.md b/docs/src/cli-catalog.md deleted file mode 100644 index 47c53cb53..000000000 --- a/docs/src/cli-catalog.md +++ /dev/null @@ -1,22 +0,0 @@ -# Explore the catalog - -Use the installed registry to discover models, accepted fields, and reductions. - -```bash -pred list -pred show MIS -pred show MIS --json -``` - -`list` reports names and aliases. `show` describes the resolved variant, its size fields, input schema, and incoming/outgoing reductions. Use that schema before constructing an instance. - -
-Example: inspect Maximum Independent Set - -```text -{{#include generated/pred-show-mis.txt}} -``` - -
- -For machine-readable catalog data, use `pred list --json`. Use [names and variants](cli-variants.md) to select exact endpoints and [path queries](cli-paths.md) to explore their connections. diff --git a/docs/src/cli-completions.md b/docs/src/cli-completions.md deleted file mode 100644 index a5ee8f3f5..000000000 --- a/docs/src/cli-completions.md +++ /dev/null @@ -1,16 +0,0 @@ -# Shell completions - -Enable tab completion by adding one line to your shell config: - -```bash -# bash (~/.bashrc) -eval "$(pred completions bash)" - -# zsh (~/.zshrc) -eval "$(pred completions zsh)" - -# fish (~/.config/fish/config.fish) -pred completions fish | source -``` - -If the shell argument is omitted, `pred completions` auto-detects your current shell. diff --git a/docs/src/cli-create.md b/docs/src/cli-create.md deleted file mode 100644 index 51318def0..000000000 --- a/docs/src/cli-create.md +++ /dev/null @@ -1,26 +0,0 @@ -# Create an instance - -**Input:** a model name or exact variant, plus fields from `pred show `. -**Output:** a JSON problem file containing `type`, `variant`, and `data`. - -```bash -pred create MIS --graph 0-1,1-2,2-3 -o problem.json -pred create MIS/SimpleGraph/i32 --graph 0-1,1-2,2-3 --weights 2,1,3,1 -o weighted.json -pred inspect weighted.json -``` - -Vertices use zero-based indices. `--graph` is a comma-separated edge list; weights must match the model's expected vertices or edges. - -## Use a canonical example - -```bash -pred create --example MIS/SimpleGraph/i32 -o model.json -pred create --example MVC/SimpleGraph/i32 --to MIS/SimpleGraph/i32 -o source.json -pred create --example MVC/SimpleGraph/i32 --to MIS/SimpleGraph/i32 --example-side target -o target.json -``` - -The first command loads a model fixture. The next two load the source and target of a documented reduction example. - -CLI fields follow schema names: `universe_size` becomes `--universe-size`, and `subsets` becomes `--subsets`. - -Next: [input examples](cli-examples.md), [random instances](cli-random.md), or [inspect and evaluate](cli-inspect.md). diff --git a/docs/src/cli-demo.md b/docs/src/cli-demo.md deleted file mode 100644 index b7e90437e..000000000 --- a/docs/src/cli-demo.md +++ /dev/null @@ -1,35 +0,0 @@ -# CLI in action - -From a graph to a checked solution in six commands. This recording runs the real CLI: discover a route, transform the instance, solve, and verify the result. - - - -[Open the player](static/cli-demo.html) · [Download the cast](static/cli-demo.cast) · [Download the offline HTML](static/cli-demo.html) - -## Run it yourself - -[Install `pred`](install.md), then run in a fresh directory. The recording uses `./pred`, a local build of the same executable. - -```bash -pred path MIS ILP -pred create MIS --graph 0-1,1-2,2-3,3-4,4-0 -o cycle.json -pred reduce cycle.json --to ILP -o reduced.json -pred solve reduced.json -pred evaluate cycle.json --config 1,0,1,0,0 -pred solve cycle.json --solver brute-force -``` - -## What to check - -The recorded route goes through Maximum Set Packing and a weight cast before reaching binary ILP. `reduced.json` preserves the source instance and path so the solver can recover a source solution. - -| Check | Recorded result | -|---|---| -| Solve the reduction bundle | `Max(2)`, configuration `[1,0,1,0,0]` | -| Evaluate that source configuration | `Max(2)` | -| Exhaustive source solve | `Max(2)`, configuration `[0,0,1,0,1]` | -| Command exit codes | All six returned `0` | - -Different configurations can have the same optimum. This cross-check validates this instance; a reduction's general correctness requires a mathematical argument. - -Next: [JSON output for agents](cli-automation.md) or [find another route](cli-paths.md). diff --git a/docs/src/cli-examples.md b/docs/src/cli-examples.md deleted file mode 100644 index f2b9cf90b..000000000 --- a/docs/src/cli-examples.md +++ /dev/null @@ -1,42 +0,0 @@ -# Input examples - -Create small instances for different input structures. Use `pred show ` for its schema and `pred create --help` for flag syntax. - -## Boolean formula - -```bash -pred create SAT --num-vars 3 --clauses '1,2;-1,3' -o sat.json -``` - -Clauses are separated by semicolons. Literals are signed, one-based variable indices; `-1` negates variable 1. - -## Quadratic matrix - -```bash -pred create QUBO --matrix '1,0.5;0.5,2' -o qubo.json -``` - -Semicolons separate rows; commas separate entries. - -## Set system - -```bash -pred create X3C --universe-size 6 --subsets '0,1,2;3,4,5;0,3,4' -o x3c.json -``` - -Subset elements use zero-based indices into the universe. - -## Integer factoring - -```bash -pred create Factoring --target 6 --m 2 --n 2 -o factoring.json -``` - -## Check an instance - -```bash -pred inspect sat.json -pred solve sat.json --solver brute-force -``` - -For other models, start from `pred create --example ` and inspect its fields. Brute-force cost is the product of variable domain sizes; use tiny examples. diff --git a/docs/src/cli-inspect.md b/docs/src/cli-inspect.md deleted file mode 100644 index 4dde633bd..000000000 --- a/docs/src/cli-inspect.md +++ /dev/null @@ -1,31 +0,0 @@ -# Inspect and evaluate - -Use `inspect` to identify a problem file or reduction bundle. Use `evaluate` to check a specific configuration against the original instance. - -## Inspect a file - -```bash -pred create MIS --graph 0-1,1-2,2-3 -o problem.json -pred inspect problem.json -pred inspect problem.json --json -``` - -Inspect the resolved variant and sizes before choosing a solver. - -## Evaluate a configuration - -```bash -pred evaluate problem.json --config 1,0,1,0 -``` - -This selects vertices 0 and 2 and returns `Max(2)`. Selecting adjacent vertices is invalid and returns `Max(None)`. - -Configurations follow each problem's variable domains; they are not always binary. Check the model definition before constructing one. - -## Read from stdin - -```bash -pred create MIS --graph 0-1,1-2,2-3 | pred evaluate - --config 1,0,1,0 -``` - -Evaluation checks one candidate. It does not establish optimality; compare with [a solver](cli-solve.md). diff --git a/docs/src/cli-paths.md b/docs/src/cli-paths.md deleted file mode 100644 index 740350dc5..000000000 --- a/docs/src/cli-paths.md +++ /dev/null @@ -1,34 +0,0 @@ -# Find reduction paths - -Choose source and target [variants](cli-variants.md), then query their direction of reachability. - -```bash -pred path MIS ILP -pred from MIS -pred to QUBO -``` - -`from` explores outgoing routes; `to` explores incoming routes. `path` finds a route between the supplied endpoints. Search defaults to witness-capable reductions. - -## Save and compare routes - -```bash -pred path MIS QUBO -o path.json -pred path MIS QUBO --all --max-paths 50 -pred path MIS QUBO --cost minimize-steps -pred path MIS QUBO --cost minimize:num_variables -``` - -`--all` is capped (20 paths by default); inspect the truncation indicator. A saved single path can be used with `pred reduce --via path.json`. - -The default cost minimizes steps. Size-based costs use reduction overhead metadata; inspect available size fields with `pred show`. Overhead formulas describe scaling bounds, not exact constructed instance sizes. - -## Multi-step example - -```text -{{#include generated/pred-path-factoring-spinglass.txt}} -``` - -A discovered path does not imply the target will be inexpensive to solve. Measure the target size and solver behavior on representative instances. - -Next: [apply a path](cli-reduce.md) or [understand overhead](design-paths.md). diff --git a/docs/src/cli-random.md b/docs/src/cli-random.md deleted file mode 100644 index c421a655c..000000000 --- a/docs/src/cli-random.md +++ /dev/null @@ -1,13 +0,0 @@ -# Random instances - -Generate a small graph for exploration: - -```bash -pred create MIS --random --num-vertices 10 -o random.json -pred inspect random.json -pred solve random.json --solver brute-force -``` - -Use `pred create --help` for generation options supported by the current build. Random generation is available for selected input structures; it is not a constructor for every model. - -Save the generated JSON when reporting results so another agent can reproduce the exact instance. Use [canonical examples](cli-create.md#use-a-canonical-example) for stable demonstrations. diff --git a/docs/src/cli-reduce.md b/docs/src/cli-reduce.md deleted file mode 100644 index 48071c9a3..000000000 --- a/docs/src/cli-reduce.md +++ /dev/null @@ -1,31 +0,0 @@ -# Reduce an instance - -**Before you start:** [create a problem file](cli-create.md) and [find a route](cli-paths.md). - -## Choose a target - -```bash -pred reduce problem.json --to QUBO -o reduced.json -pred inspect reduced.json -``` - -The reduction bundle contains the source instance, target instance, and variant-level path. Keep the whole bundle to preserve solution recovery. - -## Use a saved path - -```bash -pred path MIS QUBO -o path.json -pred reduce problem.json --via path.json -o reduced.json -``` - -The path determines the target; `--to` is unnecessary. The input must match the path's source variant. For a weighted input, find a path from its exact weighted variant. - -## Solve and recover - -```bash -pred solve reduced.json --solver brute-force -``` - -The solver solves the target and maps the result back through the bundle. Keep targets small when using brute force. For JSON output, the `intermediate` field records the target result alongside the recovered source solution. - -Next: [solver options](cli-solve.md) or [pipeline commands](cli-automation.md). diff --git a/docs/src/cli-solve.md b/docs/src/cli-solve.md deleted file mode 100644 index 6afd49824..000000000 --- a/docs/src/cli-solve.md +++ /dev/null @@ -1,37 +0,0 @@ -# Solve an instance - -**Input:** a problem JSON file or a complete reduction bundle. -**Output:** the evaluation and, for witness-capable problems, a solution configuration. - -```bash -pred solve problem.json -pred solve problem.json --solver brute-force -pred solve problem.json --timeout 30 -pred solve problem.json -o solution.json -``` - -## Choose a solver - -| Solver | Behavior | -|---|---| -| `ilp` (default) | Finds a witness-capable ILP route, solves the target, and recovers a source configuration | -| `brute-force` | Enumerates configurations; use for tiny instances and cross-checks | -| `customized` | Uses exact backends for selected models; inspect `pred solve --help` for supported models | - -If ILP reports no route, inspect `pred path ILP` and try brute force on a small instance. A model's presence in the catalog does not guarantee every solver supports it. - -## Solve a bundle - -```bash -pred solve reduced.json --json -``` - -A bundle requires a witness-capable target and path. Its result includes the recovered source solution and the intermediate target result. - -Aggregate-only problems produce values without representative configurations. Do not assume every successful solve contains a witness. - -## Verify the result - -Evaluate the returned configuration on the original instance using `pred evaluate`. For a tiny example, compare its objective with an exhaustive solve. Different optimal configurations are acceptable when their evaluations agree. - -Next: [JSON and automation](cli-automation.md). diff --git a/docs/src/cli-variants.md b/docs/src/cli-variants.md deleted file mode 100644 index 0f8cce33f..000000000 --- a/docs/src/cli-variants.md +++ /dev/null @@ -1,20 +0,0 @@ -# Names and variants - -Aliases such as `MIS` resolve to full problem names. A bare name selects that model's declared default variant; `MIS` resolves to `MaximumIndependentSet/SimpleGraph/One`. - -## Select an exact variant - -```bash -pred show MIS/SimpleGraph/i32 -pred path MIS/SimpleGraph/i32 ILP/bool -``` - -Slash-separated parameters select graph, weight, or other variant values. Use `pred list` and `pred show` to inspect the registry instead of guessing a variant. - -`One` means unit weights. Providing non-unit `--weights` when creating a default MIS instance upgrades it to `i32`. Specify `MIS/SimpleGraph/i32` explicitly when a reproducible endpoint matters. - -## Common aliases - -{{#include generated/pred-aliases.txt}} - -Next: [find a path](cli-paths.md). diff --git a/docs/src/contributing.md b/docs/src/contributing.md deleted file mode 100644 index 1c7eaa2ae..000000000 --- a/docs/src/contributing.md +++ /dev/null @@ -1,15 +0,0 @@ -# Contributing - -Propose a model or reduction through the [agent proposal workflow](agent-propose.md), or use the repository's [issue templates](https://github.com/CodingThrust/problem-reductions/issues/new/choose). - -For implementation, read [AGENTS.md](https://github.com/CodingThrust/problem-reductions/blob/main/AGENTS.md) and the canonical `.claude/CLAUDE.md` instructions. Model and rule skills describe required code, tests, examples, and mathematical documentation. - -## What a rule contributes - -A useful rule includes exact endpoint variants, a construction, solution or value extraction, a correctness argument, and overhead metadata. Closed-loop tests and independently checked examples provide concrete evidence alongside the proof. - -## Authorship and license - -The project's authorship policy adds contributors of 10 non-trivial reduction rules to the [paper](reductions.pdf). The software is MIT licensed. - -Next: [implement and review](agent-pipeline.md). diff --git a/docs/src/design-paths.md b/docs/src/design-paths.md deleted file mode 100644 index 101cde4c6..000000000 --- a/docs/src/design-paths.md +++ /dev/null @@ -1,32 +0,0 @@ -# Path costs and overhead - -`ReductionGraph` searches a directed graph of exact `(name, variant)` pairs. Registered reductions carry capabilities; natural variant connections reflect graph and weight subtype relations. - -## Choose a cost - -| Cost | Purpose | -|---|---| -| `MinimizeSteps` | Fewest reduction steps | -| `Minimize("field")` | Cost based on an output size field | -| `CustomCost(closure)` | User-defined edge cost from overhead and current size | - -`find_cheapest_path` accepts source/target variant maps, an input `ProblemSize`, and a cost function. `find_all_paths` enumerates simple paths. Use bounded enumeration when exploring a large graph. - -## Interpret an overhead - -```rust,ignore -#[reduction(overhead = { - num_vars = "num_vertices + num_edges", - num_clauses = "3 * num_edges", -})] -``` - -Expressions refer to getters on the source type. The macro validates those names at compile time. The metadata describes scaling bounds; it does not promise exact target counts for each input. - -For a concrete instance, inspect the constructed target. For a chain, `path_overheads` returns each edge's expressions and `compose_path_overhead` substitutes them to obtain an end-to-end bound. - -## Keep costs separate from solve time - -A shorter route can produce a harder target. Compare target sizes and solver measurements as well as hop counts; do not present symbolic overheads as measured runtime. - -Next: [execute a path](rust-paths.md) or [download the graph](reductions/reduction_graph.json). diff --git a/docs/src/design-problem.md b/docs/src/design-problem.md deleted file mode 100644 index 1e5b25ca3..000000000 --- a/docs/src/design-problem.md +++ /dev/null @@ -1,23 +0,0 @@ -# Problem contract - -Every problem implements `Problem`. The associated `Value` type is the per-configuration aggregate returned by `evaluate()`. Solvers fold these values across the configuration space, and witness-capable aggregates can also recover representative configurations. - -```rust,ignore -trait Problem { - const NAME: &'static str; // e.g., "MaximumIndependentSet" - type Value: Clone; // e.g., Max, Or, Sum - fn dims(&self) -> Vec; // config space per variable - fn evaluate(&self, config: &[usize]) -> Self::Value; - fn variant() -> Vec<(&'static str, &'static str)>; // e.g., [("graph", "SimpleGraph"), ("weight", "i32")] - fn num_variables(&self) -> usize; // default: dims().len() - fn problem_type() -> ProblemType; // default: registry lookup by NAME -} -``` - -- **`Problem`** — the base trait. Every problem declares a `NAME` (e.g., `"MaximumIndependentSet"`). The solver explores the configuration space defined by `dims()` and scores each configuration with `evaluate()`. For example, a 4-vertex MIS has `dims() = [2, 2, 2, 2]` (each vertex is selected or not); `evaluate(&[1, 0, 1, 0])` returns `Max(Some(2))` if vertices 0 and 2 form an independent set, or `Max(None)` if they share an edge. Each problem also provides inherent getter methods (e.g., `num_vertices()`, `num_edges()`) used by reduction overhead expressions. -- **Witness-capable objective problems** — typically use `Max`, `Min`, or `Extremum` as `Value`. -- **Witness-capable feasibility problems** — typically use `Or`. -- **Aggregate-only problems** — use fold values such as `Sum` or `And`; these solve to a value but do not admit representative witness configurations. -- **Common aggregate wrappers** — `Max`, `Min`, `Sum`, `Or`, `And`, `Extremum`, `ExtremumSense`. - -Next: [variant registration](design-variant-registration.md). diff --git a/docs/src/design-reductions.md b/docs/src/design-reductions.md deleted file mode 100644 index 4141a9757..000000000 --- a/docs/src/design-reductions.md +++ /dev/null @@ -1,36 +0,0 @@ -# Reduction contracts - -A reduction connects exact source and target variants. Its contract determines how a result can be recovered. - -| Capability | Contract | Example | -|---|---|---| -| Witness | `ReduceTo` and `ReductionResult::extract_solution` | Solve a target, recover a source configuration | -| Aggregate | `ReduceToAggregate` and `AggregateReductionResult::extract_value` | Solve a target value, recover a source value | -| Turing | Multiple target queries | Optimize by querying a decision problem at several bounds | - -Search defaults to witness mode. Use `ReductionMode::Aggregate` or `ReductionMode::Turing` for the corresponding graph queries; do not replay those edges as a single witness mapping. - -## Register a witness reduction - -```rust,ignore -#[reduction(overhead = { - num_vertices = "num_vertices", - num_edges = "num_edges", -})] -impl ReduceTo> - for MaximumIndependentSet -{ - // Provide Result and reduce_to(); the result owns the target - // and maps a vertex-cover witness to its independent-set complement. -} -``` - -This is a schematic declaration. Read a [complete implementation](https://github.com/CodingThrust/problem-reductions/blob/main/src/rules/maximumindependentset_minimumvertexcover.rs) for the result type and constructor. - -The attribute requires overhead metadata and registers witness/configuration reductions. Aggregate and Turing edges currently use manual `ReductionEntry` registration. Keep one primitive registration for each exact endpoint pair. - -## Check correctness - -Prove that the construction and extraction preserve the required result. For a small witness example, solve source and target independently, extract the target solution, and compare its source evaluation with the direct optimum. - -Next: [overhead semantics](design-paths.md) or [implementation workflow](agent-pipeline.md). diff --git a/docs/src/design-serialization.md b/docs/src/design-serialization.md deleted file mode 100644 index 53f314ca5..000000000 --- a/docs/src/design-serialization.md +++ /dev/null @@ -1,14 +0,0 @@ -# JSON serialization - -All problem types support JSON serialization via serde: - -```rust,ignore -use problemreductions::io::{to_json, from_json}; - -let json: String = to_json(&problem)?; -let restored: MaximumIndependentSet = from_json(&json)?; -``` - -These helpers serialize typed Rust problem data. The CLI additionally wraps data with `type` and `variant` for dynamic loading. Keep that wrapper when passing files between CLI commands. - -Next: [CLI JSON and automation](cli-automation.md). diff --git a/docs/src/design-variant-registration.md b/docs/src/design-variant-registration.md deleted file mode 100644 index 851429d43..000000000 --- a/docs/src/design-variant-registration.md +++ /dev/null @@ -1,66 +0,0 @@ -# Register variants - -## VariantParam trait - -Each variant parameter type implements `VariantParam`, which declares its category, value, and optional parent: - -```rust,ignore -pub trait VariantParam: 'static { - const CATEGORY: &'static str; // e.g., "graph", "weight", "k" - const VALUE: &'static str; // e.g., "SimpleGraph", "i32" - const PARENT_VALUE: Option<&'static str>; // None for root types -} -``` - -Types with a parent also implement `CastToParent`, providing the runtime conversion for variant casts: - -```rust,ignore -pub trait CastToParent: VariantParam { - type Parent: VariantParam; - fn cast_to_parent(&self) -> Self::Parent; -} -``` - -## Registration with `impl_variant_param!` - -The `impl_variant_param!` macro implements `VariantParam` (and optionally `CastToParent` / `KValue`) for a type: - -```rust,ignore -// Root type (no parent): -impl_variant_param!(SimpleGraph, "graph"); - -// K root (arbitrary K): -impl_variant_param!(KN, "k", k: None); - -// Specific K with parent: -impl_variant_param!(K3, "k", parent: KN, cast: |_| KN, k: Some(3)); -``` - -## Variant cast reductions with `impl_variant_reduction!` - -When a more specific variant needs to be treated as a less specific one, an explicit variant cast reduction is declared: - -```rust,ignore -impl_variant_reduction!( - MaximumIndependentSet, - => , - fields: [num_vertices, num_edges], - |src| MaximumIndependentSet::new( - src.graph().cast_to_parent(), src.weights().to_vec()) -); -``` - -## Composing `Problem::variant()` - -The `variant_params!` macro composes the `Problem::variant()` body from type parameter names: - -```rust,ignore -// MaximumIndependentSet -fn variant() -> Vec<(&'static str, &'static str)> { - crate::variant_params![G, W] - // e.g., MaximumIndependentSet - // -> vec![("graph", "UnitDiskGraph"), ("weight", "One")] -} -``` - -Use `declare_variants!` in the model file to register concrete types and their dynamic load/serialize/solve metadata. See existing models and the [repository instructions](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/CLAUDE.md) for the current macro contract. diff --git a/docs/src/design-variants.md b/docs/src/design-variants.md deleted file mode 100644 index d177566a3..000000000 --- a/docs/src/design-variants.md +++ /dev/null @@ -1,33 +0,0 @@ -# Variant system - -A single problem name like `MaximumIndependentSet` can have multiple **variants** — carrying weights on vertices, or defined on a restricted topology (e.g., king's subgraph). Variants form a subtype hierarchy: independent sets on king's subgraphs are a subset of independent sets on unit-disk graphs. The reduction from a more specific variant to a less specific one is a **variant cast** — an identity mapping where indices are preserved. - -
- -![Variant Hierarchy](static/variant-hierarchy.svg) - -
-
- -![Variant Hierarchy](static/variant-hierarchy-dark.svg) - -
- -Variant types fall into three categories: - -- **Graph type** — `SimpleGraph` (root), `PlanarGraph`, `BipartiteGraph`, `UnitDiskGraph`, `KingsSubgraph`, `TriangularSubgraph`. -- **Weight type** — `One` (unweighted), `i32`, `f64`. -- **K value** — e.g., `K3` for 3-SAT, `KN` for arbitrary K. - -
- -![Lattices](static/lattices.svg) - -
-
- -![Lattices](static/lattices-dark.svg) - -
- -Next: [register variants](design-variant-registration.md). diff --git a/docs/src/install.md b/docs/src/install.md deleted file mode 100644 index 11db6abd4..000000000 --- a/docs/src/install.md +++ /dev/null @@ -1,29 +0,0 @@ -# Install the CLI - -The `pred` executable explores models and reductions, creates instances, and runs solvers. - -## Install a release - -```bash -cargo install problemreductions-cli -pred --version -``` - -Rust and a native build toolchain are required. The default solver backend is HiGHS. - -## Build the current repository - -```bash -git clone https://github.com/CodingThrust/problem-reductions -cd problem-reductions -cargo install --path problemreductions-cli -pred --version -``` - -The website catalog is built from the repository. A published crate may have fewer models or rules; use the source build when reproducing current catalog entries. - -## Optional features - -For the MCP server, install with `--features mcp`. The CLI also exposes `cplex` and `lp-solvers` for separately installed solver backends; consult the [CLI manifest](https://github.com/CodingThrust/problem-reductions/blob/main/problemreductions-cli/Cargo.toml) before configuring one. - -Next: [first solve](cli.md) or [connect with MCP](mcp.md). diff --git a/docs/src/mcp-tools.md b/docs/src/mcp-tools.md deleted file mode 100644 index a61dd8044..000000000 --- a/docs/src/mcp-tools.md +++ /dev/null @@ -1,31 +0,0 @@ -# MCP tool reference - -[Connect the server](mcp.md), then use its advertised tool schemas as the runtime contract. Tools return JSON strings; pass the returned instance or bundle as `problem_json` to the next operation. - -## Graph queries - -| Tool | Parameters | Description | -|------|-----------|-------------| -| `list_problems` | *(none)* | List all registered problem types with aliases, variant counts, and reduction counts | -| `show_problem` | `problem` (string) | Show details for a problem type: variants, size fields, schema, and incoming/outgoing reductions | -| `neighbors` | `problem` (string), `hops` (int, default: 1), `direction` ("out"\|"in"\|"both", default: "out") | Find neighboring problems reachable via reduction edges within a given hop distance | -| `find_path` | `source` (string), `target` (string), `cost` (string, default: "minimize-steps"), `all` (bool, default: false), `max_paths` (int, default: 20) | Find a reduction path between two problems, optionally minimizing a size field or returning all paths | -| `export_graph` | *(none)* | Export the full reduction graph as JSON (nodes, edges, overheads) | - -## Instances - -| Tool | Parameters | Description | -|------|-----------|-------------| -| `create_problem` | `problem_type` (string), `params` (JSON object) | Create a problem instance from parameters and return its JSON representation. Supports graph problems, SAT, QUBO, SpinGlass, KColoring, Factoring, and random graph generation | -| `inspect_problem` | `problem_json` (string) | Inspect a problem JSON or reduction bundle: returns type, size metrics, available solvers, and reduction targets | -| `evaluate` | `problem_json` (string), `config` (array of int) | Evaluate a configuration against a problem instance and return the objective value or feasibility | -| `reduce` | `problem_json` (string), `target` (string) | Reduce a problem instance to a target type, returning a reduction bundle with the transformed instance and path metadata | -| `solve` | `problem_json` (string), `solver` ("ilp"\|"brute-force"\|"customized", default: "ilp"), `timeout` (int, default: 0) | Solve a problem instance or reduction bundle using the chosen backend, with optional timeout | - -## Prompt templates - -The server also advertises `what_is`, `model_my_problem`, `compare`, `reduce`, `solve`, `find_reduction`, and `overview`. Inspect each prompt’s declared arguments in your MCP client. - -Creation supports selected models and uses MCP parameter names such as `edges`; CLI creation flags are a separate interface. For models outside MCP creation support, create JSON with the CLI and pass it to the instance tools. - -Next: [example session](mcp-walkthrough.md). diff --git a/docs/src/mcp-walkthrough.md b/docs/src/mcp-walkthrough.md deleted file mode 100644 index b1ff2d747..000000000 --- a/docs/src/mcp-walkthrough.md +++ /dev/null @@ -1,30 +0,0 @@ -# MCP example session - -**Before you start:** [connect the server](mcp.md). This is a reproducible task specification; the [CLI recording](cli-demo.md) shows a real execution of the corresponding workflow. - -## Ask the agent - -```text -Create Maximum Independent Set on the cycle with edges -0-1, 1-2, 2-3, 3-4, 4-0. Discover a route to ILP, reduce and solve it. -Evaluate the recovered source configuration on the original instance. -Cross-check the optimum with brute force and report both evaluations. -``` - -## Expected tool sequence - -| Tool | Key input | -|---|---| -| `show_problem` | `problem: "MIS"` | -| `find_path` | `source: "MIS", target: "ILP"` | -| `create_problem` | `problem_type: "MIS", params: {"edges": "0-1,1-2,2-3,3-4,4-0"}` | -| `reduce` | Original problem JSON, `target: "ILP"` | -| `solve` | Reduction bundle JSON, `solver: "ilp"` | -| `evaluate` | Original problem JSON, recovered `config` | -| `solve` | Original problem JSON, `solver: "brute-force"` | - -## Check the result - -The maximum independent set has size 2. Both solves and the source evaluation should return `Max(2)`. Valid witnesses include `[1,0,1,0,0]` and `[0,0,1,0,1]`. - -Retain the original JSON for evaluation. Passing only the transformed target loses the context required to check the original problem. diff --git a/docs/src/mcp.md b/docs/src/mcp.md deleted file mode 100644 index d7adec2d2..000000000 --- a/docs/src/mcp.md +++ /dev/null @@ -1,31 +0,0 @@ -# Connect with MCP - -`pred mcp` exposes catalog queries, instance creation, reductions, and solving over a local stdio MCP server. - -## Install with MCP support - -```bash -cargo install problemreductions-cli --features mcp -pred mcp --help -``` - -For the current checkout, use `cargo install --path problemreductions-cli --features mcp`. MCP is an optional feature and is not included in the default CLI install. - -## Configure your client - -Register a local stdio server with executable `pred` and argument `mcp`. Clients using an `mcpServers` configuration accept this shape: - -```json -{ - "mcpServers": { - "problemreductions": { - "command": "pred", - "args": ["mcp"] - } - } -} -``` - -Ensure the client can resolve `pred` on its `PATH`, or supply the executable's absolute path. Reload the client and request the server's tool list to verify the connection. - -Next: [tool reference](mcp-tools.md) or [example session](mcp-walkthrough.md). diff --git a/docs/src/rust-paths.md b/docs/src/rust-paths.md deleted file mode 100644 index 3812533b8..000000000 --- a/docs/src/rust-paths.md +++ /dev/null @@ -1,35 +0,0 @@ -# Discover and execute paths - -Search uses exact variants. This example discovers a witness-capable route from `Factoring` to `SpinGlass`. - -```rust,ignore -use problemreductions::prelude::*; -use problemreductions::rules::{MinimizeSteps, ReductionGraph}; -use problemreductions::topology::SimpleGraph; -use problemreductions::types::ProblemSize; -{{#include ../../examples/chained_reduction_factoring_to_spinglass.rs:step1}} -``` - -## Execute the route - -```rust,ignore -let factoring = Factoring::new(2, 2, 6); -let reduction = graph - .reduce_along_path(&rpath, &factoring) - .unwrap(); -let target: &SpinGlass = reduction.target_problem(); -// Solve `target` with a compatible solver, then recover source bits: -// let source_config = reduction.extract_solution(&target_config); -``` - -`extract_solution` walks the intermediate mappings in reverse. A discovered path establishes a registered transformation, not an efficient target solve. - -The [runnable factoring example](https://github.com/CodingThrust/problem-reductions/blob/main/examples/chained_reduction_factoring_to_spinglass.rs) discovers the SpinGlass route, then separately solves factoring through a direct integer ILP reduction. Run it with: - -```bash -cargo run --example chained_reduction_factoring_to_spinglass -``` - -It checks that the recovered factors multiply to 6 and prints composed overheads. - -Next: [path costs and overhead](design-paths.md). diff --git a/docs/src/rust-reduction.md b/docs/src/rust-reduction.md deleted file mode 100644 index 4a4d56472..000000000 --- a/docs/src/rust-reduction.md +++ /dev/null @@ -1,30 +0,0 @@ -# Apply a reduction - -Reduce Maximum Set Packing to binary ILP, solve the target, and recover the original configuration. The default library features include the HiGHS backend. - -```rust -use problemreductions::prelude::*; -use problemreductions::models::algebraic::ILP; -use problemreductions::solvers::ILPSolver; - -fn main() { - let problem = MaximumSetPacking::::new(vec![ - vec![0, 1], vec![1, 2], vec![2, 3], vec![4, 5], - ]); - let reduction = ReduceTo::>::reduce_to(&problem); - let target = reduction.target_problem(); - assert_eq!(target.num_vars(), 4); - assert_eq!(target.num_constraints(), 2); - - let target_solution = ILPSolver::new().solve(target).unwrap(); - let solution = reduction.extract_solution(&target_solution); - assert_eq!(solution, vec![1, 0, 1, 1]); - println!("{}", problem.evaluate(&solution)); // Max(3) -} -``` - -The target has one binary variable per set and excludes overlapping pairs. `extract_solution` maps a target witness to the source configuration space. - -For types implementing `ReduceTo>`, `ILPSolver::new().solve_reduced(&problem)` combines these steps. It returns an optional source configuration; evaluate it against the original problem. - -Next: [discover a multi-step path](rust-paths.md). diff --git a/docs/src/rust-solvers.md b/docs/src/rust-solvers.md deleted file mode 100644 index 1a2a62263..000000000 --- a/docs/src/rust-solvers.md +++ /dev/null @@ -1,27 +0,0 @@ -# Solver contracts - -Choose a solver by its input and result contract. - -| API | Result | Scope | -|---|---|---| -| `Solver::solve` with `BruteForce` | Aggregate value | Any problem whose value implements `Aggregate` | -| `BruteForce::find_witness` | Optional configuration | Values supporting representative witnesses | -| `ILPSolver::solve` | Optional ILP configuration | ILP instances; requires an ILP backend | -| `ILPSolver::solve_reduced` | Optional source configuration | Witness-capable problems implementing `ReduceTo>` | -| `CustomizedSolver` | Model-specific exact solving | Selected models with specialized implementations | - -The aggregate `Solver::solve` contract differs from the inherent witness-returning ILP methods. Counting and other aggregate-only problems need a value, not a representative configuration. - -## Feature selection - -HiGHS is enabled by default through `ilp-highs`. To use the library without it: - -```bash -cargo add problemreductions --no-default-features -``` - -See the [solver API](api/problemreductions/solvers/index.html) for exact method signatures and supported specialized backends. - -## Verification - -For witness results, evaluate the recovered configuration against the source. For aggregate results, compare values according to the reduction's extraction contract. Use small exhaustive solves as independent checks. diff --git a/src/rules/graph.rs b/src/rules/graph.rs index ef8a27ff2..608da182b 100644 --- a/src/rules/graph.rs +++ b/src/rules/graph.rs @@ -958,25 +958,29 @@ impl ReductionGraph { /// Compute the source problem's size from a type-erased instance. /// - /// Iterates over all registered reduction entries with a matching source name - /// and merges their `source_size_fn` results to capture all size fields. - /// Different entries may reference different getter methods (e.g., one uses - /// `num_vertices` while another also uses `num_edges`). - pub fn compute_source_size(name: &str, instance: &dyn Any) -> ProblemSize { + /// Iterates over all registered reduction entries whose source name *and* + /// variant match, and merges their `source_size_fn` results to capture all + /// size fields. Different entries may reference different getter methods + /// (e.g., one uses `num_vertices` while another also uses `num_edges`). + /// Each `source_size_fn` downcasts to one concrete type, so entries for + /// other variants of the same problem must not be consulted. + pub fn compute_source_size( + name: &str, + variant: &BTreeMap, + instance: &dyn Any, + ) -> ProblemSize { let mut merged: Vec<(String, usize)> = Vec::new(); let mut seen: HashSet = HashSet::new(); for entry in inventory::iter:: { - if entry.source_name == name { - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - (entry.source_size_fn)(instance) - })); - if let Ok(size) = result { - for (k, v) in size.components { - if seen.insert(k.clone()) { - merged.push((k, v)); - } - } + if entry.source_name != name + || Self::variant_to_map(&entry.source_variant()) != *variant + { + continue; + } + for (k, v) in (entry.source_size_fn)(instance).components { + if seen.insert(k.clone()) { + merged.push((k, v)); } } } diff --git a/src/solvers/ilp/solver.rs b/src/solvers/ilp/solver.rs index 51b2a0df2..155030676 100644 --- a/src/solvers/ilp/solver.rs +++ b/src/solvers/ilp/solver.rs @@ -254,7 +254,7 @@ impl ILPSolver { instance: &dyn std::any::Any, ) -> Option { let ilp_variants = graph.variants_for("ILP"); - let input_size = crate::rules::ReductionGraph::compute_source_size(name, instance); + let input_size = crate::rules::ReductionGraph::compute_source_size(name, variant, instance); let mut best_path: Option = None; let mut best_cost = f64::INFINITY; diff --git a/src/unit_tests/rules/graph.rs b/src/unit_tests/rules/graph.rs index e915ac482..bbd8211c7 100644 --- a/src/unit_tests/rules/graph.rs +++ b/src/unit_tests/rules/graph.rs @@ -1645,15 +1645,33 @@ fn test_compute_source_size() { SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1, 1, 1, 1], ); - let size = ReductionGraph::compute_source_size("MaximumIndependentSet", &problem); + let variant = + ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant()); + let size = ReductionGraph::compute_source_size("MaximumIndependentSet", &variant, &problem); assert_eq!(size.get("num_vertices"), Some(4)); assert_eq!(size.get("num_edges"), Some(3)); } +#[test] +fn test_compute_source_size_only_consults_matching_variant() { + // Entries for other variants of the same problem must never be invoked: + // their size functions downcast to a different concrete type and would panic. + let problem = MaximumIndependentSet::::new( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]), + vec![One; 5], + ); + let variant = + ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant()); + let size = ReductionGraph::compute_source_size("MaximumIndependentSet", &variant, &problem); + assert_eq!(size.get("num_vertices"), Some(5)); + assert_eq!(size.get("num_edges"), Some(5)); +} + #[test] fn test_compute_source_size_unknown_problem() { let problem = 42u32; - let size = ReductionGraph::compute_source_size("NonExistentProblem", &problem); + let size = + ReductionGraph::compute_source_size("NonExistentProblem", &BTreeMap::new(), &problem); assert!(size.components.is_empty()); } From 79f60aab295224226ea37d1e7dd450470b9c9797 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Mon, 7 Sep 2026 08:58:07 +0800 Subject: [PATCH 2/3] Simplify the documentation site Collapse the 38-page guide into 8 pages plus a placeholder: overview, CLI quick start and command reference, reduction graph, skills, Rust getting started, API, design, and open problems. Remove repeated --help, rustdoc, and CLAUDE.md content and the per-page link chains. Drop MCP from the documentation. Re-record the CLI demo with scripts/record_cli_demo.py: blank lines between command blocks and a direct ILP solve instead of brute force. Strip decorative copy from the landing page and remove the legacy anchor redirects. Update the documentation and website test suites. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Heb7aLYWxgQE4v6TPNAQE4 --- docs/src/SUMMARY.md | 57 +--- docs/src/api.md | 2 +- docs/src/cli-commands.md | 131 ++++++++ docs/src/cli.md | 50 ++- docs/src/design.md | 109 ++++++- docs/src/getting-started.md | 73 ++++- docs/src/introduction.md | 40 +-- docs/src/open-problems.md | 3 + docs/src/reduction-graph.md | 2 +- docs/src/skills.md | 76 ++++- docs/src/static/cli-demo.cast | 512 +++++++++++++++---------------- docs/src/static/docs-theme.js | 64 ---- docs/website/README.md | 48 +-- docs/website/assets/site.js | 2 +- docs/website/index.html | 50 +-- scripts/generate_doc_snippets.sh | 9 +- scripts/record_cli_demo.py | 101 ++++++ scripts/test_documentation.py | 35 ++- scripts/test_website.py | 2 +- 19 files changed, 855 insertions(+), 511 deletions(-) create mode 100644 docs/src/cli-commands.md create mode 100644 docs/src/open-problems.md create mode 100755 scripts/record_cli_demo.py diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index c9ce4eece..a31b2ecf4 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -2,53 +2,22 @@ [Overview](introduction.md) -# Start here - -- [CLI in action](cli-demo.md) -- [Start with an agent](skills.md) -- [Install the CLI](install.md) -- [First solve](cli.md) - -# Agent workflows - -- [Find a solver](agent-find-solver.md) -- [Extend a solver's reach](agent-find-problem.md) -- [Propose a model or rule](agent-propose.md) -- [Implement and review](agent-pipeline.md) -- [Connect with MCP](mcp.md) - - [Tool reference](mcp-tools.md) - - [Example session](mcp-walkthrough.md) - -# CLI reference - -- [Explore the catalog](cli-catalog.md) -- [Names and variants](cli-variants.md) -- [Find reduction paths](cli-paths.md) -- [Create an instance](cli-create.md) - - [Input examples](cli-examples.md) - - [Random instances](cli-random.md) -- [Inspect and evaluate](cli-inspect.md) -- [Reduce an instance](cli-reduce.md) -- [Solve an instance](cli-solve.md) -- [JSON and automation](cli-automation.md) -- [Shell completions](cli-completions.md) +# CLI + +- [Quick start](cli.md) +- [Command reference](cli-commands.md) +- [Reduction graph](reduction-graph.md) + +# Agents + +- [Skills](skills.md) # Rust library -- [First Rust program](getting-started.md) -- [Apply a reduction](rust-reduction.md) -- [Discover and execute paths](rust-paths.md) -- [Solver contracts](rust-solvers.md) +- [Getting started](getting-started.md) - [API reference](api.md) +- [Design](design.md) -# Internals +# Research -- [Architecture](design.md) -- [Problem contract](design-problem.md) -- [Variant system](design-variants.md) - - [Register variants](design-variant-registration.md) -- [Reduction contracts](design-reductions.md) -- [Path costs and overhead](design-paths.md) -- [JSON serialization](design-serialization.md) -- [Reduction graph](reduction-graph.md) -- [Contributing](contributing.md) +- [Open problems](open-problems.md) diff --git a/docs/src/api.md b/docs/src/api.md index e534bb8d9..5f2b32e8c 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -4,4 +4,4 @@ The API is generated from Rust source with rustdoc. Start with [models](api/problemreductions/models/index.html), [reduction contracts](api/problemreductions/rules/index.html), or [solvers](api/problemreductions/solvers/index.html). -Run `make doc` to build the guides and API locally. For short runnable examples, see [first Rust program](getting-started.md) and [apply a reduction](rust-reduction.md). +Run `make doc` to build the guides and API locally. For short runnable examples, see [getting started](getting-started.md). diff --git a/docs/src/cli-commands.md b/docs/src/cli-commands.md new file mode 100644 index 000000000..0232d9de3 --- /dev/null +++ b/docs/src/cli-commands.md @@ -0,0 +1,131 @@ +# Command reference + +Every command accepts `--json` for structured output, `-o FILE` to save JSON, and `-q` to silence informational messages. `pred --help` lists all flags; this page shows one example per command. + +## Catalog + +```bash +pred list +pred show MIS +pred list --rules +``` + +`list` reports every problem with its aliases, variants, and reduction counts. `show` describes the resolved variant, its size fields, input schema, and incoming and outgoing reductions. Read that schema before constructing an instance. + +
+Example: pred show MIS + +```text +{{#include generated/pred-show-mis.txt}} +``` + +
+ +## Names and variants + +```bash +pred show MIS/SimpleGraph/i32 +pred path MIS/SimpleGraph/i32 ILP/bool +``` + +Aliases such as `MIS` resolve to full names, and a bare name selects the declared default variant: `MIS` is `MaximumIndependentSet/SimpleGraph/One`. Slash-separated parameters select graph, weight, or other variant values. `One` means unit weights; passing non-unit `--weights` to `create` upgrades a default instance to `i32`. Name the exact variant when a reproducible endpoint matters. + +{{#include generated/pred-aliases.txt}} + +## Paths + +```bash +pred path MIS ILP +pred path MIS QUBO --all --max-paths 50 +pred path MIS QUBO --cost minimize:num_variables -o path.json +pred from MIS --hops 2 +pred to QUBO +``` + +`path` finds the cheapest route between two endpoints; `--all` enumerates alternatives up to `--max-paths`. The default cost minimizes steps; `minimize:` uses the overhead metadata of a size field from `pred show`. `from` and `to` explore outgoing and incoming neighbors. Search defaults to reductions that can map a solution back. + +
+Example: a multi-step path from Factoring to SpinGlass + +```text +{{#include generated/pred-path-factoring-spinglass.txt}} +``` + +
+ +Overhead formulas describe scaling bounds, not exact target sizes. A discovered route does not imply the target is cheap to solve; inspect the constructed target on representative instances. + +## Create + +```bash +pred create MIS --graph 0-1,1-2,2-3 -o problem.json +pred create MIS/SimpleGraph/i32 --graph 0-1,1-2,2-3 --weights 2,1,3,1 -o weighted.json +pred create --example MVC/SimpleGraph/i32 --to MIS/SimpleGraph/i32 -o source.json +pred create MIS --random --num-vertices 10 --edge-prob 0.3 --seed 42 -o random.json +``` + +Flags follow the schema field names in kebab-case: `universe_size` becomes `--universe-size`. Vertices use zero-based indices and `--graph` is a comma-separated edge list. `--example` loads a canonical model fixture, or with `--to` the source side of a documented reduction example (`--example-side target` for the other side). `--random` generates a graph instance; save the JSON so the instance can be reproduced. + +Other input structures: + +```bash +pred create SAT --num-vars 3 --clauses '1,2;-1,3' -o sat.json # signed one-based literals; ';' separates clauses +pred create QUBO --matrix '1,0.5;0.5,2' -o qubo.json # ';' separates rows +pred create X3C --universe-size 6 --subsets '0,1,2;3,4,5;0,3,4' -o x3c.json +pred create Factoring --target 6 --m 2 --n 2 -o factoring.json +``` + +## Inspect and evaluate + +```bash +pred inspect problem.json +pred evaluate problem.json --config 1,0,1,0 +pred create MIS --graph 0-1,1-2,2-3 | pred evaluate - --config 1,0,1,0 +``` + +`inspect` reports the resolved variant and sizes of a problem file or reduction bundle. `evaluate` scores one configuration: selecting vertices 0 and 2 returns `Max(2)`, while selecting adjacent vertices returns `Max(None)`. Configurations follow each problem's variable domains and are not always binary. `-` reads from stdin. + +## Reduce + +```bash +pred reduce problem.json --to QUBO -o reduced.json +pred reduce problem.json --via path.json -o reduced.json +pred extract reduced.json --config 1,0,1,0 +``` + +The bundle contains the source instance, the target instance, and the variant-level path; keep it whole to preserve solution recovery. `--via` replays a path saved by `pred path -o`, whose source variant must match the input. `extract` maps a target-space configuration back to the source. + +## Solve + +```bash +pred solve problem.json +pred solve problem.json --solver brute-force +pred solve reduced.json --timeout 30 --json +``` + +| Solver | Behavior | +|---|---| +| `ilp` (default) | Finds a route to ILP, solves the target, and recovers a source configuration | +| `brute-force` | Enumerates all configurations; for tiny instances and cross-checks | +| `customized` | Exact structure-exploiting backends for selected models; see `pred solve --help` | + +Solving a bundle solves the target and maps the result back; JSON output records the target result under `intermediate`. If the ILP solver reports no route, check `pred path ILP`. Aggregate-only problems return a value without a configuration. To verify a result, evaluate the returned configuration on the original instance, or compare the value with an exhaustive solve on a small example. + +## JSON and pipes + +```bash +pred create MIS --graph 0-1,1-2,2-3 | pred reduce - --to QUBO | pred solve - --json +pred export-graph -o reduction_graph.json +``` + +Check the exit status before consuming a result, and enable `set -o pipefail` in scripts. Keep `type` and `variant` with each instance; an alias alone does not identify an exact endpoint. The site publishes [reduction_graph.json](reductions/reduction_graph.json) and [problem_schemas.json](reductions/problem_schemas.json) from the same registry; the local `export-graph` describes the installed version. + +## Shell completions + +```bash +eval "$(pred completions bash)" # ~/.bashrc +eval "$(pred completions zsh)" # ~/.zshrc +pred completions fish | source # ~/.config/fish/config.fish +``` + +Without an argument, `pred completions` detects the current shell. diff --git a/docs/src/cli.md b/docs/src/cli.md index 6a7282587..22198cf3c 100644 --- a/docs/src/cli.md +++ b/docs/src/cli.md @@ -1,25 +1,53 @@ -# CLI Tool +# Quick start -Create and solve your first problem with `pred`. [Install the CLI](install.md) before running these commands in a fresh working directory. - -## Create a graph problem +## Install ```bash -pred create MIS --graph 0-1,1-2,2-3,3-4,4-0 -o cycle.json +cargo install problemreductions-cli +pred --version ``` -`MIS` means Maximum Independent Set: select as many pairwise non-adjacent vertices as possible. This graph is a cycle with five vertices. +Rust and a native build toolchain are required. The default ILP backend is HiGHS; the `cplex` and `lp-solvers` features enable separately installed backends listed in the [CLI manifest](https://github.com/CodingThrust/problem-reductions/blob/main/problemreductions-cli/Cargo.toml). -## Solve and check +The published crate may lag behind the catalog on this site. To build the current checkout: ```bash +git clone https://github.com/CodingThrust/problem-reductions +cd problem-reductions +cargo install --path problemreductions-cli +``` + +## First solve + +```bash +pred create MIS --graph 0-1,1-2,2-3,3-4,4-0 -o cycle.json pred solve cycle.json pred evaluate cycle.json --config 1,0,1,0,0 -pred solve cycle.json --solver brute-force ``` -Both solvers return `Max(2)`. The configuration `1,0,1,0,0` selects vertices 0 and 2 and evaluates to the same value. Several optimal configurations exist, so the solvers may return different selections. +`MIS` is Maximum Independent Set: select as many pairwise non-adjacent vertices as possible. The graph is a cycle on five vertices. `solve` discovers a route to ILP, solves the target, and maps the solution back, reporting `Max(2)` with a configuration such as `[1, 0, 1, 0, 0]`. `evaluate` scores a configuration of your own against the same instance. Several optimal configurations exist, so the solver's choice may differ from yours. + +## Terminal session + +A recording of the real CLI: discover a route, transform the instance, solve, and check the result. + + + +[Open the player](static/cli-demo.html) · [Download the cast](static/cli-demo.cast) + +```bash +pred path MIS ILP +pred create MIS --graph 0-1,1-2,2-3,3-4,4-0 -o cycle.json +pred reduce cycle.json --to ILP -o reduced.json +pred solve reduced.json +pred evaluate cycle.json --config 1,0,1,0,0 +pred solve cycle.json +``` -The default solver discovers a route to ILP and maps the solution back. Brute force checks all configurations; keep instances small. +The route passes through Maximum Set Packing and a weight cast before reaching binary ILP. `reduced.json` keeps the source instance and the path, so solving the bundle recovers a source solution. The final command solves the original file directly and discovers the same route on its own. -Next: [watch the full run](cli-demo.md), [inspect the path](cli-paths.md), or [save a reduction bundle](cli-reduce.md). +| Check | Recorded result | +|---|---| +| Solve the reduction bundle | `Max(2)`, configuration `[1, 0, 1, 0, 0]` | +| Evaluate that configuration on the source | `Max(2)` | +| Solve the original instance directly | `Max(2)`, solver `ilp (via ILP)` | diff --git a/docs/src/design.md b/docs/src/design.md index 203d7e52c..acfbf5c79 100644 --- a/docs/src/design.md +++ b/docs/src/design.md @@ -1,6 +1,6 @@ -# Architecture +# Design -The Rust library stores problem definitions, executable reductions, and their registry metadata. The CLI and MCP server expose that core to tools and agents. +The Rust library holds problem definitions, executable reductions, and their registry metadata. The CLI exposes that core to tools and agents. | Location | Responsibility | |---|---| @@ -8,13 +8,12 @@ The Rust library stores problem definitions, executable reductions, and their re | `src/rules/` | Reduction implementations and solution/value mappings | | `src/registry/` | Concrete variant metadata and dynamic dispatch | | `src/solvers/` | Exhaustive, ILP, specialized, and decision-search solvers | -| `problemreductions-cli/` | CLI and optional MCP server | | `src/example_db/` | Canonical model and rule examples | | `src/unit_tests/` | Tests mirroring the source tree | +| `problemreductions-cli/` | The `pred` CLI | ## Module map -
@@ -32,4 +31,104 @@ The Rust library stores problem definitions, executable reductions, and their re
-Read the [problem contract](design-problem.md), [reduction contracts](design-reductions.md), and [variant system](design-variants.md) before extending the library. +## Problem contract + +Every problem implements `Problem`. `evaluate()` returns the associated `Value` for one configuration; solvers fold those values across the configuration space defined by `dims()`. + +```rust,ignore +trait Problem { + const NAME: &'static str; // e.g., "MaximumIndependentSet" + type Value: Clone; // e.g., Max, Or, Sum + fn dims(&self) -> Vec; // configuration space per variable + fn evaluate(&self, config: &[usize]) -> Self::Value; + fn variant() -> Vec<(&'static str, &'static str)>; // e.g., [("graph", "SimpleGraph"), ("weight", "i32")] + fn num_variables(&self) -> usize; // default: dims().len() + fn problem_type() -> ProblemType; // default: registry lookup by NAME +} +``` + +A four-vertex independent set problem has `dims() = [2, 2, 2, 2]`; `evaluate(&[1, 0, 1, 0])` returns `Max(Some(2))` if vertices 0 and 2 are non-adjacent and `Max(None)` otherwise. Witness-capable objective problems use `Max`, `Min`, or `Extremum`; feasibility problems use `Or`; aggregate-only problems such as counting use `Sum` or `And` and solve to a value without a representative configuration. Each problem also provides inherent getters such as `num_vertices()` that reduction overhead expressions reference. + +## Variants + +One problem name can have several **variants**: weights on vertices, or a restricted topology such as a king's subgraph. Variants form a subtype hierarchy, and the reduction from a more specific variant to a less specific one is a **variant cast**, an identity mapping that preserves indices. + +
+ +![Variant Hierarchy](static/variant-hierarchy.svg) + +
+
+ +![Variant Hierarchy](static/variant-hierarchy-dark.svg) + +
+ +Variant parameters fall into three categories: graph type (`SimpleGraph` at the root, then `PlanarGraph`, `BipartiteGraph`, `UnitDiskGraph`, `KingsSubgraph`, `TriangularSubgraph`), weight type (`One`, `i32`, `f64`), and K value (`K3` for 3-SAT, `KN` for arbitrary K). + +
+ +![Lattices](static/lattices.svg) + +
+
+ +![Lattices](static/lattices-dark.svg) + +
+ +Each parameter type implements `VariantParam`, declaring its category, value, and optional parent; types with a parent also implement `CastToParent` for the runtime conversion. `Problem::variant()` is composed from the type parameters with `variant_params![G, W]`. The macros `impl_variant_param!`, `impl_variant_reduction!`, and `declare_variants!` register parameter types, explicit variant casts, and concrete variants with their load, serialize, and solve metadata. Their current contract is documented in the [repository instructions](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/CLAUDE.md) and visible in any model file. + +## Reductions + +A reduction connects exact source and target variants. Its capability determines how a result is recovered: + +| Capability | Contract | Example | +|---|---|---| +| Witness | `ReduceTo` and `ReductionResult::extract_solution` | Solve a target, recover a source configuration | +| Aggregate | `ReduceToAggregate` and `AggregateReductionResult::extract_value` | Solve a target value, recover a source value | +| Turing | Multiple target queries | Optimize by querying a decision problem at several bounds | + +Graph search defaults to witness mode; `ReductionMode::Aggregate` and `ReductionMode::Turing` select the others. A witness reduction is registered with the `#[reduction]` attribute, whose `overhead` block is required: + +```rust,ignore +#[reduction(overhead = { + num_vertices = "num_vertices", + num_edges = "num_edges", +})] +impl ReduceTo> + for MaximumIndependentSet +{ + // Provide Result and reduce_to(); the result owns the target + // and maps a vertex-cover witness to its independent-set complement. +} +``` + +See a [complete implementation](https://github.com/CodingThrust/problem-reductions/blob/main/src/rules/maximumindependentset_minimumvertexcover.rs) for the result type. Aggregate and Turing edges use manual `ReductionEntry` registration. Keep one primitive registration per exact endpoint pair. Correctness needs a proof that construction and extraction preserve the required result; a closed-loop test on a small example is evidence, not proof. + +## Path costs and overhead + +`ReductionGraph` searches a directed graph of exact `(name, variant)` pairs. Registered reductions carry capabilities; natural variant connections follow the graph and weight subtype relations. + +| Cost | Purpose | +|---|---| +| `MinimizeSteps` | Fewest reduction steps | +| `Minimize("field")` | Cost based on an output size field | +| `CustomCost(closure)` | User-defined edge cost from overhead and current size | + +`find_cheapest_path` takes source and target variant maps, an input `ProblemSize`, and a cost; `find_all_paths` enumerates simple paths with a bound. Overhead expressions refer to getters on the source type and are validated at compile time. They describe scaling bounds, not exact target counts: for a concrete instance inspect the constructed target, and for a chain use `path_overheads` and `compose_path_overhead` for an end-to-end bound. A shorter route can produce a harder target, so compare target sizes and solver measurements as well as hop counts. + +## JSON serialization + +```rust,ignore +use problemreductions::io::{to_json, from_json}; + +let json: String = to_json(&problem)?; +let restored: MaximumIndependentSet = from_json(&json)?; +``` + +These helpers serialize typed problem data. The CLI additionally wraps data with `type` and `variant` for dynamic loading; keep that wrapper when passing files between commands. + +## Contributing + +Propose a model or reduction through the `propose` [skill](skills.md) or the [issue templates](https://github.com/CodingThrust/problem-reductions/issues/new/choose). A useful rule includes exact endpoint variants, a construction, solution or value extraction, a correctness argument, and overhead metadata. The `add-model` and `add-rule` skills list the required code, tests, examples, and paper changes; `.claude/CLAUDE.md` holds the conventions. diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index 06f077157..90f238f6d 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -1,12 +1,12 @@ -# First Rust program - -Add the library to a Rust project: +# Getting started ```bash cargo add problemreductions ``` -## Solve a small set-packing instance +HiGHS is enabled by default through the `ilp-highs` feature. Add `--no-default-features` to use the library without an ILP backend. + +## Solve a small instance ```rust use problemreductions::prelude::*; @@ -21,8 +21,67 @@ fn main() { } ``` -The optimal packing selects sets 0, 2, and 3 and evaluates to `Max(3)`. The witness vector is `[1,0,1,1]`. +The optimal packing selects sets 0, 2, and 3: the witness is `[1, 0, 1, 1]` and evaluates to `Max(3)`. `Problem::evaluate` scores a configuration; `BruteForce` enumerates the configuration space, so keep exhaustive examples small. + +## Apply a reduction + +Reduce the same instance to binary ILP, solve the target, and recover the original configuration: + +```rust +use problemreductions::prelude::*; +use problemreductions::models::algebraic::ILP; +use problemreductions::solvers::ILPSolver; + +fn main() { + let problem = MaximumSetPacking::::new(vec![ + vec![0, 1], vec![1, 2], vec![2, 3], vec![4, 5], + ]); + let reduction = ReduceTo::>::reduce_to(&problem); + let target = reduction.target_problem(); + assert_eq!(target.num_vars(), 4); + assert_eq!(target.num_constraints(), 2); + + let target_solution = ILPSolver::new().solve(target).unwrap(); + let solution = reduction.extract_solution(&target_solution); + assert_eq!(solution, vec![1, 0, 1, 1]); + println!("{}", problem.evaluate(&solution)); // Max(3) +} +``` + +The target has one binary variable per set and one constraint per overlapping pair. `extract_solution` maps a target witness back to the source configuration space. For any type implementing `ReduceTo>`, `ILPSolver::new().solve_reduced(&problem)` combines these steps and returns an optional source configuration. + +## Discover and run a path + +Search uses exact variants. This discovers a route from `Factoring` to `SpinGlass` and executes it: + +```rust,ignore +use problemreductions::prelude::*; +use problemreductions::rules::{MinimizeSteps, ReductionGraph}; +use problemreductions::topology::SimpleGraph; +use problemreductions::types::ProblemSize; +{{#include ../../examples/chained_reduction_factoring_to_spinglass.rs:step1}} + +let factoring = Factoring::new(2, 2, 6); +let reduction = graph.reduce_along_path(&rpath, &factoring).unwrap(); +let target: &SpinGlass = reduction.target_problem(); +// Solve `target` with a compatible solver, then recover source bits: +// let source_config = reduction.extract_solution(&target_config); +``` + +`extract_solution` walks the intermediate mappings in reverse. The full [example](https://github.com/CodingThrust/problem-reductions/blob/main/examples/chained_reduction_factoring_to_spinglass.rs) also solves factoring through a direct ILP reduction and checks that the recovered factors multiply to 6: + +```bash +cargo run --example chained_reduction_factoring_to_spinglass +``` + +## Solver contracts -`Problem::evaluate` checks a configuration; `BruteForce` enumerates the configuration space to find an optimum. Keep exhaustive examples small. +| API | Result | Scope | +|---|---|---| +| `Solver::solve` with `BruteForce` | Aggregate value | Any problem whose value implements `Aggregate` | +| `BruteForce::find_witness` | Optional configuration | Values supporting representative witnesses | +| `ILPSolver::solve` | Optional ILP configuration | ILP instances; requires an ILP backend | +| `ILPSolver::solve_reduced` | Optional source configuration | Problems implementing `ReduceTo>` | +| `CustomizedSolver` | Model-specific exact solving | Selected models with specialized implementations | -Next: [apply an ILP reduction](rust-reduction.md) or [read solver contracts](rust-solvers.md). +Counting and other aggregate-only problems produce a value, not a representative configuration. For witness results, evaluate the recovered configuration against the source; for aggregate results, compare values through the reduction's extraction contract. Method signatures are in the [solver API](api/problemreductions/solvers/index.html). diff --git a/docs/src/introduction.md b/docs/src/introduction.md index 85eee55a6..49570e0a3 100644 --- a/docs/src/introduction.md +++ b/docs/src/introduction.md @@ -1,29 +1,29 @@ -# Documentation +# Problem Reductions -Use executable reductions to connect a hard problem to a solver. Explore the catalog, transform an instance, and check the recovered solution. These guides are organized around small tasks for agents and their operators. +Problem Reductions is a Rust library and command-line tool for NP-hard problems and the reductions between them. Each problem is a model with a configuration space and an objective. Each reduction is a registered rule that maps an instance of one problem to an instance of another and maps solutions back. Searching the reduction graph yields a route from a problem to a solver, such as integer linear programming, with recovery of a solution to the original instance. The catalog currently holds: -## See what works +{{#include generated/catalog-counts.md}} -[Watch the CLI solve a graph problem →](cli-demo.md) +- [Atlas](index.html#atlas): every problem variant and reduction, with schemas and overheads +- [Paper](reductions.pdf): definitions, constructions, and proofs +- [Rust API](api/problemreductions/index.html): generated from source -A real terminal run: discover a route, reduce to ILP, recover an independent set, and cross-check the optimum. Six commands, about half a minute. +This guide covers the `pred` CLI, the agent skills shipped with the repository, and the Rust library. Every page has a **Markdown** link for reading without a browser; the [Markdown index](markdown/index.md) lists all pages. -## Choose a task +## Cite -| You have… | Start here | You get… | -|---|---|---| -| A problem to solve | [Find a solver](agent-find-solver.md) | A model, reduction route, and solver recommendation | -| A solver to reuse | [Extend its reach](agent-find-problem.md) | Reachable source problems and size overheads | -| A candidate connection | [Propose a model or rule](agent-propose.md) | A precise research proposal | -| An approved issue | [Implement and review](agent-pipeline.md) | A tested implementation for review | -| An instance to run | [First solve](cli.md) | A solution checked against the original problem | - -## Give an agent the right context - -Start with [agent setup](skills.md). Every page has a **Markdown** link for direct reading; the [Markdown index](markdown/index.md) lists all tasks. Use [JSON exports](cli-automation.md) for registry data and [MCP](mcp.md) for tool access. +```bibtex +@misc{pan2026problemreductionsscaleagentic, + title = {Problem Reductions at Scale: Agentic Integration of Computationally Hard Problems}, + author = {Xi-Wei Pan and Shi-Wen An and Jin-Guo Liu}, + year = {2026}, + eprint = {2604.11535}, + archivePrefix = {arXiv}, + primaryClass = {cs.AI}, + url = {https://arxiv.org/abs/2604.11535}, +} +``` ## Research scope -The long-term goal is autonomous discovery of new reduction rules. Today, this repository provides executable models, registered reductions, solver routing, and agent workflows for proposals, implementation, and review. A candidate rule needs a mathematical argument as well as tests; passing finite examples does not establish a general proof. - -[Explore the atlas](index.html#atlas) · [Read the mathematical manual](reductions.pdf) · [Browse the Rust API](api.md) +The long-term goal is autonomous discovery of new reduction rules. Today the repository provides executable models, registered reductions, solver routing, and agent workflows for proposals, implementation, and review. A rule needs a mathematical argument as well as tests; passing finite examples does not establish a general proof. diff --git a/docs/src/open-problems.md b/docs/src/open-problems.md new file mode 100644 index 000000000..c893dd887 --- /dev/null +++ b/docs/src/open-problems.md @@ -0,0 +1,3 @@ +# Open problems + +To be released. diff --git a/docs/src/reduction-graph.md b/docs/src/reduction-graph.md index 8af87ecad..fb87fbb55 100644 --- a/docs/src/reduction-graph.md +++ b/docs/src/reduction-graph.md @@ -32,4 +32,4 @@ You can also explore this graph from the terminal with the [CLI tool](./cli.md). For theoretical background and correctness proofs, see the [PDF manual](https://codingthrust.github.io/problem-reductions/reductions.pdf). -For exact variants and structured output, use [CLI path queries](cli-paths.md). +For exact variants and structured output, use [CLI path queries](cli-commands.md#paths). diff --git a/docs/src/skills.md b/docs/src/skills.md index d382df1c4..c60b6665d 100644 --- a/docs/src/skills.md +++ b/docs/src/skills.md @@ -1,8 +1,8 @@ -# Start with an agent +# Skills -An agent can use the CLI directly from a repository checkout. Skills supply the task procedure; the CLI supplies current model and reduction data. +Skills are task procedures under `.claude/skills//SKILL.md` in the repository. An agent reads one and follows it, using the `pred` CLI for current model, variant, and path data. They work with Claude Code as slash commands (`/find-solver`) and with any agent that can read files. -## Prepare the workspace +## Setup ```bash git clone https://github.com/CodingThrust/problem-reductions @@ -10,9 +10,7 @@ cd problem-reductions make cli ``` -This installs `pred` into Cargo’s binary directory (usually `~/.cargo/bin`). Confirm it is on your shell’s `PATH` with `pred --version`. - -## Give the agent this context +This installs `pred` into Cargo's binary directory. Give the agent this context: ```text Work in this repository. Read AGENTS.md and .claude/CLAUDE.md. @@ -21,10 +19,68 @@ Read the SKILL.md for the task before following its workflow. Report the exact model variant, commands, results, and unresolved assumptions. ``` -Then choose [find a solver](agent-find-solver.md), [extend a solver's reach](agent-find-problem.md), or [propose a rule](agent-propose.md). +Every skill is invoked the same way. Name the skill, then describe the task: + +```text +Read .claude/skills/find-solver/SKILL.md and follow it. +My problem: [inputs, constraints, and objective]. +Typical size: [counts and ranges]. +``` + +## Use the atlas + +| Skill | What it produces | +|---|---| +| `find-solver` | Matches a real-world problem to a library model, explores reduction routes, and recommends solvers. Writes a solution document to `docs/solutions/`. | +| `find-problem` | The reverse: given a solver for one model, lists the source problems it can handle through incoming reductions, ranked by effective complexity. | + +Ask the agent to construct a small instance, solve it, and evaluate the recovered configuration on the original problem before scaling up. A solver for a target handles sources that reduce **to** it; a route in the other direction establishes nothing. + +## Contribute + +| Skill | What it produces | +|---|---| +| `propose` | Turns a definition or a candidate source-to-target connection into a precise proposal and files a GitHub issue. | +| `check-issue` | Quality gate for `[Model]` and `[Rule]` issues: usefulness, non-triviality, literature, and writing. Posts a report. | +| `fix-issue` | Fixes problems found by `check-issue`, then re-checks and moves the issue to Ready. | +| `issue-to-pr` | Converts an approved issue into a pull request with an implementation plan. | +| `add-model` | Adds a problem model: source, variants, tests, canonical example, and paper entry. | +| `add-rule` | Adds a reduction rule with the same artifacts, verified mathematically by default. | +| `verify-reduction` | Standalone verification of a rule: Typst proof, a constructor script, and an adversary script with thousands of independent checks. | +| `fix-pr` | Resolves review comments, CI failures, and coverage gaps on a pull request. | +| `write-model-in-paper`, `write-rule-in-paper` | Write or improve an entry in the Typst paper. | + +For a rule, distinguish a construction supported by literature from a new conjecture, and record proof gaps and counterexamples explicitly. + +## Maintain + +| Skill | What it produces | +|---|---| +| `run-pipeline` | Takes one Ready issue from the project board through implementation to the Review pool. | +| `review-pipeline` | Agentic review of a pull request: structural check, quality check, and feature tests. Moves it to Final review. | +| `review-structural`, `review-quality` | The two read-only sub-reviews, usable on their own. | +| `final-review` | Interactive maintainer review, then merge or hold. | +| `auto-pipeline` | Chains the steps above from a Backlog issue to Final review. | +| `topology-sanity-check` | Detects isolated problems, missing NP-hardness chains from 3-SAT, and dominated rules. | +| `review-paper` | Reviews ten paper entries for mechanical and critical issues. | +| `release` | Determines the version bump, verifies tests, and tags a release. | +| `dev-setup` | Installs and configures the development tools. | +| `update-papers` | Downloads referenced papers and regenerates the collection index. | + +The corresponding make targets run in a configured maintainer checkout: + +```bash +make run-issue N=42 # implement one issue +make run-pipeline # pick the next Ready issue +make run-review N=570 # review one pull request +``` + +A passing test suite is evidence for the tested instances, not a proof for all inputs. Keep the mathematical argument, the constructor and adversary checks, closed-loop tests, and review findings with the work. + +## Reading without a browser -## Read without a browser +The [Markdown index](markdown/index.md) lists every page of this guide with code includes expanded. [reduction_graph.json](reductions/reduction_graph.json) and [problem_schemas.json](reductions/problem_schemas.json) provide the registry as structured data. -Use the [Markdown index](markdown/index.md) to retrieve only the relevant task pages. Code includes are expanded in those files. The [graph and schemas](cli-automation.md#registry-exports) provide structured data. +## Authorship -An agent with MCP support can instead [connect to `pred mcp`](mcp.md). Repository skills still require access to the checkout. +Contributors of ten non-trivial reduction rules are added to the author list of the [paper](reductions.pdf). The software is MIT licensed. diff --git a/docs/src/static/cli-demo.cast b/docs/src/static/cli-demo.cast index cd9afb3e8..af70441f6 100644 --- a/docs/src/static/cli-demo.cast +++ b/docs/src/static/cli-demo.cast @@ -1,267 +1,251 @@ {"version": 2, "width": 88, "height": 22, "title": "From a graph to a verified solution", "env": {"TERM": "xterm-256color", "SHELL": "/bin/bash"}} [0.0, "o", "\u001b[90m# Discover a route to an available solver\u001b[0m\r\n"] [0.45, "o", "$ "] -[0.75, "o", "."] -[0.798061, "o", "/"] -[0.831401, "o", "p"] -[0.852611, "o", "r"] -[0.899118, "o", "e"] -[0.946418, "o", "d"] -[0.98372, "o", " "] -[1.023524, "o", "p"] -[1.046874, "o", "a"] -[1.089336, "o", "t"] -[1.116966, "o", "h"] -[1.138829, "o", " "] -[1.181915, "o", "M"] -[1.212605, "o", "I"] -[1.251057, "o", "S"] -[1.2893, "o", " "] -[1.31446, "o", "I"] -[1.340586, "o", "L"] -[1.36479, "o", "P"] -[1.566199, "o", "\r\n"] -[1.746434, "o", "Path (3 steps): MaximumIndependentSet/SimpleGraph/One → MaximumSetPacking/One → ILP/bool\r\n\r\n Step 1: MaximumIndependentSet/SimpleGraph/One → MaximumSetPacking/One\r\n num_sets = O(num_vertices)\r\n universe_size = O(num_edges)\r\n\r\n Step 2: MaximumSetPacking/One → MaximumSetPacking/i32\r\n num_sets = O(num_sets)\r\n universe_size = O(universe_size)\r\n\r\n Step 3: MaximumSetPacking/i32 → ILP/bool\r\n num_vars = O(num_sets)\r\n num_constraints = O(universe_size)\r\n\r\n Overall:\r\n num_vars = O(num_vertices)\r\n num_constraints = O(num_edges)\r\n\r\n"] -[4.246434, "o", "\u001b[90m# Create a five-vertex cycle\u001b[0m\r\n"] -[4.696434, "o", "$ "] -[4.996434, "o", "."] -[5.031063, "o", "/"] -[5.07908, "o", "p"] -[5.101888, "o", "r"] -[5.138038, "o", "e"] -[5.172084, "o", "d"] -[5.209925, "o", " "] -[5.233415, "o", "c"] -[5.270627, "o", "r"] -[5.299175, "o", "e"] -[5.335755, "o", "a"] -[5.374805, "o", "t"] -[5.409274, "o", "e"] -[5.440221, "o", " "] -[5.468197, "o", "M"] -[5.515335, "o", "I"] -[5.54903, "o", "S"] -[5.584875, "o", " "] -[5.606415, "o", "-"] -[5.641642, "o", "-"] -[5.662804, "o", "g"] -[5.687829, "o", "r"] -[5.722069, "o", "a"] -[5.753634, "o", "p"] -[5.776151, "o", "h"] -[5.813602, "o", " "] -[5.839194, "o", "0"] -[5.875799, "o", "-"] -[5.900838, "o", "1"] -[5.948083, "o", ","] -[5.99067, "o", "1"] -[6.038464, "o", "-"] -[6.063419, "o", "2"] -[6.09297, "o", ","] -[6.115078, "o", "2"] -[6.143828, "o", "-"] -[6.187411, "o", "3"] -[6.213376, "o", ","] -[6.238704, "o", "3"] -[6.286436, "o", "-"] -[6.311764, "o", "4"] -[6.356113, "o", ","] -[6.378262, "o", "4"] -[6.410075, "o", "-"] -[6.440864, "o", "0"] -[6.471432, "o", " "] -[6.515293, "o", "-"] -[6.549619, "o", "o"] -[6.59254, "o", " "] -[6.626723, "o", "c"] -[6.670609, "o", "y"] -[6.716293, "o", "c"] -[6.749602, "o", "l"] -[6.792471, "o", "e"] -[6.836284, "o", "."] -[6.865563, "o", "j"] -[6.890032, "o", "s"] -[6.916229, "o", "o"] -[6.949439, "o", "n"] -[7.153784, "o", "\r\n"] -[7.304369, "o", "Wrote cycle.json\r\n"] -[9.804369, "o", "\u001b[90m# Transform the instance and preserve the way back\u001b[0m\r\n"] -[10.254369, "o", "$ "] -[10.554369, "o", "."] -[10.590203, "o", "/"] -[10.634427, "o", "p"] -[10.669013, "o", "r"] -[10.712909, "o", "e"] -[10.752288, "o", "d"] -[10.791237, "o", " "] -[10.821907, "o", "r"] -[10.862582, "o", "e"] -[10.90626, "o", "d"] -[10.931661, "o", "u"] -[10.978084, "o", "c"] -[11.006626, "o", "e"] -[11.031961, "o", " "] -[11.076495, "o", "c"] -[11.117658, "o", "y"] -[11.160878, "o", "c"] -[11.194385, "o", "l"] -[11.217367, "o", "e"] -[11.249433, "o", "."] -[11.27177, "o", "j"] -[11.300782, "o", "s"] -[11.322846, "o", "o"] -[11.358034, "o", "n"] -[11.381488, "o", " "] -[11.428607, "o", "-"] -[11.46919, "o", "-"] -[11.499066, "o", "t"] -[11.546527, "o", "o"] -[11.569382, "o", " "] -[11.59759, "o", "I"] -[11.620634, "o", "L"] -[11.65357, "o", "P"] -[11.680223, "o", " "] -[11.712329, "o", "-"] -[11.753066, "o", "o"] -[11.798911, "o", " "] -[11.833926, "o", "r"] -[11.877845, "o", "e"] -[11.90896, "o", "d"] -[11.954003, "o", "u"] -[11.989424, "o", "c"] -[12.030092, "o", "e"] -[12.055983, "o", "d"] -[12.093349, "o", "."] -[12.122799, "o", "j"] -[12.166559, "o", "s"] -[12.202521, "o", "o"] -[12.237509, "o", "n"] -[12.460162, "o", "\r\n"] -[12.611677, "o", "Wrote reduced.json\r\n"] -[15.111677, "o", "\u001b[90m# Solve the target and recover a source solution\u001b[0m\r\n"] -[15.561677, "o", "$ "] -[15.861677, "o", "."] -[15.89805, "o", "/"] -[15.928395, "o", "p"] -[15.953061, "o", "r"] -[15.991547, "o", "e"] -[16.038457, "o", "d"] -[16.083043, "o", " "] -[16.105996, "o", "s"] -[16.136104, "o", "o"] -[16.157151, "o", "l"] -[16.197043, "o", "v"] -[16.235915, "o", "e"] -[16.278136, "o", " "] -[16.303288, "o", "r"] -[16.330365, "o", "e"] -[16.363482, "o", "d"] -[16.405111, "o", "u"] -[16.43191, "o", "c"] -[16.475949, "o", "e"] -[16.507623, "o", "d"] -[16.553103, "o", "."] -[16.600984, "o", "j"] -[16.637033, "o", "s"] -[16.683823, "o", "o"] -[16.71664, "o", "n"] -[16.936828, "o", "\r\n"] -[17.089047, "o", "Problem: MaximumIndependentSet\r\nSolver: ilp (via ILP)\r\nSolution: [1, 0, 1, 0, 0]\r\nEvaluation: Max(2)\r\n\r\nHint: use -o to save full solution details (including intermediate results) as JSON.\r\n"] -[19.589047, "o", "\u001b[90m# Check an independent set of size two\u001b[0m\r\n"] -[20.039047, "o", "$ "] -[20.339047, "o", "."] -[20.38376, "o", "/"] -[20.428246, "o", "p"] -[20.451846, "o", "r"] -[20.480135, "o", "e"] -[20.512622, "o", "d"] -[20.557641, "o", " "] -[20.58639, "o", "e"] -[20.610575, "o", "v"] -[20.642085, "o", "a"] -[20.669073, "o", "l"] -[20.710128, "o", "u"] -[20.747578, "o", "a"] -[20.770048, "o", "t"] -[20.815616, "o", "e"] -[20.848952, "o", " "] -[20.891705, "o", "c"] -[20.917334, "o", "y"] -[20.946955, "o", "c"] -[20.970008, "o", "l"] -[21.002068, "o", "e"] -[21.035078, "o", "."] -[21.075196, "o", "j"] -[21.109624, "o", "s"] -[21.141839, "o", "o"] -[21.176222, "o", "n"] -[21.205097, "o", " "] -[21.236444, "o", "-"] -[21.274258, "o", "-"] -[21.301027, "o", "c"] -[21.336556, "o", "o"] -[21.383587, "o", "n"] -[21.417394, "o", "f"] -[21.457107, "o", "i"] -[21.496583, "o", "g"] -[21.54002, "o", " "] -[21.581336, "o", "1"] -[21.620835, "o", ","] -[21.644739, "o", "0"] -[21.676005, "o", ","] -[21.719239, "o", "1"] -[21.742064, "o", ","] -[21.766985, "o", "0"] -[21.813335, "o", ","] -[21.850799, "o", "0"] -[22.079244, "o", "\r\n"] -[22.220535, "o", "Max(2)\r\n"] -[24.720535, "o", "\u001b[90m# Cross-check the optimum by exhaustive search\u001b[0m\r\n"] -[25.170535, "o", "$ "] -[25.470535, "o", "."] -[25.512635, "o", "/"] -[25.561514, "o", "p"] -[25.590875, "o", "r"] -[25.631085, "o", "e"] -[25.661342, "o", "d"] -[25.708829, "o", " "] -[25.738055, "o", "s"] -[25.764502, "o", "o"] -[25.808302, "o", "l"] -[25.837115, "o", "v"] -[25.873068, "o", "e"] -[25.91093, "o", " "] -[25.94144, "o", "c"] -[25.971798, "o", "y"] -[26.001788, "o", "c"] -[26.030278, "o", "l"] -[26.072549, "o", "e"] -[26.110047, "o", "."] -[26.146398, "o", "j"] -[26.190474, "o", "s"] -[26.218674, "o", "o"] -[26.243513, "o", "n"] -[26.265307, "o", " "] -[26.287353, "o", "-"] -[26.336092, "o", "-"] -[26.384359, "o", "s"] -[26.424024, "o", "o"] -[26.472762, "o", "l"] -[26.508201, "o", "v"] -[26.541368, "o", "e"] -[26.576796, "o", "r"] -[26.599728, "o", " "] -[26.641996, "o", "b"] -[26.671541, "o", "r"] -[26.712087, "o", "u"] -[26.758553, "o", "t"] -[26.785702, "o", "e"] -[26.820824, "o", "-"] -[26.860112, "o", "f"] -[26.903819, "o", "o"] -[26.943936, "o", "r"] -[26.974207, "o", "c"] -[27.011004, "o", "e"] -[27.216555, "o", "\r\n"] -[27.358887, "o", "Problem: MaximumIndependentSet\r\nSolver: brute-force\r\nSolution: [0, 0, 1, 0, 1]\r\nEvaluation: Max(2)\r\n\r\nHint: use -o to save full solution details as JSON.\r\n"] -[29.858887, "o", "$ "] +[0.476395, "o", "."] +[0.497848, "o", "/"] +[0.533589, "o", "p"] +[0.552802, "o", "r"] +[0.585255, "o", "e"] +[0.612846, "o", "d"] +[0.631646, "o", " "] +[0.663287, "o", "p"] +[0.681502, "o", "a"] +[0.711034, "o", "t"] +[0.730173, "o", "h"] +[0.749908, "o", " "] +[0.77918, "o", "M"] +[0.819947, "o", "I"] +[0.840627, "o", "S"] +[0.864148, "o", " "] +[0.899218, "o", "I"] +[0.943438, "o", "L"] +[0.977069, "o", "P"] +[1.277069, "o", "\r\n"] +[1.427069, "o", "Path (3 steps): \u001b[1mMaximumIndependentSet/SimpleGraph/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mMaximumSetPacking/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mILP/bool\u001b[0m\r\n\r\n \u001b[36mStep 1\u001b[39m: \u001b[1mMaximumIndependentSet/SimpleGraph/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mMaximumSetPacking/One\u001b[0m\r\n num_sets = O(num_vertices)\r\n universe_size = O(num_edges)\r\n\r\n \u001b[36mStep 2\u001b[39m: \u001b[1mMaximumSetPacking/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mMaximumSetPacking/i32\u001b[0m\r\n num_sets = O(num_sets)\r\n universe_size = O(universe_size)\r\n\r\n \u001b[36mStep 3\u001b[39m: \u001b[1mMaximumSetPacking/i32\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mILP/bool\u001b[0m\r\n num_vars = O(num_sets)\r\n num_constraints = O(universe_size)\r\n\r\n \u001b[36mOverall\u001b[39m:\r\n num_vars = O(num_vertices)\r\n num_constraints = O(num_edges)\r\n\r\n"] +[4.127069, "o", "\r\n"] +[4.127069, "o", "\u001b[90m# Create a five-vertex cycle\u001b[0m\r\n"] +[4.577069, "o", "$ "] +[4.605546, "o", "."] +[4.650582, "o", "/"] +[4.669056, "o", "p"] +[4.710726, "o", "r"] +[4.736143, "o", "e"] +[4.757408, "o", "d"] +[4.777916, "o", " "] +[4.803873, "o", "c"] +[4.844334, "o", "r"] +[4.86664, "o", "e"] +[4.9004, "o", "a"] +[4.935798, "o", "t"] +[4.96358, "o", "e"] +[4.996373, "o", " "] +[5.01531, "o", "M"] +[5.034156, "o", "I"] +[5.057183, "o", "S"] +[5.093766, "o", " "] +[5.123126, "o", "-"] +[5.149244, "o", "-"] +[5.183117, "o", "g"] +[5.213208, "o", "r"] +[5.238916, "o", "a"] +[5.278755, "o", "p"] +[5.31587, "o", "h"] +[5.339987, "o", " "] +[5.373542, "o", "0"] +[5.40569, "o", "-"] +[5.447837, "o", "1"] +[5.485821, "o", ","] +[5.511191, "o", "1"] +[5.556338, "o", "-"] +[5.576855, "o", "2"] +[5.605944, "o", ","] +[5.644719, "o", "2"] +[5.666205, "o", "-"] +[5.697318, "o", "3"] +[5.715581, "o", ","] +[5.751816, "o", "3"] +[5.790803, "o", "-"] +[5.824318, "o", "4"] +[5.866475, "o", ","] +[5.892582, "o", "4"] +[5.92959, "o", "-"] +[5.963715, "o", "0"] +[5.997427, "o", " "] +[6.027604, "o", "-"] +[6.068746, "o", "o"] +[6.11288, "o", " "] +[6.143568, "o", "c"] +[6.179687, "o", "y"] +[6.198563, "o", "c"] +[6.235748, "o", "l"] +[6.271381, "o", "e"] +[6.316898, "o", "."] +[6.357524, "o", "j"] +[6.382798, "o", "s"] +[6.410964, "o", "o"] +[6.447211, "o", "n"] +[6.747211, "o", "\r\n"] +[6.897211, "o", "Wrote cycle.json\r\n"] +[9.597211, "o", "\r\n"] +[9.597211, "o", "\u001b[90m# Transform the instance and preserve the way back\u001b[0m\r\n"] +[10.047211, "o", "$ "] +[10.064999, "o", "."] +[10.095333, "o", "/"] +[10.117277, "o", "p"] +[10.137765, "o", "r"] +[10.156593, "o", "e"] +[10.195685, "o", "d"] +[10.216523, "o", " "] +[10.240741, "o", "r"] +[10.269054, "o", "e"] +[10.311094, "o", "d"] +[10.33054, "o", "u"] +[10.360516, "o", "c"] +[10.393357, "o", "e"] +[10.43574, "o", " "] +[10.476291, "o", "c"] +[10.518119, "o", "y"] +[10.543217, "o", "c"] +[10.572225, "o", "l"] +[10.599618, "o", "e"] +[10.642024, "o", "."] +[10.686531, "o", "j"] +[10.707985, "o", "s"] +[10.730163, "o", "o"] +[10.753933, "o", "n"] +[10.777743, "o", " "] +[10.808742, "o", "-"] +[10.842717, "o", "-"] +[10.867367, "o", "t"] +[10.884627, "o", "o"] +[10.913739, "o", " "] +[10.941432, "o", "I"] +[10.974756, "o", "L"] +[11.019131, "o", "P"] +[11.056002, "o", " "] +[11.087873, "o", "-"] +[11.122661, "o", "o"] +[11.159124, "o", " "] +[11.17781, "o", "r"] +[11.220653, "o", "e"] +[11.260081, "o", "d"] +[11.30221, "o", "u"] +[11.342149, "o", "c"] +[11.370503, "o", "e"] +[11.399045, "o", "d"] +[11.419146, "o", "."] +[11.454412, "o", "j"] +[11.473333, "o", "s"] +[11.4924, "o", "o"] +[11.515508, "o", "n"] +[11.815508, "o", "\r\n"] +[11.965508, "o", "Wrote reduced.json\r\n"] +[14.665508, "o", "\r\n"] +[14.665508, "o", "\u001b[90m# Solve the target and recover a source solution\u001b[0m\r\n"] +[15.115508, "o", "$ "] +[15.137288, "o", "."] +[15.164147, "o", "/"] +[15.182792, "o", "p"] +[15.199941, "o", "r"] +[15.221406, "o", "e"] +[15.241448, "o", "d"] +[15.268979, "o", " "] +[15.286851, "o", "s"] +[15.328975, "o", "o"] +[15.363662, "o", "l"] +[15.385049, "o", "v"] +[15.4094, "o", "e"] +[15.436468, "o", " "] +[15.464015, "o", "r"] +[15.484668, "o", "e"] +[15.526066, "o", "d"] +[15.571583, "o", "u"] +[15.60204, "o", "c"] +[15.633007, "o", "e"] +[15.652604, "o", "d"] +[15.672666, "o", "."] +[15.699599, "o", "j"] +[15.724306, "o", "s"] +[15.76513, "o", "o"] +[15.786886, "o", "n"] +[16.086886, "o", "\r\n"] +[16.236886, "o", "Problem: MaximumIndependentSet\r\nSolver: ilp (via ILP)\r\nSolution: [1, 0, 1, 0, 0]\r\nEvaluation: Max(2)\r\n\r\nHint: use -o to save full solution details (including intermediate results) as JSON.\r\n"] +[18.936886, "o", "\r\n"] +[18.936886, "o", "\u001b[90m# Check an independent set of size two\u001b[0m\r\n"] +[19.386886, "o", "$ "] +[19.404689, "o", "."] +[19.449002, "o", "/"] +[19.481238, "o", "p"] +[19.50257, "o", "r"] +[19.535232, "o", "e"] +[19.553147, "o", "d"] +[19.585379, "o", " "] +[19.630479, "o", "e"] +[19.672288, "o", "v"] +[19.709323, "o", "a"] +[19.733926, "o", "l"] +[19.761546, "o", "u"] +[19.783461, "o", "a"] +[19.82266, "o", "t"] +[19.855019, "o", "e"] +[19.894421, "o", " "] +[19.920983, "o", "c"] +[19.944498, "o", "y"] +[19.984827, "o", "c"] +[20.030111, "o", "l"] +[20.071615, "o", "e"] +[20.111788, "o", "."] +[20.152312, "o", "j"] +[20.190594, "o", "s"] +[20.214215, "o", "o"] +[20.246148, "o", "n"] +[20.27345, "o", " "] +[20.29142, "o", "-"] +[20.309361, "o", "-"] +[20.334488, "o", "c"] +[20.359036, "o", "o"] +[20.395965, "o", "n"] +[20.440437, "o", "f"] +[20.470357, "o", "i"] +[20.514272, "o", "g"] +[20.559645, "o", " "] +[20.604073, "o", "1"] +[20.631634, "o", ","] +[20.655076, "o", "0"] +[20.6787, "o", ","] +[20.701463, "o", "1"] +[20.724445, "o", ","] +[20.759419, "o", "0"] +[20.802285, "o", ","] +[20.84344, "o", "0"] +[21.14344, "o", "\r\n"] +[21.29344, "o", "Max(2)\r\n"] +[23.99344, "o", "\r\n"] +[23.99344, "o", "\u001b[90m# Solve the original instance with the default solver\u001b[0m\r\n"] +[24.44344, "o", "$ "] +[24.474282, "o", "."] +[24.510082, "o", "/"] +[24.550071, "o", "p"] +[24.569636, "o", "r"] +[24.605653, "o", "e"] +[24.64879, "o", "d"] +[24.688284, "o", " "] +[24.726859, "o", "s"] +[24.75766, "o", "o"] +[24.779904, "o", "l"] +[24.819593, "o", "v"] +[24.846237, "o", "e"] +[24.88626, "o", " "] +[24.931165, "o", "c"] +[24.959617, "o", "y"] +[24.988228, "o", "c"] +[25.032423, "o", "l"] +[25.070274, "o", "e"] +[25.092274, "o", "."] +[25.113047, "o", "j"] +[25.134508, "o", "s"] +[25.177504, "o", "o"] +[25.21769, "o", "n"] +[25.51769, "o", "\r\n"] +[25.66769, "o", "Problem: MaximumIndependentSet\r\nSolver: ilp (via ILP)\r\nSolution: [1, 0, 1, 0, 0]\r\nEvaluation: Max(2)\r\n\r\nHint: use -o to save full solution details as JSON.\r\n"] +[28.36769, "o", "$ "] diff --git a/docs/src/static/docs-theme.js b/docs/src/static/docs-theme.js index 3c11dccb7..1f2ecb37d 100644 --- a/docs/src/static/docs-theme.js +++ b/docs/src/static/docs-theme.js @@ -14,70 +14,6 @@ }); } const page = location.pathname.split("/").pop() || "introduction.html"; - const legacy = { - "cli.html": { - installation: "install.html", - "ilp-backend": "install.html#optional-features", - "quick-start": "cli.html", - "global-flags": "cli-automation.html", - commands: "cli-catalog.html", - "shell-completions": "cli-completions.html", - "json-output": "cli-automation.html", - "problem-name-aliases": "cli-variants.html", - "pred-list--list-all-problem-types": "cli-catalog.html", - "pred-show--inspect-a-problem": "cli-catalog.html", - "pred-to--explore-incoming-neighbors": "cli-paths.html", - "pred-from--explore-outgoing-neighbors": "cli-paths.html", - "pred-path--find-a-reduction-path": "cli-paths.html", - "pred-export-graph--export-the-reduction-graph": - "cli-automation.html#registry-exports", - "pred-create--create-a-problem-instance": "cli-create.html", - "pred-evaluate--evaluate-a-configuration": "cli-inspect.html", - "pred-inspect--inspect-a-problem-file": "cli-inspect.html", - "pred-reduce--reduce-a-problem": "cli-reduce.html", - "pred-solve--solve-a-problem": "cli-solve.html", - }, - "introduction.html": { - "reduction-graph": "reduction-graph.html", - "our-vision": "introduction.html#research-scope", - "call-for-contributions": "contributing.html", - authorship: "contributing.html#authorship-and-license", - }, - "design.html": { - "module-architecture": "design.html#module-map", - "problem-model": "design-problem.html", - "variant-system": "design-variants.html", - "reduction-rules": "design-reductions.html", - "reduction-graph": "design-paths.html", - "path-finding": "design-paths.html", - "executable-paths": "rust-paths.html", - solvers: "rust-solvers.html", - "json-serialization": "design-serialization.html", - contributing: "contributing.html", - }, - "getting-started.html": { - installation: "getting-started.html", - solvers: "rust-solvers.html", - "the-reduction-workflow": "rust-reduction.html", - "json-resources": "cli-automation.html#registry-exports", - "example-1-direct-reduction--set-packing-to-ilp": "rust-reduction.html", - "example-2-reduction-path-search--integer-factoring-to-spin-glass": - "rust-paths.html", - }, - "mcp.html": { - setup: "mcp.html#install-with-mcp-support", - walkthrough: "mcp-walkthrough.html", - "available-tools": "mcp-tools.html", - "graph-query-tools": "mcp-tools.html#graph-queries", - "instance-tools": "mcp-tools.html#instances", - "available-prompts": "mcp-tools.html#prompt-templates", - }, - }; - const redirect = legacy[page]?.[location.hash.slice(1)]; - if (redirect) { - location.replace(root + redirect); - return; - } const sidebar = document.querySelector(".sidebar"); if (sidebar) { const brand = document.createElement("a"); diff --git a/docs/website/README.md b/docs/website/README.md index ed2ef0f6e..344bf852b 100644 --- a/docs/website/README.md +++ b/docs/website/README.md @@ -68,33 +68,20 @@ clipboard actions, the mathematical example, legacy docs, and mobile overflow. ## Documentation -The guide uses short task pages grouped in `docs/src/SUMMARY.md`: start here, -agent workflows, CLI reference, Rust library, and internals. Keep one task or -contract per page and link prerequisites and next steps. Agent prompts name the -actual repository skill file; instructions distinguish implemented capabilities -from the research goal. +The guide has nine pages listed in `docs/src/SUMMARY.md`: an overview, the CLI +(quick start, command reference, reduction graph), agent skills, the Rust library +(getting started, API, design), and a research placeholder. Keep one example per +concept and link to `pred --help`, rustdoc, or `.claude/CLAUDE.md` instead of +restating them. `docs/src/static/docs-theme.css` supplies the restrained dark reading theme; -`docs-theme.js` adds Markdown access and redirects old section links. Native -mdBook search, code copying, and sidebar navigation remain available. Both -mdBook 0.4.37 (deployment) and 0.5.2 are supported. +`docs-theme.js` adds the Markdown links. Native mdBook search, code copying, and +sidebar navigation remain available. Both mdBook 0.4.37 (deployment) and 0.5.2 +are supported. The website builder emits `book/markdown/` from the same source pages, expanding Rust and generated-output includes. The Markdown index is the entry point for -agents. The builder preserves the Markdown index URL after mdBook’s HTML link rewriting. - -To check the complete assembled documentation, build the API and PDF first: - -```sh -make doc -make paper -cp docs/paper/reductions.pdf book/reductions.pdf -uv run --no-project --with playwright python scripts/test_documentation.py -``` - -The checks cover task navigation, search, clipboard, Markdown links and includes, -legacy section redirects, mobile layout, offline playback, and replaying the -recorded CLI workflow against its expected optimum. +agents. ## CLI recording @@ -103,13 +90,12 @@ commands against the current CLI. `cli-demo.html` embeds that cast and asciinema-player 3.8.0 for offline playback, with autoplay disabled. The bundled player is Apache-2.0 licensed; its license is alongside the recording. -To refresh the recording, use the `how-to-demo-cli` skill from -[qude-software-skills](https://github.com/QudeLeap/qude-software-skills/tree/main/skills/how-to-demo-cli). -Build the CLI, create an isolated temporary directory, and place a `pred` symlink -to the new executable there. Record the commands in `docs/src/cli-demo.md`, using -`./pred` in that directory, at 88 columns × 22 rows with `--theme nord`, -`--typing-speed 35`, `--step-pause 2.5`, and `--no-autoplay`. +To refresh the recording after a CLI change, build the CLI and run: + +```sh +python3 scripts/record_cli_demo.py --pred target/debug/pred +``` -Retain real output and exit codes; never substitute a simulated successful run. -The checked-in HTML adds the documentation palette and a compact embedded mode -to the skill's generated wrapper. Preserve those wrapper styles when refreshing. +The script runs the commands listed in `docs/src/cli.md` in a temporary +directory, simulates typing, and fails if any command exits non-zero. Never +substitute a simulated successful run. diff --git a/docs/website/assets/site.js b/docs/website/assets/site.js index 651bd079e..f8b3894b6 100644 --- a/docs/website/assets/site.js +++ b/docs/website/assets/site.js @@ -639,7 +639,7 @@ "connections", "main", ].includes(parts[0]); - document.title = `${baseTitle} — A new route through hard problems`; + document.title = baseTitle; if (isHome) { main.innerHTML = homeHTML; hydrateHome(); diff --git a/docs/website/index.html b/docs/website/index.html index 86ef0109f..9e1d07935 100644 --- a/docs/website/index.html +++ b/docs/website/index.html @@ -18,7 +18,7 @@ content="The open infrastructure for autonomous research in computational complexity." /> - Problem Reductions — A new route through hard problems + Problem Reductions
-

- A new route
through - hard
- problems.
-

+

Problem
Reductions

- An executable atlas of computational problems.
- A foundation for AI agents to discover what connects them. + An executable atlas of NP-hard problems
+ and the reductions between them.

-

- Open source. - Executable mathematics. Inspectable evidence. -

@@ -121,7 +109,7 @@

-

A growing body of
executable knowledge.

+

Atlas

__PROBLEM_COUNT__Problem families
@@ -139,7 +127,7 @@

-

One problem.
Many possibilities.

+

Problems and reductions

A different formulation can unlock a different solver. Explore the @@ -159,7 +147,7 @@

One problem.
Many possibilities.

-

Knowledge that
builds on itself.

+

Research loop

@@ -215,17 +203,17 @@

Grow the atlas

-
# Install the research toolkit
+          
# Install the CLI
 $ cargo install problemreductions-cli
 
 # Explore a problem
@@ -272,9 +260,6 @@ 

Give your research
a place to start.

# Search for a route to a solver $ pred path MIS ILP
- Connect your agent with MCP
@@ -288,7 +273,6 @@

Give your research
a place to start.

width="372" height="107" /> -

Hard problems. Shared knowledge. New possibilities.

"$OUT/pred-aliases.txt" -# 13. Factoring example output (path discovery line + overhead) +# 13. Catalog counts for the guide overview (same registry as the atlas) +"$PRED" list --json | python3 -c " +import json, sys +data = json.load(sys.stdin) +print(f\"**{data['num_types']}** problem families · **{data['num_variant_nodes']}** concrete variants · **{data['num_reductions']}** directed reductions\") +" > "$OUT/catalog-counts.md" + +# 14. Factoring example output (path discovery line + overhead) FACTORING_OUTPUT=$(cargo run --example chained_reduction_factoring_to_spinglass 2>/dev/null) echo "$FACTORING_OUTPUT" | head -1 > "$OUT/factoring-path.txt" echo "$FACTORING_OUTPUT" | sed -n '2p' > "$OUT/factoring-result.txt" diff --git a/scripts/record_cli_demo.py b/scripts/record_cli_demo.py new file mode 100755 index 000000000..08660c0bd --- /dev/null +++ b/scripts/record_cli_demo.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""Record the documentation CLI demo as an asciinema v2 cast from real output. + +Usage: scripts/record_cli_demo.py [--pred target/debug/pred] [-o docs/src/static/cli-demo.cast] + +Each command runs in a pseudo-terminal inside a temporary directory holding a +`pred` symlink to the given binary. Typing is simulated; output is never +edited. The script fails if any command exits non-zero. +""" + +import argparse +import json +import os +import pty +import random +import shlex +import subprocess +import tempfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +WIDTH, HEIGHT = 88, 22 +TYPING_DELAY = 1 / 35 +STEP_PAUSE = 2.5 + +# (comment, command) pairs, run in order. +STEPS = [ + ("Discover a route to an available solver", "./pred path MIS ILP"), + ("Create a five-vertex cycle", "./pred create MIS --graph 0-1,1-2,2-3,3-4,4-0 -o cycle.json"), + ("Transform the instance and preserve the way back", "./pred reduce cycle.json --to ILP -o reduced.json"), + ("Solve the target and recover a source solution", "./pred solve reduced.json"), + ("Check an independent set of size two", "./pred evaluate cycle.json --config 1,0,1,0,0"), + ("Solve the original instance with the default solver", "./pred solve cycle.json"), +] + + +def run_in_pty(command, cwd): + master, slave = pty.openpty() + env = dict(os.environ, TERM="xterm-256color", COLUMNS=str(WIDTH), LINES=str(HEIGHT)) + process = subprocess.Popen(shlex.split(command), cwd=cwd, stdin=slave, stdout=slave, + stderr=slave, env=env, close_fds=True) + os.close(slave) + chunks = [] + while True: + try: + data = os.read(master, 65536) + except OSError: + break + if not data: + break + chunks.append(data) + os.close(master) + code = process.wait() + if code != 0: + raise SystemExit(f"{command!r} exited with {code}:\n{b''.join(chunks).decode()}") + return b"".join(chunks).decode() + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--pred", default=ROOT / "target/debug/pred", type=Path) + parser.add_argument("-o", "--output", default=ROOT / "docs/src/static/cli-demo.cast", type=Path) + parser.add_argument("--seed", default=7, type=int) + args = parser.parse_args() + if not args.pred.exists(): + raise SystemExit(f"CLI binary not found: {args.pred}") + + rng = random.Random(args.seed) + events, clock = [], 0.0 + + def emit(text, delay=0.0): + nonlocal clock + clock += delay + events.append([round(clock, 6), "o", text]) + + with tempfile.TemporaryDirectory(prefix="pred-demo-") as work: + os.symlink(args.pred.resolve(), Path(work) / "pred") + for index, (comment, command) in enumerate(STEPS): + if index: + emit("\r\n", 0.2) + emit(f"\x1b[90m# {comment}\x1b[0m\r\n") + emit("$ ", 0.45) + for char in command: + emit(char, TYPING_DELAY * rng.uniform(0.6, 1.6)) + emit("\r\n", 0.3) + emit(run_in_pty(command, work).replace("\r\n", "\n").replace("\n", "\r\n"), 0.15) + clock += STEP_PAUSE + emit("$ ", 0.2) + + header = {"version": 2, "width": WIDTH, "height": HEIGHT, + "title": "From a graph to a verified solution", + "env": {"TERM": "xterm-256color", "SHELL": "/bin/bash"}} + with args.output.open("w") as handle: + handle.write(json.dumps(header) + "\n") + for event in events: + handle.write(json.dumps(event) + "\n") + print(f"Wrote {args.output} ({len(events)} events, {clock:.1f}s)") + + +if __name__ == "__main__": + main() diff --git a/scripts/test_documentation.py b/scripts/test_documentation.py index 561f7ed71..7f47adaad 100644 --- a/scripts/test_documentation.py +++ b/scripts/test_documentation.py @@ -40,15 +40,18 @@ def test_markdown_pages_have_expanded_includes_and_valid_local_links(self): continue self.assertTrue((page.parent / unquote(parsed.path)).exists(), f"{filename}: {target}") - self.assertIn("let graph = ReductionGraph::new()", (BOOK / "markdown/rust-paths.md").read_text()) + self.assertIn("let graph = ReductionGraph::new()", (BOOK / "markdown/getting-started.md").read_text()) def test_recording_contains_real_solution_and_does_not_autoplay(self): events = [json.loads(line) for line in (ROOT / "docs/src/static/cli-demo.cast").read_text().splitlines()] self.assertEqual(events[0]["version"], 2) output = "".join(event[2] for event in events[1:]) - self.assertIn("Solver: ilp (via ILP)", output) - self.assertIn("Solver: brute-force", output) + self.assertEqual(output.count("Solver: ilp (via ILP)"), 2) + self.assertNotIn("brute-force", output) + self.assertNotIn("panicked", output) self.assertGreaterEqual(output.count("Max(2)"), 3) + # Command blocks are separated by a blank line. + self.assertEqual(output.count("\r\n\r\n\x1b[90m#"), 5) self.assertIn('"autoPlay": false', (ROOT / "docs/src/static/cli-demo.html").read_text()) def test_demo_replays_and_matches_the_recorded_optimum(self): @@ -65,7 +68,7 @@ def run(*args): run("create", "MIS", "--graph", "0-1,1-2,2-3,3-4,4-0", "-o", "cycle.json") run("reduce", "cycle.json", "--to", "ILP", "-o", "reduced.json") reduced = json.loads(run("solve", "reduced.json", "--json")) - direct = json.loads(run("solve", "cycle.json", "--solver", "brute-force", "--json")) + direct = json.loads(run("solve", "cycle.json", "--json")) self.assertEqual(reduced["evaluation"], "Max(2)") self.assertEqual(direct["evaluation"], reduced["evaluation"]) self.assertIn("Max(2)", run("evaluate", "cycle.json", "--config", @@ -99,10 +102,10 @@ def tearDown(self): def visit(self, filename): self.page.goto(urljoin(self.base, filename)) - def test_task_navigation_and_markdown(self): + def test_sidebar_navigation_and_markdown(self): self.visit("introduction.html") - self.page.locator("main").get_by_role("link", name="Find a solver", exact=True).click() - expect(self.page.locator("main h1")).to_have_text("Find a solver") + self.page.locator(".sidebar").get_by_role("link", name="Skills", exact=True).click() + expect(self.page.locator("main h1")).to_have_text("Skills") href = self.page.get_by_role("navigation", name="Documentation resources").get_by_role("link", name="Markdown", exact=True).get_attribute("href") result = self.context.request.get(urljoin(self.page.url, href)) self.assertEqual(result.status, 200) @@ -119,16 +122,14 @@ def test_search_finds_a_specific_task(self): self.assertGreater(results.locator("a").count(), 0) def test_copy_commands(self): - self.visit("cli-demo.html") - self.page.locator("main pre").hover() + self.visit("cli.html") + self.page.locator("main pre").first.hover() self.page.locator("main .clip-button").first.click() - self.assertIn("pred reduce cycle.json --to ILP", self.page.evaluate("navigator.clipboard.readText()")) + self.assertIn("cargo install problemreductions-cli", self.page.evaluate("navigator.clipboard.readText()")) - def test_old_deep_links_reach_the_new_task(self): - for old, title in [("cli.html#installation", "Install the CLI"), - ("design.html#reduction-rules", "Reduction contracts"), - ("mcp.html#walkthrough", "MCP example session")]: - self.visit(old) + def test_readme_links_resolve(self): + for filename, title in [("cli.html", "Quick start"), ("skills.html", "Skills"), ("design.html", "Design")]: + self.visit(filename) expect(self.page.locator("main h1")).to_have_text(title) def test_recording_loads_offline_and_plays(self): @@ -141,13 +142,13 @@ def test_recording_loads_offline_and_plays(self): def test_mobile_menu_and_embedded_player_fit(self): self.page.set_viewport_size({"width": 390, "height": 844}) - self.visit("cli-demo.html") + self.visit("cli.html") frame = self.page.frame_locator(".cli-cast") expect(frame.locator(".ap-control-bar")).to_be_visible() self.assertTrue(frame.locator("body").evaluate("e => e.scrollHeight <= innerHeight + 2")) self.assertTrue(self.page.locator("body").evaluate("e => e.scrollWidth <= innerWidth")) self.page.locator("#sidebar-toggle, #mdbook-sidebar-toggle").click() - expect(self.page.locator(".sidebar").get_by_role("link", name="Start with an agent", exact=True)).to_be_visible() + expect(self.page.locator(".sidebar").get_by_role("link", name="Skills", exact=True)).to_be_visible() if __name__ == "__main__": diff --git a/scripts/test_website.py b/scripts/test_website.py index 8c0bf5556..40fbcc958 100644 --- a/scripts/test_website.py +++ b/scripts/test_website.py @@ -132,7 +132,7 @@ def test_documentation_is_reachable(self): self.visit() self.page.locator(".docs-nav").click() self.assertTrue(self.page.url.endswith("/introduction.html")) - expect(self.page.get_by_role("heading", name="Documentation", exact=True)).to_be_visible() + expect(self.page.get_by_role("heading", name="Problem Reductions", exact=True)).to_be_visible() self.assertNotIn("{{#include", self.page.locator("body").inner_text()) From c21c9e3bb683ccc3aa52392d321cef5e8d0464f0 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Mon, 7 Sep 2026 09:37:32 +0800 Subject: [PATCH 3/3] Polish the website and documentation Homepage: drop the decorative labels around the hero graph, the overlay card, the "Atlas" stats label, the category tagline, and the example pill. Set the "Problem Reductions" title and section headings in one typeface. Rename the "Infrastructure" nav item to match its "Get started" section. Remove the now-unused CSS. Docs: replace the Markdown/All pages header links with GitHub and Atlas links. Replace the interactive module map on the Design page with a "Depends on" column in the module table and delete the module-graph generator, assets, and unused module-overview diagram. CLI recording: the player page embedded a stale copy of the cast without blank lines between commands. The recording script now collapses trailing blank lines and embeds the cast into cli-demo.html itself. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01WQFSR77SahSqX36EpNCPUT --- .github/workflows/docs.yml | 1 - Makefile | 4 - book.toml | 4 +- docs/src/design.md | 41 +-- docs/src/static/cli-demo.cast | 2 +- docs/src/static/cli-demo.html | 5 +- docs/src/static/docs-theme.js | 7 +- docs/src/static/module-graph.css | 53 --- docs/src/static/module-graph.js | 257 -------------- docs/src/static/module-overview-dark.svg | 413 ----------------------- docs/src/static/module-overview.svg | 413 ----------------------- docs/src/static/module-overview.typ | 40 --- docs/website/README.md | 7 +- docs/website/assets/site.css | 150 +------- docs/website/assets/site.js | 7 +- docs/website/index.html | 43 +-- examples/export_module_graph.rs | 343 ------------------- scripts/gen_module_graph.py | 247 -------------- scripts/record_cli_demo.py | 22 +- scripts/test_documentation.py | 6 +- 20 files changed, 62 insertions(+), 2003 deletions(-) delete mode 100644 docs/src/static/module-graph.css delete mode 100644 docs/src/static/module-graph.js delete mode 100644 docs/src/static/module-overview-dark.svg delete mode 100644 docs/src/static/module-overview.svg delete mode 100644 docs/src/static/module-overview.typ delete mode 100644 examples/export_module_graph.rs delete mode 100644 scripts/gen_module_graph.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b6b58ec6b..a262a1d0a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -48,7 +48,6 @@ jobs: cargo run --example export_graph node scripts/generate_reduction_graph_layout.js cargo run --example export_schemas - cargo run --example export_module_graph cargo build -p problemreductions-cli --bin pred bash scripts/generate_doc_snippets.sh target/debug/pred diff --git a/Makefile b/Makefile index 1c3bbdfa9..c18221e67 100644 --- a/Makefile +++ b/Makefile @@ -93,7 +93,6 @@ doc: node_modules/elkjs/package.json cargo run --example export_graph node scripts/generate_reduction_graph_layout.js cargo run --example export_schemas - cargo run --example export_module_graph cargo build -p problemreductions-cli --bin pred bash scripts/generate_doc_snippets.sh target/debug/pred mdbook build @@ -106,7 +105,6 @@ doc: node_modules/elkjs/package.json website: cargo run --example export_graph cargo run --example export_schemas - cargo run --example export_module_graph cargo build -p problemreductions-cli --bin pred bash scripts/generate_doc_snippets.sh target/debug/pred mdbook build @@ -131,8 +129,6 @@ mdbook: node_modules/elkjs/package.json @node scripts/generate_reduction_graph_layout.js 2>&1 | tail -1 @echo "Exporting schemas..." @cargo run --example export_schemas 2>&1 | tail -1 - @echo "Exporting module graph..." - @cargo run --example export_module_graph 2>&1 | tail -1 @echo "Generating CLI doc snippets..." @cargo build -p problemreductions-cli --bin pred @bash scripts/generate_doc_snippets.sh target/debug/pred diff --git a/book.toml b/book.toml index 5c64bdbd7..d090aa848 100644 --- a/book.toml +++ b/book.toml @@ -9,8 +9,8 @@ src = "docs/src" default-theme = "navy" git-repository-url = "https://github.com/CodingThrust/problem-reductions" edit-url-template = "https://github.com/CodingThrust/problem-reductions/edit/main/{path}" -additional-css = ["docs/src/static/docs-theme.css", "docs/src/static/theme-images.css", "docs/src/static/reduction-graph.css", "docs/src/static/module-graph.css"] -additional-js = ["docs/src/static/docs-theme.js", "docs/src/static/cytoscape.min.js", "docs/src/static/reduction-graph.js", "docs/src/static/module-graph.js"] +additional-css = ["docs/src/static/docs-theme.css", "docs/src/static/theme-images.css", "docs/src/static/reduction-graph.css"] +additional-js = ["docs/src/static/docs-theme.js", "docs/src/static/cytoscape.min.js", "docs/src/static/reduction-graph.js"] no-section-label = true [output.html.fold] diff --git a/docs/src/design.md b/docs/src/design.md index acfbf5c79..32bdccb3c 100644 --- a/docs/src/design.md +++ b/docs/src/design.md @@ -2,34 +2,19 @@ The Rust library holds problem definitions, executable reductions, and their registry metadata. The CLI exposes that core to tools and agents. -| Location | Responsibility | -|---|---| -| `src/models/` | Models grouped by graph, formula, set, algebraic, or miscellaneous input | -| `src/rules/` | Reduction implementations and solution/value mappings | -| `src/registry/` | Concrete variant metadata and dynamic dispatch | -| `src/solvers/` | Exhaustive, ILP, specialized, and decision-search solvers | -| `src/example_db/` | Canonical model and rule examples | -| `src/unit_tests/` | Tests mirroring the source tree | -| `problemreductions-cli/` | The `pred` CLI | - -## Module map - -
-
-
- Core - Models - Rules - Registry - Solvers - Utilities -
-
-
- Click a module to expand/collapse its public items. - Double-click to open rustdoc. -
-
+Each layer depends only on the layers above it. + +| Location | Responsibility | Depends on | +|---|---|---| +| `src/traits.rs`, `src/types.rs`, `src/variant.rs`, `src/topology/` | Core: the `Problem` trait, aggregate values, variant parameters, graph types | — | +| `src/models/` | Models grouped by graph, formula, set, algebraic, or miscellaneous input | Core | +| `src/rules/` | Reduction implementations and solution/value mappings | Models | +| `src/registry/` | Concrete variant metadata and dynamic dispatch | Rules | +| `src/solvers/` | Exhaustive, ILP, specialized, and decision-search solvers | Core | +| `src/io.rs`, `src/expr.rs` | JSON serialization and overhead expressions | Core | +| `src/example_db/` | Canonical model and rule examples | Models, rules | +| `src/unit_tests/` | Tests mirroring the source tree | Everything | +| `problemreductions-cli/` | The `pred` CLI | The library | ## Problem contract diff --git a/docs/src/static/cli-demo.cast b/docs/src/static/cli-demo.cast index af70441f6..0a10f372d 100644 --- a/docs/src/static/cli-demo.cast +++ b/docs/src/static/cli-demo.cast @@ -21,7 +21,7 @@ [0.943438, "o", "L"] [0.977069, "o", "P"] [1.277069, "o", "\r\n"] -[1.427069, "o", "Path (3 steps): \u001b[1mMaximumIndependentSet/SimpleGraph/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mMaximumSetPacking/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mILP/bool\u001b[0m\r\n\r\n \u001b[36mStep 1\u001b[39m: \u001b[1mMaximumIndependentSet/SimpleGraph/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mMaximumSetPacking/One\u001b[0m\r\n num_sets = O(num_vertices)\r\n universe_size = O(num_edges)\r\n\r\n \u001b[36mStep 2\u001b[39m: \u001b[1mMaximumSetPacking/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mMaximumSetPacking/i32\u001b[0m\r\n num_sets = O(num_sets)\r\n universe_size = O(universe_size)\r\n\r\n \u001b[36mStep 3\u001b[39m: \u001b[1mMaximumSetPacking/i32\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mILP/bool\u001b[0m\r\n num_vars = O(num_sets)\r\n num_constraints = O(universe_size)\r\n\r\n \u001b[36mOverall\u001b[39m:\r\n num_vars = O(num_vertices)\r\n num_constraints = O(num_edges)\r\n\r\n"] +[1.427069, "o", "Path (3 steps): \u001b[1mMaximumIndependentSet/SimpleGraph/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mMaximumSetPacking/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mILP/bool\u001b[0m\r\n\r\n \u001b[36mStep 1\u001b[39m: \u001b[1mMaximumIndependentSet/SimpleGraph/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mMaximumSetPacking/One\u001b[0m\r\n num_sets = O(num_vertices)\r\n universe_size = O(num_edges)\r\n\r\n \u001b[36mStep 2\u001b[39m: \u001b[1mMaximumSetPacking/One\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mMaximumSetPacking/i32\u001b[0m\r\n num_sets = O(num_sets)\r\n universe_size = O(universe_size)\r\n\r\n \u001b[36mStep 3\u001b[39m: \u001b[1mMaximumSetPacking/i32\u001b[0m \u001b[32m\u2192\u001b[39m \u001b[1mILP/bool\u001b[0m\r\n num_vars = O(num_sets)\r\n num_constraints = O(universe_size)\r\n\r\n \u001b[36mOverall\u001b[39m:\r\n num_vars = O(num_vertices)\r\n num_constraints = O(num_edges)\r\n"] [4.127069, "o", "\r\n"] [4.127069, "o", "\u001b[90m# Create a five-vertex cycle\u001b[0m\r\n"] [4.577069, "o", "$ "] diff --git a/docs/src/static/cli-demo.html b/docs/src/static/cli-demo.html index f47b16c75..185580188 100644 --- a/docs/src/static/cli-demo.html +++ b/docs/src/static/cli-demo.html @@ -2388,16 +2388,15 @@

From a graph to a verified solution

-

6 command(s) · 6 passed · 0 failed

-
Recorded with how-to-demo-cli · asciinema-player 3.8.0 · self-contained & offline.
+
Recorded with scripts/record_cli_demo.py · asciinema-player 3.8.0 · self-contained & offline.
diff --git a/docs/src/static/docs-theme.js b/docs/src/static/docs-theme.js index 1f2ecb37d..9b0c1e0e4 100644 --- a/docs/src/static/docs-theme.js +++ b/docs/src/static/docs-theme.js @@ -43,14 +43,13 @@ nav.className = "docs-tools"; nav.setAttribute("aria-label", "Documentation resources"); const links = [ - ["Markdown", "markdown/" + page.replace(/\.html$/, ".md")], - ["All pages", "markdown/index.md"], - ["Open atlas ↗", "index.html#atlas"], + ["GitHub ↗", "https://github.com/CodingThrust/problem-reductions"], + ["Atlas ↗", root + "index.html#atlas"], ]; for (const [label, href] of links) { const link = document.createElement("a"); link.textContent = label; - link.href = root + href; + link.href = href; nav.append(link); } menu.prepend(nav); diff --git a/docs/src/static/module-graph.css b/docs/src/static/module-graph.css deleted file mode 100644 index 6689e74c6..000000000 --- a/docs/src/static/module-graph.css +++ /dev/null @@ -1,53 +0,0 @@ -#module-graph { - width: 100%; - height: 550px; - border: 1px solid var(--sidebar-bg); - border-radius: 4px; - background: var(--bg); -} - -#mg-controls { - margin-top: 8px; - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: 8px; - font-family: sans-serif; - font-size: 13px; - color: var(--fg); -} - -#mg-legend span.swatch { - display: inline-block; - width: 14px; - height: 14px; - border: 1px solid #999; - margin-right: 3px; - vertical-align: middle; - border-radius: 2px; -} - -#mg-tooltip { - display: none; - position: absolute; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--sidebar-bg); - padding: 6px 10px; - border-radius: 4px; - font-family: sans-serif; - font-size: 13px; - box-shadow: 0 2px 8px rgba(0,0,0,0.15); - pointer-events: none; - z-index: 1000; - max-width: 300px; -} - -#mg-help { - margin-top: 6px; - font-family: sans-serif; - font-size: 12px; - color: var(--fg); - opacity: 0.6; -} diff --git a/docs/src/static/module-graph.js b/docs/src/static/module-graph.js deleted file mode 100644 index 1e131ffab..000000000 --- a/docs/src/static/module-graph.js +++ /dev/null @@ -1,257 +0,0 @@ -document.addEventListener('DOMContentLoaded', function() { - var container = document.getElementById('module-graph'); - if (!container) return; - - var categoryColors = { - core: '#c8f0c8', - model_graph: '#c8c8f0', - model_formula: '#c8c8f0', - model_set: '#c8c8f0', - model_algebraic: '#c8c8f0', - model_misc: '#c8c8f0', - rule: '#f0d8b0', - registry: '#b0e0f0', - solver: '#d0f0d0', - utility: '#e0e0e0' - }; - var categoryBorders = { - core: '#4a8c4a', - model_graph: '#4a4a8c', - model_formula: '#4a4a8c', - model_set: '#4a4a8c', - model_algebraic: '#4a4a8c', - model_misc: '#4a4a8c', - rule: '#8c6a4a', - registry: '#4a6a8c', - solver: '#4a8c6a', - utility: '#888888' - }; - var kindIcons = { - 'struct': 'S', 'enum': 'E', 'function': 'fn', 'trait': 'T', - 'type_alias': 'type', 'constant': 'const' - }; - - // Fixed positions — arranged in logical groups - var fixedPositions = { - // Core (left column) - 'traits': { x: 80, y: 80 }, - 'types': { x: 80, y: 230 }, - 'variant': { x: 80, y: 380 }, - 'topology': { x: 80, y: 530 }, - // Models (center column) - 'models/graph': { x: 340, y: 80 }, - 'models/formula': { x: 340, y: 230 }, - 'models/set': { x: 340, y: 350 }, - 'models/algebraic':{ x: 340, y: 450 }, - 'models/misc': { x: 340, y: 550 }, - // Rules + Registry (right-center) - 'rules': { x: 600, y: 80 }, - 'registry': { x: 600, y: 230 }, - // Solvers + Utilities (far right) - 'solvers': { x: 800, y: 80 }, - 'expr': { x: 800, y: 230 }, - 'io': { x: 800, y: 380 } - }; - - fetch('static/module-graph.json') - .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }) - .then(function(data) { - var elements = []; - - data.modules.forEach(function(mod) { - var parentId = 'mod_' + mod.name.replace(/\//g, '_'); - var pos = fixedPositions[mod.name] || { x: 500, y: 280 }; - - // Compound parent node - elements.push({ - data: { - id: parentId, - label: mod.name, - category: mod.category, - doc_path: mod.doc_path, - itemCount: mod.items.length, - isParent: true - } - }); - - // Child item nodes - mod.items.forEach(function(item, idx) { - var childId = mod.name.replace(/\//g, '_') + '::' + item.name; - var icon = kindIcons[item.kind] || item.kind; - elements.push({ - data: { - id: childId, - parent: parentId, - label: icon + ' ' + item.name, - fullLabel: mod.name + '::' + item.name, - category: mod.category, - kind: item.kind, - doc: item.doc || '', - isChild: true, - moduleName: mod.name, - itemName: item.name - }, - position: { - x: pos.x, - y: pos.y + 18 + idx * 22 - } - }); - }); - }); - - // Module-level edges - data.edges.forEach(function(e) { - elements.push({ - data: { - id: 'edge_' + e.source.replace(/\//g, '_') + '_' + e.target.replace(/\//g, '_'), - source: 'mod_' + e.source.replace(/\//g, '_'), - target: 'mod_' + e.target.replace(/\//g, '_') - } - }); - }); - - var cy = cytoscape({ - container: container, - elements: elements, - style: [ - // Module nodes (compound parents) - { selector: 'node[?isParent]', style: { - 'label': 'data(label)', - 'text-valign': 'center', 'text-halign': 'center', - 'font-size': '11px', 'font-family': 'monospace', 'font-weight': 'bold', - 'min-width': function(ele) { return Math.max(ele.data('label').length * 7.5 + 20, 80); }, - 'min-height': 36, - 'padding': '4px', - 'shape': 'round-rectangle', - 'background-color': function(ele) { return categoryColors[ele.data('category')] || '#f0f0f0'; }, - 'border-width': 2, - 'border-color': function(ele) { return categoryBorders[ele.data('category')] || '#999'; }, - 'compound-sizing-wrt-labels': 'include', - 'cursor': 'pointer' - }}, - // Expanded parent - { selector: 'node[?isParent].expanded', style: { - 'text-valign': 'top', - 'padding': '10px' - }}, - // Child item nodes - { selector: 'node[?isChild]', style: { - 'label': 'data(label)', - 'text-valign': 'center', 'text-halign': 'center', - 'font-size': '9px', 'font-family': 'monospace', - 'width': function(ele) { return Math.max(ele.data('label').length * 5.5 + 8, 40); }, - 'height': 18, - 'shape': 'round-rectangle', - 'background-color': function(ele) { return categoryColors[ele.data('category')] || '#f0f0f0'; }, - 'border-width': 1, - 'border-color': function(ele) { return categoryBorders[ele.data('category')] || '#999'; } - }}, - // Edges - { selector: 'edge', style: { - 'width': 1.5, 'line-color': '#999', 'target-arrow-color': '#999', - 'target-arrow-shape': 'triangle', 'curve-style': 'bezier', - 'arrow-scale': 0.8, - 'source-distance-from-node': 5, - 'target-distance-from-node': 5 - }} - ], - layout: { name: 'preset' }, - userZoomingEnabled: true, - userPanningEnabled: true, - boxSelectionEnabled: false - }); - - // Initial state: hide all children, position parents at fixed positions - cy.nodes('[?isChild]').style('display', 'none'); - Object.keys(fixedPositions).forEach(function(name) { - var node = cy.getElementById('mod_' + name.replace(/\//g, '_')); - if (node.length) node.position(fixedPositions[name]); - }); - cy.fit(40); - - var expandedParents = {}; - - // Click: toggle expand/collapse - cy.on('tap', 'node[?isParent]', function(evt) { - var parentNode = evt.target; - var parentId = parentNode.id(); - var children = parentNode.children(); - - if (expandedParents[parentId]) { - // Collapse - children.style('display', 'none'); - parentNode.removeClass('expanded'); - expandedParents[parentId] = false; - var name = parentNode.data('label'); - if (fixedPositions[name]) { - parentNode.position(fixedPositions[name]); - } - } else { - // Expand - children.style('display', 'element'); - parentNode.addClass('expanded'); - expandedParents[parentId] = true; - } - }); - - // Rustdoc URL prefixes by kind - var kindPrefix = { - 'function': 'fn', 'struct': 'struct', 'enum': 'enum', - 'trait': 'trait', 'type_alias': 'type', 'constant': 'constant' - }; - - // Double-click: open rustdoc - cy.on('dbltap', 'node[?isParent]', function(evt) { - var d = evt.target.data(); - if (d.doc_path) { - window.open('api/problemreductions/' + d.doc_path, '_blank'); - } - }); - cy.on('dbltap', 'node[?isChild]', function(evt) { - var d = evt.target.data(); - var prefix = kindPrefix[d.kind] || d.kind; - var modPath = d.moduleName.replace(/\//g, '/'); - window.open('api/problemreductions/' + modPath + '/' + prefix + '.' + d.itemName + '.html', '_blank'); - }); - - // Tooltip - var tooltip = document.getElementById('mg-tooltip'); - cy.on('mouseover', 'node[?isParent]', function(evt) { - var d = evt.target.data(); - tooltip.innerHTML = '' + d.label + ' (' + d.itemCount + ' items)
Click to expand, double-click for docs'; - tooltip.style.display = 'block'; - }); - cy.on('mouseover', 'node[?isChild]', function(evt) { - var d = evt.target.data(); - var html = '' + d.fullLabel + '
' + d.kind + ''; - if (d.doc) html += '
' + d.doc + ''; - tooltip.innerHTML = html; - tooltip.style.display = 'block'; - }); - cy.on('mousemove', 'node', function(evt) { - var pos = evt.renderedPosition || evt.position; - var rect = container.getBoundingClientRect(); - tooltip.style.left = (rect.left + window.scrollX + pos.x + 15) + 'px'; - tooltip.style.top = (rect.top + window.scrollY + pos.y - 10) + 'px'; - }); - cy.on('mouseout', 'node', function() { tooltip.style.display = 'none'; }); - - // Edge tooltip - cy.on('mouseover', 'edge', function(evt) { - var src = evt.target.source().data('label'); - var dst = evt.target.target().data('label'); - tooltip.innerHTML = '' + src + ' \u2192 ' + dst + ''; - tooltip.style.display = 'block'; - }); - cy.on('mousemove', 'edge', function(evt) { - var pos = evt.renderedPosition || evt.position; - var rect = container.getBoundingClientRect(); - tooltip.style.left = (rect.left + window.scrollX + pos.x + 15) + 'px'; - tooltip.style.top = (rect.top + window.scrollY + pos.y - 10) + 'px'; - }); - cy.on('mouseout', 'edge', function() { tooltip.style.display = 'none'; }); - }) - .catch(function(err) { - container.innerHTML = '

Failed to load module graph: ' + err.message + '

'; - }); -}); diff --git a/docs/src/static/module-overview-dark.svg b/docs/src/static/module-overview-dark.svg deleted file mode 100644 index 6d520f881..000000000 --- a/docs/src/static/module-overview-dark.svg +++ /dev/null @@ -1,413 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/src/static/module-overview.svg b/docs/src/static/module-overview.svg deleted file mode 100644 index ba76e869b..000000000 --- a/docs/src/static/module-overview.svg +++ /dev/null @@ -1,413 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/src/static/module-overview.typ b/docs/src/static/module-overview.typ deleted file mode 100644 index afd84a84b..000000000 --- a/docs/src/static/module-overview.typ +++ /dev/null @@ -1,40 +0,0 @@ -#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge -#set page(width: auto, height: auto, margin: (top: 5pt, bottom: 5pt, left: 5pt, right: 5pt), fill: none) -#set text(font: "Helvetica Neue") - -#let module-overview(dark: false) = { - let (fg, box-color) = if dark { - (rgb("#e2e8f0"), rgb("#94a3b8")) - } else { - (rgb("#1e293b"), rgb("#64748b")) - } - - // Module colors - darker versions for dark mode - let (model-color, rule-color, registry-color, solver-color) = if dark { - (rgb("#166534"), rgb("#1e3a5f"), rgb("#854d0e"), rgb("#7f1d1d")) - } else { - (rgb("#dcfce7"), rgb("#dbeafe"), rgb("#fef3c7"), rgb("#fee2e2")) - } - - set text(fill: fg, size: 10pt) - - diagram( - node-stroke: 1.5pt + box-color, - edge-stroke: 1.5pt + box-color, - spacing: (25mm, 15mm), - - // Module nodes - node((0, 0), box(width: 30mm, align(center)[#strong[models/]\ #text(size: 10pt)[Problem types]]), fill: model-color, corner-radius: 6pt, inset: 10pt, name: ), - node((1, 0), box(width: 30mm, align(center)[#strong[rules/]\ #text(size: 10pt)[Reductions]]), fill: rule-color, corner-radius: 6pt, inset: 10pt, name: ), - node((2, 0), box(width: 30mm, align(center)[#strong[registry/]\ #text(size: 10pt)[Graph metadata]]), fill: registry-color, corner-radius: 6pt, inset: 10pt, name: ), - node((1, 1), box(width: 30mm, align(center)[#strong[solvers/]\ #text(size: 8pt)[BruteForce, ILP]]), fill: solver-color, corner-radius: 6pt, inset: 10pt, name: ), - - // Relationships - edge(, , "->", label: text(size: 10pt)[imports], label-sep: 2pt, label-pos: 0.5, label-side: left), - edge(, , "->", label: text(size: 10pt)[registers], label-sep: 2pt, label-pos: 0.5, label-side: left), - edge(, , "<-", label: text(size: 10pt)[solves], label-sep: 2pt, label-pos: 0.5, label-side: left), - ) -} - -#let standalone-dark = sys.inputs.at("dark", default: "false") == "true" -#module-overview(dark: standalone-dark) diff --git a/docs/website/README.md b/docs/website/README.md index 344bf852b..582100d81 100644 --- a/docs/website/README.md +++ b/docs/website/README.md @@ -75,7 +75,7 @@ concept and link to `pred --help`, rustdoc, or `.claude/CLAUDE.md` instead of restating them. `docs/src/static/docs-theme.css` supplies the restrained dark reading theme; -`docs-theme.js` adds the Markdown links. Native mdBook search, code copying, and +`docs-theme.js` adds the GitHub and atlas links. Native mdBook search, code copying, and sidebar navigation remain available. Both mdBook 0.4.37 (deployment) and 0.5.2 are supported. @@ -86,8 +86,9 @@ agents. ## CLI recording `docs/src/static/cli-demo.cast` contains real PTY output from six successful -commands against the current CLI. `cli-demo.html` embeds that cast and -asciinema-player 3.8.0 for offline playback, with autoplay disabled. The bundled +commands against the current CLI. `cli-demo.html` embeds a copy of that cast +and asciinema-player 3.8.0 for offline playback, with autoplay disabled; the +recording script refreshes both files. The bundled player is Apache-2.0 licensed; its license is alongside the recording. To refresh the recording after a CLI change, build the CLI and run: diff --git a/docs/website/assets/site.css b/docs/website/assets/site.css index 62442056f..383f04e97 100644 --- a/docs/website/assets/site.css +++ b/docs/website/assets/site.css @@ -121,12 +121,6 @@ h2 { letter-spacing: -0.055em; line-height: 1.03; } -h1 em, -h2 em { - font-family: var(--serif); - font-weight: 400; - letter-spacing: -0.025em; -} h2 { font-size: clamp(38px, 4.1vw, 57px); } @@ -248,9 +242,6 @@ p { font-size: clamp(60px, 6vw, 84px); line-height: 0.99; } -.hero h1 em { - color: var(--green); -} .hero-description { margin-top: 26px; font-size: 15px; @@ -302,37 +293,11 @@ p { .text-link:hover { color: #dfedce; } -.hero-footnote { - display: flex; - align-items: center; - gap: 8px; - margin-top: 25px; - font-size: 10px; - color: var(--muted); -} -.tiny-node { - height: 7px; - width: 7px; - border: 1px solid #96aa8b; - border-radius: 2px; -} .atlas-visual { position: relative; min-width: 0; - padding: 0 0 68px; margin-right: -15px; } -.visual-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - padding: 15px 0; - border-top: 1px solid var(--line); - color: var(--muted); -} -.map-scale { - font-size: 11px; -} .hero-graph { height: 427px; position: relative; @@ -396,88 +361,24 @@ p { font-weight: 600; fill: #d4eab6; } -.graph-caption { - font-size: 9px; - color: var(--muted); - display: flex; - justify-content: space-between; - align-items: center; - padding: 14px 0; - border-top: 1px solid var(--line); -} -.graph-caption > span { - display: flex; - align-items: center; - gap: 6px; -} -.legend-dot { - width: 5px; - height: 5px; - background: var(--green); - border-radius: 50%; - display: inline-block; -} -.graph-feature { - position: absolute; - left: 11%; - right: 3%; - bottom: 0; - display: flex; - align-items: center; - gap: 14px; - padding: 16px 19px; - background: var(--surface); - border: 1px solid var(--line); - border-radius: 6px; - box-shadow: 0 12px 35px #00000024; - transition: transform 0.2s; -} -.graph-feature:hover { - transform: translateY(-3px); -} -.feature-icon { - font-size: 25px; - color: var(--green); - width: 37px; - height: 37px; - text-align: center; - background: var(--pale); - border-radius: 4px; -} -.graph-feature strong { - font-size: 12px; - font-weight: 500; - display: block; -} -.graph-feature strong span { - margin-inline: 6px; - color: var(--muted); -} -.graph-feature > span:last-child { - margin-left: auto; -} .stats-bar { display: grid; - grid-template-columns: 1.4fr 1fr 1fr 1fr 1.1fr; + grid-template-columns: repeat(3, 1fr) 1.1fr; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding-block: 27px; align-items: center; } -.stats-bar > p { - font-size: 12px; - line-height: 1.6; -} -.stats-bar > p strong { - font-weight: 500; - color: var(--ink); -} .stats-bar > div { border-left: 1px solid var(--line); padding-left: 32px; display: flex; flex-direction: column; } +.stats-bar > div:first-child { + border-left: 0; + padding-left: 0; +} .stats-bar > div strong { font-size: 32px; letter-spacing: -1.6px; @@ -572,7 +473,7 @@ p { } .section-bottom { display: flex; - justify-content: space-between; + justify-content: flex-end; gap: 25px; margin-top: 24px; color: var(--muted); @@ -583,9 +484,6 @@ p { color: var(--ink); padding-block: 85px; } -.research-section h2 em { - color: var(--lime); -} .research-section p { color: #b5c5b2; } @@ -1317,20 +1215,9 @@ pre { .hero-description { font-size: 14px; } - .hero-footnote { - font-size: 9px; - } .hero-actions { gap: 18px; } - .graph-feature { - left: 2%; - right: 0; - padding: 14px; - } - .graph-feature strong { - font-size: 10px; - } .section-heading { gap: 50px; } @@ -1388,9 +1275,6 @@ pre { .hero-copy { max-width: 600px; } - .hero-footnote { - font-size: 10px; - } .atlas-visual { margin: 0; max-width: 600px; @@ -1400,29 +1284,13 @@ pre { .hero-graph { height: 360px; } - .graph-feature { - left: 15%; - right: 0; - } .stats-bar { grid-template-columns: repeat(3, 1fr); gap: 20px 0; } - .stats-bar > p { - grid-column: 1/-1; - display: flex; - gap: 5px; - } - .stats-bar > p br { - display: none; - } .stats-bar > div { padding-left: 20px; } - .stats-bar > div:nth-child(2) { - border: 0; - padding-left: 0; - } .stats-bar > a { display: none; } @@ -1654,15 +1522,9 @@ pre { .hero-actions .text-link { font-size: 12px; } - .hero-footnote { - font-size: 8px; - } .hero-graph { height: 310px; } - .graph-feature { - left: 0; - } .stats-bar > div strong { font-size: 28px; } diff --git a/docs/website/assets/site.js b/docs/website/assets/site.js index f8b3894b6..743c32e1c 100644 --- a/docs/website/assets/site.js +++ b/docs/website/assets/site.js @@ -191,7 +191,6 @@ if (featuredEdge) { document.querySelector("#featured-result-link").href = ruleHref(featuredEdge); - document.querySelector(".graph-feature").href = ruleHref(featuredEdge); } } @@ -200,7 +199,7 @@
-

Find your next connection.

+

Find your next connection.

Explore ${families.length} problem families and their implemented reductions. Every connection has explicit variants, size bounds, and @@ -466,9 +465,7 @@ Implemented

- ${escape(nameOf(sourceName))}
→ ${escape(nameOf(targetName))} + ${escape(nameOf(sourceName))}
→ ${escape(nameOf(targetName))}

${complement ? "Keep the graph. Complement the solution. A foundational connection between two views of the same combinatorial structure." : "An executable construction connecting two concrete problem variants, with explicit size bounds and a recoverable contract."} diff --git a/docs/website/index.html b/docs/website/index.html index 9e1d07935..c4713b66c 100644 --- a/docs/website/index.html +++ b/docs/website/index.html @@ -7,15 +7,12 @@ - + Problem Reductions @@ -46,7 +43,7 @@

-
- Reduction atlasVariants grouped -
-
- Implemented connectionsOpen atlas -
- Vertex Cover Independent - Set
-

Atlas

__PROBLEM_COUNT__Problem families
@@ -127,7 +106,7 @@

Problem
Reductions

-

Problems and reductions

+

Problems and reductions

A different formulation can unlock a different solver. Explore the @@ -137,8 +116,7 @@

Problems and reductions

- Graph theory, logic, set systems, algebra, and beyond.Browse all problems
@@ -147,7 +125,7 @@

Problems and reductions

-

Research loop

+

Research loop

@@ -203,7 +181,7 @@

Grow the atlas

-

Get started

+

Get started

Use the atlas from the terminal, from Rust, or through an agent.
All three share the same models and constructions. diff --git a/examples/export_module_graph.rs b/examples/export_module_graph.rs deleted file mode 100644 index b32b51903..000000000 --- a/examples/export_module_graph.rs +++ /dev/null @@ -1,343 +0,0 @@ -//! Export module architecture graph for mdBook interactive visualization. -//! -//! Generates `docs/src/static/module-graph.json` from inventory-registered -//! problem schemas and reduction entries — no nightly rustdoc required. -//! -//! Run with: `cargo run --example export_module_graph [output_path]` - -use problemreductions::registry::ProblemSchemaEntry; -use problemreductions::rules::ReductionEntry; -use serde::Serialize; -use std::collections::{BTreeMap, BTreeSet}; -use std::path::PathBuf; - -/// Category for color-coding in the visualization. -fn module_category(module_path: &str) -> &'static str { - if module_path.contains("models::graph") { - "model_graph" - } else if module_path.contains("models::formula") { - "model_formula" - } else if module_path.contains("models::set") { - "model_set" - } else if module_path.contains("models::algebraic") { - "model_algebraic" - } else if module_path.contains("models::misc") { - "model_misc" - } else if module_path.contains("rules") { - "rule" - } else if module_path.contains("registry") { - "registry" - } else if module_path.contains("solvers") { - "solver" - } else { - "core" - } -} - -/// Convert a full module path like "problemreductions::models::graph::foo" to a -/// short display path like "models/graph". -fn module_display_path(module_path: &str) -> String { - let stripped = module_path - .strip_prefix("problemreductions::") - .unwrap_or(module_path); - // Take up to the second-level module (e.g., "models::graph::foo" -> "models/graph") - let parts: Vec<&str> = stripped.split("::").collect(); - match parts.len() { - 0 => stripped.replace("::", "/"), - 1 => parts[0].to_string(), - _ => { - // For "models::graph::something", keep "models/graph" - // For "rules::something", keep "rules" - if parts[0] == "models" && parts.len() >= 2 { - format!("{}/{}", parts[0], parts[1]) - } else { - parts[0].to_string() - } - } - } -} - -#[derive(Serialize)] -struct ModuleNode { - name: String, - category: String, - doc_path: String, - items: Vec, -} - -#[derive(Serialize, Clone)] -struct ModuleItem { - name: String, - kind: String, - doc: String, -} - -#[derive(Serialize)] -struct Edge { - source: String, - target: String, -} - -#[derive(Serialize)] -struct ModuleGraph { - modules: Vec, - edges: Vec, -} - -fn main() { - // Group problems by module display path - let mut module_items: BTreeMap> = BTreeMap::new(); - let mut module_categories: BTreeMap = BTreeMap::new(); - - for entry in inventory::iter:: { - let display = module_display_path(entry.module_path); - let category = module_category(entry.module_path).to_string(); - module_categories.entry(display.clone()).or_insert(category); - module_items.entry(display).or_default().push(ModuleItem { - name: entry.display_name.to_string(), - kind: "struct".to_string(), - doc: entry.description.to_string(), - }); - } - - // Add well-known non-model modules with their key items - type ModuleSpec = ( - &'static str, - &'static str, - &'static [(&'static str, &'static str, &'static str)], - ); - let static_modules: &[ModuleSpec] = &[ - ( - "traits", - "core", - &[( - "Problem", - "trait", - "Core trait for all computational problems", - )], - ), - ( - "types", - "core", - &[ - ("Aggregate", "trait", "Trait for aggregate value types"), - ("Max", "struct", "Maximum aggregate wrapper"), - ("Min", "struct", "Minimum aggregate wrapper"), - ("Sum", "struct", "Summation aggregate wrapper"), - ("Or", "struct", "Existential (logical or) aggregate"), - ("And", "struct", "Universal (logical and) aggregate"), - ("Extremum", "struct", "Runtime max/min aggregate"), - ("One", "struct", "Unit weight marker type"), - ("WeightElement", "trait", "Trait for weight types"), - ], - ), - ( - "variant", - "core", - &[ - ("VariantParam", "trait", "Trait for variant parameter types"), - ( - "CastToParent", - "trait", - "Trait for variant cast conversions", - ), - ], - ), - ( - "topology", - "core", - &[ - ("SimpleGraph", "struct", "Simple undirected graph"), - ("PlanarGraph", "struct", "Planar graph"), - ("BipartiteGraph", "struct", "Bipartite graph"), - ("UnitDiskGraph", "struct", "Unit disk graph"), - ("KingsSubgraph", "struct", "Kings subgraph"), - ], - ), - ( - "rules", - "rule", - &[ - ("ReduceTo", "trait", "Trait for witness/config reductions"), - ( - "ReductionResult", - "trait", - "Result of a reduction with solution extraction", - ), - ( - "ReduceToAggregate", - "trait", - "Trait for aggregate/value reductions", - ), - ( - "AggregateReductionResult", - "trait", - "Result of a reduction with value extraction", - ), - ], - ), - ( - "registry", - "registry", - &[ - ( - "ReductionGraph", - "struct", - "Global graph of all registered reductions", - ), - ("ReductionEntry", "struct", "A single registered reduction"), - ("VariantEntry", "struct", "A registered problem variant"), - ], - ), - ( - "solvers", - "solver", - &[ - ("BruteForce", "struct", "Exhaustive search solver"), - ("ILPSolver", "struct", "Integer linear programming solver"), - ( - "Solver", - "trait", - "Solver trait for aggregate value computation", - ), - ], - ), - ( - "io", - "utility", - &[ - ("to_json", "function", "Serialize a problem to JSON"), - ("from_json", "function", "Deserialize a problem from JSON"), - ], - ), - ]; - - for &(name, category, items) in static_modules { - module_categories - .entry(name.to_string()) - .or_insert_with(|| category.to_string()); - let entry = module_items.entry(name.to_string()).or_default(); - // Only add if not already populated from inventory - if entry.is_empty() { - for &(item_name, kind, doc) in items { - entry.push(ModuleItem { - name: item_name.to_string(), - kind: kind.to_string(), - doc: doc.to_string(), - }); - } - } - } - - // Build edges from reduction entries (which module uses which) - let mut edges: BTreeSet<(String, String)> = BTreeSet::new(); - - // Each reduction connects source module -> target module - for entry in inventory::iter:: { - let src_module = find_problem_module(entry.source_name, &module_items); - let dst_module = find_problem_module(entry.target_name, &module_items); - if let (Some(src), Some(dst)) = (src_module, dst_module) { - if src != dst { - // rule module depends on both model modules - edges.insert(("rules".to_string(), src.clone())); - edges.insert(("rules".to_string(), dst)); - } - } - } - - // Add well-known architectural edges - let known_edges: &[(&str, &str)] = &[ - ("models/graph", "traits"), - ("models/graph", "types"), - ("models/graph", "topology"), - ("models/graph", "variant"), - ("models/formula", "traits"), - ("models/formula", "types"), - ("models/set", "traits"), - ("models/set", "types"), - ("models/algebraic", "traits"), - ("models/algebraic", "types"), - ("models/misc", "traits"), - ("models/misc", "types"), - ("rules", "traits"), - ("rules", "types"), - ("registry", "rules"), - ("registry", "traits"), - ("registry", "types"), - ("solvers", "traits"), - ("solvers", "types"), - ("io", "traits"), - ("topology", "variant"), - ]; - - for &(src, dst) in known_edges { - if module_categories.contains_key(src) && module_categories.contains_key(dst) { - edges.insert((src.to_string(), dst.to_string())); - } - } - - // Assemble output - let modules: Vec = module_categories - .iter() - .map(|(name, category)| { - let items = module_items.get(name).cloned().unwrap_or_default(); - ModuleNode { - name: name.clone(), - category: category.clone(), - doc_path: format!("{name}/index.html"), - items, - } - }) - .collect(); - - let edge_list: Vec = edges - .into_iter() - .map(|(source, target)| Edge { source, target }) - .collect(); - - let graph = ModuleGraph { - modules, - edges: edge_list, - }; - - let output_path = std::env::args() - .nth(1) - .map(PathBuf::from) - .unwrap_or_else(|| PathBuf::from("docs/src/static/module-graph.json")); - - if let Some(parent) = output_path.parent() { - std::fs::create_dir_all(parent).expect("Failed to create output directory"); - } - - let json = serde_json::to_string(&graph).expect("Failed to serialize"); - std::fs::write(&output_path, &json).expect("Failed to write file"); - - println!( - "Wrote {} ({} modules, {} edges)", - output_path.display(), - graph.modules.len(), - graph.edges.len() - ); -} - -/// Find which module display path a problem belongs to. -fn find_problem_module( - problem_name: &str, - module_items: &BTreeMap>, -) -> Option { - for (module, items) in module_items { - for item in items { - // Match against display name or the struct name - if item.name.replace(' ', "") == problem_name { - return Some(module.clone()); - } - } - } - // Fallback: search inventory directly - for entry in inventory::iter:: { - if entry.name == problem_name { - return Some(module_display_path(entry.module_path)); - } - } - None -} diff --git a/scripts/gen_module_graph.py b/scripts/gen_module_graph.py deleted file mode 100644 index 360e66f81..000000000 --- a/scripts/gen_module_graph.py +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env python3 -"""Generate module-graph.json for the mdbook interactive architecture page. - -Reads the rustdoc JSON output and produces a Cytoscape-ready graph with: -- Module nodes (compound, color-coded by category) -- Public item nodes (children of modules) -- Dependency edges between modules - -Usage: - cargo +nightly rustdoc -- -Z unstable-options --output-format json - python3 scripts/gen_module_graph.py -""" -import json -import sys -from pathlib import Path - -RUSTDOC_JSON = Path("target/doc/problemreductions.json") -OUTPUT = Path("docs/src/static/module-graph.json") -CRATE_NAME = "problemreductions" - -# Category assignment by full module path -CATEGORIES = { - "traits": "core", - "types": "core", - "variant": "core", - "topology": "core", - "models/graph": "model_graph", - "models/formula": "model_formula", - "models/set": "model_set", - "models/algebraic": "model_algebraic", - "models/misc": "model_misc", - "rules": "rule", - "registry": "registry", - "solvers": "solver", - "io": "utility", - "export": "utility", - "config": "utility", - "error": "utility", -} - -# Only include these top-level and second-level modules in the graph -INCLUDED_MODULES = { - "traits", - "types", - "variant", - "topology", - "models/graph", - "models/formula", - "models/set", - "models/algebraic", - "models/misc", - "rules", - "registry", - "solvers", - "io", - "export", -} - -# Item kinds to include in the child list -INCLUDE_KINDS = {"struct", "enum", "trait", "function", "type_alias", "constant"} - - -def main(): - if not RUSTDOC_JSON.exists(): - print( - "Error: {} not found. Run:\n" - " cargo +nightly rustdoc -- -Z unstable-options --output-format json".format( - RUSTDOC_JSON - ), - file=sys.stderr, - ) - sys.exit(1) - - with open(RUSTDOC_JSON) as f: - data = json.load(f) - - index = data["index"] - - # 1. Build parent map to reconstruct full module paths - parent_map = {} # child_module_id → (parent_module_id, parent_name) - for item_id, item in index.items(): - inner = item.get("inner", {}) - if not isinstance(inner, dict) or "module" not in inner: - continue - for child_id in inner["module"].get("items", []): - cid = str(child_id) - if cid in index: - child_inner = index[cid].get("inner", {}) - if isinstance(child_inner, dict) and "module" in child_inner: - parent_map[cid] = (item_id, item.get("name")) - - def get_full_path(item_id): - parts = [index[item_id].get("name")] - current = item_id - while current in parent_map: - current = parent_map[current][0] - parts.append(index[current].get("name")) - parts.reverse() - return "/".join(p for p in parts if p != CRATE_NAME) - - def resolve_item(item_id): - """Follow re-exports (use) to get the actual item.""" - cid = str(item_id) - seen = set() - while cid in index and cid not in seen: - seen.add(cid) - item = index[cid] - inner = item.get("inner", {}) - if isinstance(inner, dict) and "use" in inner: - target_id = str(inner["use"].get("id", "")) - if target_id and target_id in index: - cid = target_id - else: - return cid - else: - return cid - return cid - - # 2. Find included modules and their public items - item_to_module = {} # item_id → module full path - modules = {} - for item_id, item in index.items(): - inner = item.get("inner", {}) - if not isinstance(inner, dict) or "module" not in inner: - continue - name = item.get("name") - if name == CRATE_NAME: - continue - full_path = get_full_path(item_id) - if full_path not in INCLUDED_MODULES: - continue - - children = [] - for child_id in inner["module"].get("items", []): - cid = str(child_id) - # Register the direct child for dependency tracking - item_to_module[cid] = full_path - - # Resolve through re-exports to find the actual item - resolved_id = resolve_item(cid) - item_to_module[resolved_id] = full_path - - if resolved_id not in index: - continue - child = index[resolved_id] - child_inner = child.get("inner", {}) - if not isinstance(child_inner, dict): - continue - kind = list(child_inner.keys())[0] if child_inner else "unknown" - if kind not in INCLUDE_KINDS: - continue - doc = child.get("docs", "") or "" - doc_summary = doc.strip().split("\n")[0][:120] if doc.strip() else "" - child_name = child.get("name") - if child_name: - children.append( - {"name": child_name, "kind": kind, "doc": doc_summary} - ) - - doc_path = full_path.replace("/", "/") + "/index.html" - modules[full_path] = { - "name": full_path, - "category": CATEGORIES.get(full_path, "utility"), - "doc_path": doc_path, - "items": children, - } - - # 3. Extract inter-module dependencies by scanning type references - def find_ids(obj, found=None): - if found is None: - found = set() - if isinstance(obj, dict): - for k, v in obj.items(): - if k == "id" and isinstance(v, (int, str)): - found.add(str(v)) - else: - find_ids(v, found) - elif isinstance(obj, list): - for v in obj: - find_ids(v, found) - return found - - edges = set() - for item_id, item in index.items(): - src = item_to_module.get(item_id) - if not src: - continue - inner = item.get("inner", {}) - if not isinstance(inner, dict): - continue - for ref_id in find_ids(inner): - dst = item_to_module.get(ref_id) - if dst and dst != src: - edges.add((src, dst)) - - # 4. Add well-known architectural edges that rustdoc heuristics may miss - # (re-exports at crate root obscure the actual module→module dependencies) - KNOWN_EDGES = [ - # All model categories depend on core - ("models/graph", "traits"), - ("models/graph", "types"), - ("models/graph", "topology"), - ("models/graph", "variant"), - ("models/formula", "traits"), - ("models/formula", "types"), - ("models/set", "traits"), - ("models/set", "types"), - ("models/algebraic", "traits"), - ("models/algebraic", "types"), - ("models/misc", "traits"), - ("models/misc", "types"), - # Rules depend on models and core - ("rules", "traits"), - ("rules", "types"), - # Registry depends on rules and core - ("registry", "rules"), - ("registry", "traits"), - ("registry", "types"), - # Solvers depend on core - ("solvers", "traits"), - ("solvers", "types"), - # IO depends on core - ("io", "traits"), - # Topology uses variant system - ("topology", "variant"), - ] - for src, dst in KNOWN_EDGES: - if src in modules and dst in modules: - edges.add((src, dst)) - - # 5. Write output - OUTPUT.parent.mkdir(parents=True, exist_ok=True) - result = { - "modules": sorted(modules.values(), key=lambda m: m["name"]), - "edges": [{"source": s, "target": t} for s, t in sorted(edges)], - } - with open(OUTPUT, "w") as f: - json.dump(result, f, indent=2) - print( - "Wrote {} ({} modules, {} edges)".format( - OUTPUT, len(result["modules"]), len(result["edges"]) - ) - ) - - -if __name__ == "__main__": - main() diff --git a/scripts/record_cli_demo.py b/scripts/record_cli_demo.py index 08660c0bd..e4399f0d3 100755 --- a/scripts/record_cli_demo.py +++ b/scripts/record_cli_demo.py @@ -5,7 +5,9 @@ Each command runs in a pseudo-terminal inside a temporary directory holding a `pred` symlink to the given binary. Typing is simulated; output is never -edited. The script fails if any command exits non-zero. +edited except that trailing blank lines are collapsed so one blank line +separates commands. The cast is also embedded into `cli-demo.html`, the +offline player page. The script fails if any command exits non-zero. """ import argparse @@ -13,6 +15,7 @@ import os import pty import random +import re import shlex import subprocess import tempfile @@ -83,18 +86,23 @@ def emit(text, delay=0.0): for char in command: emit(char, TYPING_DELAY * rng.uniform(0.6, 1.6)) emit("\r\n", 0.3) - emit(run_in_pty(command, work).replace("\r\n", "\n").replace("\n", "\r\n"), 0.15) + output = run_in_pty(command, work).replace("\r\n", "\n").rstrip("\n") + emit(output.replace("\n", "\r\n") + "\r\n", 0.15) clock += STEP_PAUSE emit("$ ", 0.2) header = {"version": 2, "width": WIDTH, "height": HEIGHT, "title": "From a graph to a verified solution", "env": {"TERM": "xterm-256color", "SHELL": "/bin/bash"}} - with args.output.open("w") as handle: - handle.write(json.dumps(header) + "\n") - for event in events: - handle.write(json.dumps(event) + "\n") - print(f"Wrote {args.output} ({len(events)} events, {clock:.1f}s)") + cast = "".join(json.dumps(item) + "\n" for item in [header, *events]) + args.output.write_text(cast) + player = args.output.with_name("cli-demo.html") + html, count = re.subn(r"var castData = \".*?\";", lambda _: "var castData = " + json.dumps(cast) + ";", + player.read_text(), count=1, flags=re.S) + if count != 1: + raise SystemExit(f"castData not found in {player}") + player.write_text(html) + print(f"Wrote {args.output} and embedded it in {player.name} ({len(events)} events, {clock:.1f}s)") if __name__ == "__main__": diff --git a/scripts/test_documentation.py b/scripts/test_documentation.py index 7f47adaad..61d05e125 100644 --- a/scripts/test_documentation.py +++ b/scripts/test_documentation.py @@ -106,8 +106,10 @@ def test_sidebar_navigation_and_markdown(self): self.visit("introduction.html") self.page.locator(".sidebar").get_by_role("link", name="Skills", exact=True).click() expect(self.page.locator("main h1")).to_have_text("Skills") - href = self.page.get_by_role("navigation", name="Documentation resources").get_by_role("link", name="Markdown", exact=True).get_attribute("href") - result = self.context.request.get(urljoin(self.page.url, href)) + tools = self.page.get_by_role("navigation", name="Documentation resources") + self.assertEqual(tools.get_by_role("link", name="GitHub", exact=False).get_attribute("href"), + "https://github.com/CodingThrust/problem-reductions") + result = self.context.request.get(urljoin(self.page.url, "markdown/skills.md")) self.assertEqual(result.status, 200) self.assertIn(".claude/skills/find-solver/SKILL.md", result.text()) self.assertLessEqual(self.page.locator(".docs-brand").bounding_box()["y"] + self.page.locator(".docs-brand").bounding_box()["height"],