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/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/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/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-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-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-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/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/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/design.md b/docs/src/design.md index 203d7e52c..32bdccb3c 100644 --- a/docs/src/design.md +++ b/docs/src/design.md @@ -1,35 +1,119 @@ -# 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. + +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 + +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) -| 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 | -| `problemreductions-cli/` | CLI and optional MCP server | -| `src/example_db/` | Canonical model and rule examples | -| `src/unit_tests/` | Tests mirroring the source tree | - -## Module map - - -
-
-
- Core - Models - Rules - Registry - Solvers - Utilities -
-
- Click a module to expand/collapse its public items. - Double-click to open rustdoc. +
+ +![Lattices](static/lattices-dark.svg) +
-
-Read the [problem contract](design-problem.md), [reduction contracts](design-reductions.md), and [variant system](design-variants.md) before extending the library. +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/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/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/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/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/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/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..0a10f372d 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"] +[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/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 3c11dccb7..9b0c1e0e4 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"); @@ -107,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 ed2ef0f6e..582100d81 100644 --- a/docs/website/README.md +++ b/docs/website/README.md @@ -68,48 +68,35 @@ 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 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. 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 `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, 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.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 651bd079e..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."} @@ -639,7 +636,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..c4713b66c 100644 --- a/docs/website/index.html +++ b/docs/website/index.html @@ -7,18 +7,15 @@ - + - Problem Reductions — A new route through hard problems + Problem Reductions Atlas Research - Infrastructure + Get started Docs @@ -70,58 +67,28 @@

-

- 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. -

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

A growing body of
executable knowledge.

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

-

One problem.
Many possibilities.

+

Problems and reductions

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

One problem.
Many possibilities.

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

One problem.
Many possibilities.

-

Knowledge that
builds on itself.

+

Research loop

@@ -215,17 +181,16 @@

Grow the atlas

-
# Install the research toolkit
+          
# Install the CLI
 $ cargo install problemreductions-cli
 
 # Explore a problem
@@ -272,9 +237,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 +250,6 @@

Give your research
a place to start.

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

Hard problems. Shared knowledge. New possibilities.

&'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/generate_doc_snippets.sh b/scripts/generate_doc_snippets.sh index 4a4663525..38767f697 100755 --- a/scripts/generate_doc_snippets.sh +++ b/scripts/generate_doc_snippets.sh @@ -67,7 +67,14 @@ for alias, name in rows: print(f'| \`{alias}\` | \`{name}\` |') " > "$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..e4399f0d3 --- /dev/null +++ b/scripts/record_cli_demo.py @@ -0,0 +1,109 @@ +#!/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 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 +import json +import os +import pty +import random +import re +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) + 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"}} + 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__": + main() diff --git a/scripts/test_documentation.py b/scripts/test_documentation.py index 561f7ed71..61d05e125 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,12 +102,14 @@ 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") - 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.page.locator(".sidebar").get_by_role("link", name="Skills", exact=True).click() + expect(self.page.locator("main h1")).to_have_text("Skills") + 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"], @@ -119,16 +124,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 +144,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()) 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()); }