diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 63928ab40..fac51c1e4 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -25,7 +25,7 @@ These repo-local skills live under `.claude/skills/*/SKILL.md`. - [review-quality](skills/review-quality/SKILL.md) -- Generic code quality review: DRY, KISS, cohesion/coupling, test quality, HCI. Read-only, no code changes. Called by `review-pipeline`. - [fix-pr](skills/fix-pr/SKILL.md) -- Resolve PR review comments, fix CI failures, and address codecov coverage gaps. Uses `gh api` for codecov (not local `cargo-llvm-cov`). - [write-model-in-paper](skills/write-model-in-paper/SKILL.md) -- Write or improve a problem-def entry in the Typst paper (standalone, for improving existing entries). Core instructions are inlined in `add-model` Step 6. -- [write-rule-in-paper](skills/write-rule-in-paper/SKILL.md) -- Write or improve a reduction-rule entry in the Typst paper (standalone, for improving existing entries). Core instructions are inlined in `add-rule` Step 5. +- [write-rule-in-paper](skills/write-rule-in-paper/SKILL.md) -- Write or improve a reduction-rule entry in the Typst paper (standalone, for improving existing entries). Core instructions are inlined in `add-rule` Step 6. - [release](skills/release/SKILL.md) -- Create a new crate release. Determines version bump from diff, verifies tests/clippy, then runs `make release`. - [check-issue](skills/check-issue/SKILL.md) -- Quality gate for `[Rule]` and `[Model]` issues. Checks usefulness, non-triviality, correctness of literature, and writing quality. Posts structured report and adds failure labels. - [fix-issue](skills/fix-issue/SKILL.md) -- Fix quality issues found by check-issue — auto-fixes mechanical problems, brainstorms substantive issues with human, then re-checks and moves to Ready. @@ -59,7 +59,7 @@ make fmt-check # Check code formatting make clippy # Run clippy lints make doc # Build mdBook documentation (includes reduction graph export) make mdbook # Build and serve mdBook with live reload -make paper # Build Typst paper from checked-in example fixtures +make paper # Generate example data and build the Typst paper make coverage # Generate coverage report (>95% required) make check # Quick pre-commit check (fmt + clippy + test) make rust-export # Generate Julia parity test data (mapping stages) @@ -151,13 +151,15 @@ Max, Min, Sum, Or, And, Extremum, ExtremumSense ### Key Patterns - `variant_params!` macro implements `Problem::variant()` — e.g., `crate::variant_params![G, W]` for two type params, `crate::variant_params![]` for none (see `src/variant.rs`) -- `declare_variants!` proc macro registers concrete type instantiations with best-known complexity and registry-backed load/serialize/value-solve/witness-solve metadata. One entry per problem may be marked `default`, and variable names in complexity strings are validated at compile time against actual getter methods. +- `declare_variants!` proc macro registers concrete type instantiations with best-known complexity and registry-backed load/serialize/value-solve/witness-solve metadata. One entry per problem may be marked `default`, and variable names in complexity strings are validated at compile time against actual getter methods. Ordinary models are constructed directly from their construction schema. When user-facing construction differs from persisted JSON, define a model-local `#[derive(CreateSpec)]` DTO plus `TryFrom`, use its generated `FIELDS` in `ProblemSchemaEntry`, and register it with `create LocalSpec`; never add model-name branches in CLI or MCP code. - `decision_problem_meta!` macro registers `DecisionProblemMeta` for a concrete inner type, providing the `DECISION_NAME` constant. -- `register_decision_variant!` macro generates `declare_variants!`, `ProblemSchemaEntry`, and both `ReductionEntry` submissions (aggregate Decision→Opt + Turing Opt→Decision) for a `Decision

` variant. Callers must define inherent getters (`num_vertices()`, `num_edges()`, `k()`) on `Decision

` before invoking. Accepts `dims`, `fields`, and `size_getters` parameters for problem-specific size fields. +- `register_decision_variant!` macro generates `declare_variants!`, `ProblemSchemaEntry`, and both `ReductionEntry` submissions (aggregate Decision→Opt + Turing Opt→Decision) for a `Decision

` variant. Callers must define inherent getters (`num_vertices()`, `num_edges()`, `k()`) on `Decision

` before invoking. Accepts an explicit structural `category` plus `dims`, `fields`, and `size_getters` parameters for problem-specific size fields. - Problems parameterized by graph type `G` and optionally weight type `W` (problem-dependent) - `Solver::solve()` computes the aggregate value for any `Problem` whose `Value` implements `Aggregate` - `BruteForce::find_witness()` / `find_all_witnesses()` recover witnesses only when `P::Value::supports_witnesses()` - `ReductionResult` provides `target_problem()` and `extract_solution()` for witness/config workflows; `AggregateReductionResult` provides `extract_value()` for aggregate/value workflows +- Every direct `extract_solution()` must call `validate_target_solution()` once before decoding; composed extractors delegate validation to the first direct decoder. +- Decode only the reduction's defined mathematical mapping. Reject malformed structure with `ExtractionError`; never panic, truncate, clamp, invent defaults, or add recovery branches. Explicit mathematical alternatives and sentinels are allowed. Test successful decoding and every rejected representation. - CLI-facing dynamic formatting uses aggregate wrapper names directly (for example `Max(2)`, `Min(None)`, `Or(true)`, or `Sum(56)`) - Graph types: SimpleGraph, PlanarGraph, BipartiteGraph, UnitDiskGraph, KingsSubgraph, TriangularSubgraph - Weight types: `One` (unit weight marker), `i32`, `f64` — all implement `WeightElement` trait @@ -165,10 +167,10 @@ Max, Min, Sum, Or, And, Extremum, ExtremumSense - Weight management via inherent methods (`weights()`, `set_weights()`, `is_weighted()`), not traits - `NumericSize` supertrait bundles common numeric bounds (`Clone + Default + PartialOrd + Num + Zero + Bounded + AddAssign + 'static`) -### Overhead System -Reduction overhead is expressed using `Expr` AST (in `src/expr.rs`) with the `#[reduction]` macro. The `overhead` attribute is **required** — omitting it is a compile error: +### Size Relations +Each reduction declares one rule-level size relation using the `Expr` AST in `src/expr.rs`. The `size` declaration is required: ```rust -#[reduction(overhead = { +#[reduction(size = upper_bound { num_vertices = "num_vertices + num_clauses", num_edges = "3 * num_clauses", })] @@ -177,9 +179,10 @@ impl ReduceTo for Source { ... } - Expression strings are parsed at compile time by a Pratt parser in the proc macro crate - Variable names are validated against actual getter methods on the source type — typos cause compile errors - Each problem type provides inherent getter methods (e.g., `num_vertices()`, `num_edges()`) that the overhead expressions reference -- **Overhead expressions describe scaling (asymptotic upper bounds), not exact sizes.** To determine the actual target problem size for a specific instance, read the `reduce_to()` construction code and count the actual variables/constraints/vertices built. -- `ReductionOverhead` stores `Vec<(&'static str, Expr)>` — field name to symbolic expression mappings -- `ReductionEntry` has both symbolic (`overhead_fn`) and compiled (`overhead_eval_fn`) evaluation — the compiled version calls getters directly +- Use `size = exact { ... }` when every formula is an equality and `size = upper_bound { ... }` when every formula is only an upper bound. One rule cannot mix relations. +- Use `size = unavailable { ... }` when no formula is representable, or an auxiliary `unavailable = { ... }` block for omitted target fields. +- `SizeTransform` evaluates and composes formulas with exact rational and arbitrary-precision integer arithmetic. It never performs budget pruning or Pareto ranking. +- Concrete instance sizes are measured independently by compiled endpoint getters on `ReductionEntry`, including target fields on sink variants. - `VariantEntry` has both a complexity string and compiled `complexity_eval_fn` — same pattern - Expressions support: constants, variables, `+`, `-`, `*`, `/`, `^`, `exp()`, `log()`, `sqrt()`, `factorial()` - Complexity strings must use **concrete numeric values only** (e.g., `"2^(2.372 * num_vertices / 3)"`, not `"2^(omega * num_vertices / 3)"`) @@ -197,25 +200,42 @@ Reduction graph nodes use variant key-value pairs from `Problem::variant()`: - Nodes come exclusively from `#[reduction]` registrations; natural edges between same-name variants are inferred from the graph/weight subtype partial order - Each primitive reduction is determined by the exact `(source_variant, target_variant)` endpoint pair - Reduction edges carry `EdgeCapabilities { witness, aggregate, turing }`; graph search defaults to witness mode, aggregate mode is available through `ReductionMode::Aggregate`, and Turing (multi-query) mode via `ReductionMode::Turing` -- `#[reduction]` accepts only `overhead = { ... }` and currently registers witness/config reductions; aggregate-only and Turing edges require manual `ReductionEntry` registration +- `#[reduction]` requires one `size = exact`, `size = upper_bound`, or `size = unavailable` declaration and currently registers witness/config reductions; aggregate-only and Turing edges require manual `ReductionEntry` registration - `Decision

→ P` is an aggregate-only edge (solve optimization, compare to bound); `P → Decision

` is a Turing edge (binary search over decision bound) ### Extension Points - New models register dynamic load/serialize/brute-force dispatch through `declare_variants!` in the model file, not by adding manual match arms in the CLI -- **CLI creation is schema-driven:** `pred create` automatically maps `ProblemSchemaEntry` fields to CLI flags via `snake_case → kebab-case` convention. New models need only: (1) matching CLI flags in `CreateArgs` + `flag_map()`, and (2) type parser support in `parse_field_value()` if using a new field type. No match arm in `create.rs` is needed. -- **CLI flag names must match schema field names.** The canonical name for a CLI flag is the schema field name in kebab-case (e.g., schema field `universe_size` → `--universe-size`, field `subsets` → `--subsets`). Old aliases (e.g., `--universe`, `--sets`) may exist as clap `alias` for backward compatibility at the clap level, but `flag_map()`, help text, error messages, and documentation must use the schema-derived name. Do not add new backward-compat aliases; if a field is renamed in the schema, update the CLI flag name to match. -- **Decision variants** of optimization problems use `Decision

` wrapper. Add via: (1) `decision_problem_meta!` for the inner type, (2) inherent methods on `Decision`, (3) `register_decision_variant!` with `dims`, `fields`, `size_getters`. Schema-driven CLI creation auto-restructures flat JSON into `{inner: {...}, bound}`. +- **Model category is explicit registry metadata.** Every `ProblemSchemaEntry` declares exactly one of `Algebraic`, `Formula`, `Graph`, `Misc`, or `Set`; catalog behavior never derives it from `module_path!()` or source location. +- **CLI creation is registry-driven and two-stage:** the static parser discovers the requested problem spec without registering model subcommands, then a second parse adds flags only for the selected concrete variant. Ordinary models use `ProblemSchemaEntry.fields` directly. Models whose construction differs from persisted JSON own a typed `CreateSpec` and fallible conversion beside the model; CLI and MCP only normalize transport values and invoke the registered constructor. +- **Each construction input has one name and one concrete type per variant.** Do not add compatibility aliases or infer types from flag names. `CreateSpec` field names render as `snake_case → kebab-case` in CLI and remain `snake_case` in MCP. Add a reusable codec only for a genuinely new transport representation, never a model-name parser branch. +- **Random generation is optional and variant-owned.** Not every model has a useful, well-defined random-instance distribution. Add `RandomGenerate` only when the generator has clear semantics and a concrete use (for example, testing or examples); never invent arbitrary bounds or distributions merely to make every model support `--random`. Implement it beside the model (normally through `impl_random_generate!` and a typed `CreateSpec` input DTO), then add `random` only to the applicable `declare_variants!` entries. CLI and MCP discover the exact variant's inputs and callback; never add a model-name random dispatch or advertise random generation on an unsupported variant. +- **Decision variants** of optimization problems use `Decision

` wrapper. Add via: (1) `decision_problem_meta!` for the inner type, (2) inherent methods on `Decision`, (3) `register_decision_variant!` with `dims`, `fields`, `size_getters`. The generated construction spec accepts flat inner fields plus `bound`; persisted JSON remains `{inner: {...}, bound}`. - Aggregate-only models are first-class in `declare_variants!`; aggregate-only and Turing reduction edges still need manual `ReductionEntry` wiring because `#[reduction]` only registers witness/config reductions today - Exact registry dispatch lives in `src/registry/`; alias resolution and partial/default variant resolution live in `problemreductions-cli/src/problem_name.rs` - `pred create` schema-driven dispatch lives in `problemreductions-cli/src/commands/create.rs` (`create_schema_driven()`) -- Canonical paper and CLI examples live in `src/example_db/model_builders.rs` and `src/example_db/rule_builders.rs` +- Canonical model examples live in `src/example_db/model_builders.rs`; rule examples live beside their rules and are collected by `src/rules/mod.rs` ## Conventions +### Numeric Contract + +Follow the [numeric types and arithmetic standard](../docs/src/design.md#numeric-types-and-arithmetic) +for every model and reduction. Before implementation, identify each numeric +input and domain, each computed total and result type, the largest supported +value, every range/sign-changing conversion, overflow behavior, and whether +arithmetic is exact or approximate. Use `TryFrom` at range boundaries and +checked arithmetic for derived values that may overflow. Rust construction, +serde, CLI, and MCP must enforce the same range. + +Issue contributors provide the mathematical definition, domains, and +constraints; implementers derive the Rust representation. Do not require issue +authors to choose implementation types or add implementation-specific numeric +fields to issue templates. Changes to issue templates require user approval. + ### File Naming - Reduction files: `src/rules/_.rs` (e.g., `maximumindependentset_qubo.rs`) - Model files: `src/models//.rs` — category is by input structure: `graph/` (graph input), `formula/` (boolean formula/circuit), `set/` (universe + subsets), `algebraic/` (matrix/linear system/lattice), `misc/` (other) -- Canonical examples: builder functions in `src/example_db/rule_builders.rs` and `src/example_db/model_builders.rs` +- Canonical examples: model builders in `src/example_db/model_builders.rs`; rule-local `canonical_rule_example_specs()` functions collected by `src/rules/mod.rs` - Example binaries in `examples/`: utility/export tools and pedagogical demos only (not per-reduction files) - Test naming: `test__to__closed_loop` @@ -261,7 +281,7 @@ Model review automation checks for a dedicated test file under `src/unit_tests/m - `.claude/` — Claude Code instructions and skills - `docs/book/` — mdBook user documentation (built with `make doc`) - `docs/paper/reductions.typ` — Typst paper with problem definitions and reduction theorems -- `src/example_db/` — Canonical model/rule examples: `model_builders.rs`, `rule_builders.rs` (in-memory builders), `specs.rs` (per-module invariant specs), consumed by `pred create --example` and paper exports +- `src/example_db/` — Model builders, shared example specs, and rule-example aggregation consumed by `pred create --example` and paper exports - `examples/` — Export utilities, graph-analysis helpers, and pedagogical demos ## Documentation Requirements @@ -309,8 +329,8 @@ The complexity string represents the **worst-case time complexity of the best kn 5. Use only concrete numeric values — no symbolic constants (epsilon, omega); inline the actual numbers with citations 6. Variable names must match getter methods on the problem type (enforced at compile time) -### Reduction Overhead (`#[reduction(overhead = {...})]`) -Overhead expressions describe how target problem size relates to source problem size. To verify correctness: +### Reduction Size Relation (`#[reduction(size = exact|upper_bound {...})]`) +Size expressions describe how target problem size relates to source problem size. To verify correctness: 1. Read the `reduce_to()` implementation and count the actual output sizes 2. Check that each field (e.g., `num_vertices`, `num_edges`, `num_sets`) matches the constructed target problem 3. Watch for common errors: universe elements mismatch (edge indices vs vertex indices), worst-case edge counts in intersection graphs (quadratic, not linear), constant factors in circuit constructions diff --git a/.claude/skills/add-model/SKILL.md b/.claude/skills/add-model/SKILL.md index 54f4c2292..87e472566 100644 --- a/.claude/skills/add-model/SKILL.md +++ b/.claude/skills/add-model/SKILL.md @@ -68,14 +68,14 @@ Read these first to understand the patterns: - **Model tests:** `src/unit_tests/models/graph/maximum_independent_set.rs` - **Trait definitions / aggregate types:** `src/traits.rs` (`Problem`), `src/types.rs` (`Aggregate`, `Max`, `Min`, `Sum`, `Or`, `And`, `Extremum`) - **Registry dispatch boundary:** `src/registry/mod.rs`, `src/registry/variant.rs` -- **CLI aliases:** `problemreductions-cli/src/problem_name.rs` -- **CLI creation:** `problemreductions-cli/src/commands/create.rs` +- **CLI and MCP construction:** discovered from the model's registry entry; no frontend model-name dispatch - **Canonical model examples:** `src/example_db/model_builders.rs` ## Pre-review Checklist Before implementing, make sure the plan explicitly covers these items that structural review checks later: -- `ProblemSchemaEntry` metadata is complete for the current schema shape (`display_name`, `aliases`, `dimensions`, and constructor-facing `fields`) +- Derive numeric implementation types from the mathematical domains in the issue and follow `docs/src/design.md#numeric-types-and-arithmetic`; serde/CLI construction uses the same validation as `new`/`try_new`, and boundary tests cover the supported maximum without requiring impractical allocation +- `ProblemSchemaEntry` metadata is complete (`display_name`, `aliases`, `dimensions`, explicit `category`, and construction `fields`) - `Problem::Value` uses the correct aggregate wrapper and witness support is intentional - `declare_variants!` is present with exactly one `default` variant when multiple concrete variants exist - CLI discovery and `pred create ` support are included where applicable @@ -92,6 +92,8 @@ Choose the appropriate sub-module under `src/models/`: - `algebraic/` -- matrices, linear systems, lattices (QUBO, ILP, CVP, BMF) - `misc/` -- unique input structures that don't fit other categories (BinPacking, PaintShop, Factoring) +Declare the same structural choice explicitly in `ProblemSchemaEntry.category`. This is required metadata and is never inferred from `module_path!()` or the file location. + ## Step 1.5: Infer problem size getters From the **best known exact algorithm** complexity (item 9), infer what problem size getter methods the struct should expose. The variables used in the complexity expression define the natural size metrics. @@ -122,7 +124,7 @@ Create `src/models//.rs`: ``` Key decisions: -- **Schema metadata:** `ProblemSchemaEntry` must reflect the current registry schema shape, including `display_name`, `aliases`, `dimensions`, and constructor-facing `fields` +- **Schema metadata:** `ProblemSchemaEntry` must include the explicit structural `category` and reflect the construction interface through `display_name`, `aliases`, `dimensions`, and `fields` - **Objective problems:** use `type Value = Max<_>`, `Min<_>`, or `Extremum<_>` when the model should expose optimization-style witness helpers - **Witness problems:** use `type Value = Or` for existential feasibility problems - **Aggregate-only problems:** use a value-only aggregate such as `Sum<_>`, `And`, or a custom `Aggregate` when witnesses are not meaningful @@ -168,22 +170,32 @@ The CLI now loads, serializes, and brute-force solves problems through the core 1. **Registry-backed dispatch comes from `declare_variants!`:** - Make sure every concrete variant you want the CLI to load is listed in `declare_variants!` - Mark the intended default variant with `default` when applicable + - Declare well-established problem aliases in `ProblemSchemaEntry.aliases` and variant-specific aliases in `declare_variants!`; CLI and MCP discover both from the registry + +## Step 4.5: Add construction support + +CLI and MCP construction are registry-driven. Do not edit either frontend to recognize a model name. + +1. If user-facing inputs exactly match persisted JSON fields, do nothing. The ordinary `declare_variants!` entry uses `ProblemSchemaEntry.fields` as required construction inputs and deserializes the model directly. + +2. If construction has derived fields, renamed inputs, defaults depending on other inputs, or a composite value assembled from multiple inputs, define a model-local DTO with `#[derive(Deserialize, CreateSpec)]`. Its named fields are the complete public construction contract. Use `Option` only for genuinely optional inputs, doc comments for help text, and `#[create(codec = "...")]` when the transport syntax cannot be inferred from the Rust type. Set `ProblemSchemaEntry.fields` to `LocalCreateSpec::FIELDS` so the catalog and executable constructor share the derived metadata. -2. **`problemreductions-cli/src/problem_name.rs`:** - - Add a lowercase alias mapping in `resolve_alias()` (e.g., `"newproblem" => "NewProblem".to_string()`) - - Only add short aliases to the `ALIASES` array if the abbreviation is **well-established in the literature** (e.g., MIS, MVC, SAT, TSP, CVP are standard; "KS" for Knapsack or "BP" for BinPacking are NOT — do not invent new abbreviations) +3. Implement `TryFrom for Model`. Validate before calling constructors that assert or panic, return a descriptive error, compute derived state there, and build the canonical model value. -## Step 4.5: Add CLI creation support +4. Register the spec on each applicable variant: `default Model => "..." create LocalCreateSpec`. Both frontends then discover the inputs automatically and serialize the constructed typed model back to canonical persisted JSON. -CLI creation is **schema-driven** — `pred create ` automatically maps `ProblemSchemaEntry` fields to CLI flags via `snake_case → kebab-case` convention. No match arm in `create.rs` is needed. +5. A new reusable external syntax may add one transport codec. It must dispatch by codec/type, never by canonical model name. Unknown or missing inputs are rejected by the core construction contract. -1. **Ensure CLI flags exist** in `problemreductions-cli/src/cli.rs` (`CreateArgs` struct) for each field in your `ProblemSchemaEntry`. The flag name must match the field name via `snake_case → kebab-case` (e.g., field `edge_weights` → flag `--edge-weights`). If a flag already exists with the right name, you're done. +### Optional random generation -2. **Add new CLI flags** only if the problem needs flags not already present. Add them to `CreateArgs` and update `all_data_flags_empty()` accordingly. Also add entries to the `flag_map()` method on `CreateArgs`. +Random generation is an optional model capability, not a model-completeness requirement. Many models do not have a natural or useful probability distribution over instances; leave random generation unregistered for those models. Do not invent arbitrary size limits, value ranges, or distributions merely to make `--random` available. -3. **Add type parser support** if the field uses a type not yet handled by `parse_field_value()` in `create.rs`. Check the existing type dispatch table — most standard types (`Vec`, `Vec`, `Vec<(usize, usize)>`, graph types, etc.) are already covered. Only add a new parser for genuinely new types. +When the model does have a well-defined generator with a concrete testing or example use, random generation is registry-driven and belongs beside the model. Do not edit CLI or MCP dispatch code. -4. **Schema alignment**: The `ProblemSchemaEntry` fields should list **constructor parameters** (what the user provides), not internal derived fields. For example, if `m` and `n` are derived from a matrix, only list `matrix` and `k` in the schema. Field names must match the struct field names exactly (used for JSON serialization and CLI flag mapping). +1. Define a typed random input DTO with `#[derive(Deserialize, CreateSpec)]`, or reuse a matching shared spec from `crate::random`. +2. Implement `RandomGenerate` with `crate::impl_random_generate!(ConcreteModel, RandomSpec, |spec| { ... })`. Validate values and return `Result`; do not round, clamp, or silently replace invalid inputs. +3. Add `random` only to the exact `declare_variants!` entries that implement the trait: `default Model => "..." create LocalCreateSpec random`. +4. The generated problem must have the same canonical name and variant as the selected registry entry. Use the concrete variant's actual graph and numeric types instead of attaching requested metadata to a different concrete instance. ## Step 4.6: Add canonical model example to example_db @@ -303,6 +315,7 @@ Structural and quality review is handled by the `review-pipeline` stage, not her |---------|-----| | Implementing weight management as a trait | Use inherent methods: `weights()`, `set_weights()`, `is_weighted()` | | Forgetting `inventory::submit!` | Every problem needs a `ProblemSchemaEntry` registration | +| Omitting or inferring the model category | Set the required `ProblemSchemaEntry.category` explicitly to one of `Algebraic`, `Formula`, `Graph`, `Misc`, or `Set`; never parse `module_path!()`. | | Missing `#[path]` test link | Add `#[cfg(test)] #[path = "..."] mod tests;` at file bottom | | Wrong `dims()` | Must match the actual configuration space (e.g., `vec![2; n]` for binary) | | Using the wrong aggregate wrapper | Objective models use `Max` / `Min` / `Extremum`, witness models use `bool`, aggregate-only models use a fold value like `Sum` / `And` | @@ -310,12 +323,12 @@ Structural and quality review is handled by the `review-pipeline` stage, not her | Forgetting `declare_variants!` | Required for variant complexity metadata and registry-backed load/serialize/solve dispatch | | Wrong aggregate wrapper | Use `Max` / `Min` / `Extremum` for objective problems, `Or` for existential witness problems, and `Sum` / `And` (or a custom aggregate) for value-only folds | | Wrong `declare_variants!` syntax | Entries no longer use `opt` / `sat`; one entry per problem may be marked `default` | -| Forgetting CLI alias | Must add lowercase entry in `problem_name.rs` `resolve_alias()` | +| Adding aliases in CLI code | Declare problem aliases in `ProblemSchemaEntry.aliases` and variant aliases in `declare_variants!` | | Adding a hand-written decision model | Use `Decision

` wrapper instead — see `decision_problem_meta!` + `register_decision_variant!` in `src/models/graph/minimum_vertex_cover.rs` for the pattern | | Inventing short aliases | Only use well-established literature abbreviations (MIS, SAT, TSP); do NOT invent new ones | -| Forgetting CLI flags | Schema-driven create needs matching CLI flags in `CreateArgs` for each `ProblemSchemaEntry` field (snake_case → kebab-case). Also add to `flag_map()`. | -| Missing type parser | If the problem uses a new field type, add a handler in `parse_field_value()` in `create.rs` | -| Schema lists derived fields | Schema should list constructor params, not internal fields (e.g., `matrix, k` not `matrix, m, n, k`) | +| Adding frontend model-name branches | Construction is model-owned. Use a local `CreateSpec` and register it with `declare_variants!`; CLI and MCP must discover it. | +| Hand-maintaining custom construction fields twice | Derive `CreateSpec`, use `LocalCreateSpec::FIELDS` in `ProblemSchemaEntry`, and register the same type in `declare_variants!`. | +| Calling a panicking constructor from `TryFrom` | Validate the spec first and return a descriptive conversion error. | | Missing canonical model example | Add a builder in `src/example_db/model_builders.rs` and keep it aligned with paper/example workflows | | Paper example not tested | Must include `test__paper_example` that verifies the exact instance, solution, and solution count shown in the paper | | Claiming direct ILP solving but leaving ` -> ILP` for later | If the issue promises a direct ILP path, implement that rule in the same PR with exact overhead metadata and production-level ILP tests | diff --git a/.claude/skills/add-rule/SKILL.md b/.claude/skills/add-rule/SKILL.md index 33a303af6..c9862a188 100644 --- a/.claude/skills/add-rule/SKILL.md +++ b/.claude/skills/add-rule/SKILL.md @@ -56,6 +56,16 @@ grep "type Value = " src/models/*/.rs src/models/*/.rs If incompatible, STOP and comment on the issue explaining the type mismatch and options. Do NOT proceed. +## Numeric Safety Gate + +Read `docs/src/design.md#numeric-types-and-arithmetic`. Derive implementation +types, supported ranges, and checked conversions from the mathematical source, +target, and reduction algorithm. Ask the contributor only when a mathematical +domain or constraint is ambiguous; do not ask them to choose Rust types. Do not +use `as` for range/sign changes. Check target-size arithmetic and auxiliary +identifiers before constructing the target, verify serde/CLI uses the same +ranges, and add focused boundary tests. + ## Reference Implementations Read these first to understand the patterns: @@ -106,13 +116,19 @@ impl ReductionResult for ReductionXToY { type Source = SourceType; type Target = TargetType; fn target_problem(&self) -> &Self::Target { &self.target } - fn extract_solution(&self, target_solution: &[usize]) -> Vec { - // Map target solution back to source solution - // If Step 1 ran: translate the verified Python extract_solution() logic + fn extract_solution( + &self, + target_solution: &[usize], + ) -> crate::rules::ExtractionResult> { + crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?; + let source_solution = /* translate the verified mathematical mapping exactly */; + Ok(source_solution) } } ``` +Every direct extractor must call `validate_target_solution()` once before decoding. It checks only length and value domains, not feasibility, optimality, or rule-specific structure; reject malformed structure with `ExtractionError`. + **ReduceTo with `#[reduction]` macro** (overhead is **required**): ```rust #[reduction(overhead = { @@ -156,6 +172,8 @@ Additional recommended tests: - Edge cases (empty graph, single vertex, etc.) - Weight preservation (if applicable) +Test every malformed representation distinguished by the decoder (for example, zero or multiple one-hot selections, or duplicate permutation entries). The canonical example supplies shared wrong-length and out-of-domain tests. + For aggregate-only reductions, replace the closed-loop witness test with value-chain tests: - Solve the target with `Solver::solve()` - Map the aggregate value back with `extract_value()` @@ -163,9 +181,9 @@ For aggregate-only reductions, replace the closed-loop witness test with value-c Link via `#[cfg(test)] #[path = "..."] mod tests;` at the bottom of the rule file. -## Step 5: Add canonical example to example_db +## Step 5: Add canonical example -Add a builder function in `src/example_db/rule_builders.rs` that constructs a small, canonical instance for this reduction. Follow the existing patterns in that file. Register the builder in `build_rule_examples()`. +Define `canonical_rule_example_specs()` in the rule module and include it from `src/rules/mod.rs::canonical_rule_example_specs()`. This enrolls the rule in shared round-trip, wrong-length, and out-of-domain extraction tests. ## Step 6: Document in paper (MANDATORY — DO NOT SKIP) @@ -231,11 +249,11 @@ Checklist: notation self-contained, complexity cited, overhead consistent, examp ```bash cargo run --example export_graph # Generate reduction_graph.json for docs/paper builds cargo run --example export_schemas # Generate problem schemas for docs/paper builds -make regenerate-fixtures # Regenerate example_db/fixtures/examples.json (slow, needs ILP) +cargo run --features "example-db" --example export_examples make test clippy # Must pass ``` -`make regenerate-fixtures` is required so the paper can load the new rule's example data from `src/example_db/fixtures/examples.json`. Without it, the `reduction-rule` entry in Step 6 will reference missing fixture data. +`export_examples` refreshes the gitignored `docs/paper/data/examples.json` used by the paper. Structural and quality review is handled by the `review-pipeline` stage, not here. The run stage just needs to produce working code. @@ -249,7 +267,9 @@ Structural and quality review is handled by the `review-pipeline` stage, not her ## CLI Impact -Adding a witness-preserving reduction rule does NOT require CLI changes -- the reduction graph is auto-generated from `#[reduction]` macros and the CLI discovers paths dynamically. However, both source and target models must already be fully registered through their model files (`declare_variants!`), aliases as needed in `problem_name.rs`, and `pred create` support where applicable (see `add-model` skill). +Adding a witness-preserving reduction rule does NOT require CLI changes -- the reduction graph is auto-generated from `#[reduction]` macros and the CLI discovers paths dynamically. However, both source and target models must already be fully registered through their model files (`ProblemSchemaEntry` and `declare_variants!`), including any aliases and `pred create` construction contract (see `add-model` skill). + +`ExtractionError` already propagates through `pred extract` and bundle `pred solve`; add a rule-specific CLI test only when the CLI surface changes. Aggregate-only reductions currently have a narrower CLI surface: - `pred solve ` can still compute direct aggregate values for aggregate-only problems @@ -261,7 +281,7 @@ Aggregate-only reductions currently have a narrower CLI surface: - Rule file: `src/rules/_.rs` -- no underscores within a problem name - e.g., `maximumindependentset_qubo.rs`, `minimumvertexcover_maximumindependentset.rs` - Test file: `src/unit_tests/rules/_.rs` -- Canonical example: builder function in `src/example_db/rule_builders.rs` +- Canonical example: `canonical_rule_example_specs()` in the rule module, included from `src/rules/mod.rs` ## Common Mistakes @@ -272,9 +292,10 @@ Aggregate-only reductions currently have a narrower CLI surface: | Wrong overhead expression | Must accurately reflect the size relationship | | Adding extra reduction metadata or duplicate primitive endpoint registration | Keep one primitive registration per endpoint pair and use only the `overhead` form of `#[reduction]` | | Missing `extract_solution` mapping state | Store any index maps needed in the ReductionResult struct | -| Not adding canonical example to `example_db` | Add builder in `src/example_db/rule_builders.rs` | +| Permissive extraction | Validate first, then map exactly or return `ExtractionError` | +| Not adding a canonical example | Add the rule-local spec and include it from `src/rules/mod.rs` | | Not regenerating reduction graph | Run `cargo run --example export_graph` after adding a rule | -| Skipping Step 5 (paper documentation) | **Every rule MUST have a `reduction-rule` entry in the paper. This is mandatory, not optional. PRs without documentation will be rejected.** | -| Source/target model not fully registered | Both problems must already have `declare_variants!`, aliases as needed, and CLI create support -- use `add-model` skill first | +| Skipping Step 6 (paper documentation) | **Every rule MUST have a `reduction-rule` entry in the paper. This is mandatory, not optional. PRs without documentation will be rejected.** | +| Source/target model not fully registered | Both problems must already have `ProblemSchemaEntry`, `declare_variants!`, registry aliases as needed, and a construction contract -- use `add-model` skill first | | Treating a direct-to-ILP rule as a toy stub | Direct ILP reductions need exact overhead metadata and strong semantic regression tests, just like other production ILP rules | | Skipping verification for complex reductions | Verification is default for a reason — `--no-verify` is for trivial identity/complement reductions only | diff --git a/.claude/skills/final-review/SKILL.md b/.claude/skills/final-review/SKILL.md index 633d86ad8..ced146b7d 100644 --- a/.claude/skills/final-review/SKILL.md +++ b/.claude/skills/final-review/SKILL.md @@ -168,12 +168,12 @@ Use `AskUserQuestion` with your recommendation: Scan the PR diff for dangerous actions: -- **Blacklisted files**: If the diff touches `docs/src/reductions/reduction_graph.json`, `docs/src/reductions/problem_schemas.json`, or `src/example_db/fixtures/examples.json` (legacy, no longer exists), **block merge**. These files are auto-generated and must not be committed in PRs — they are rebuilt by CI/`make doc`/`make paper`. Flag immediately and recommend OnHold. +- **Blacklisted files**: If the diff touches `docs/src/reductions/reduction_graph.json` or `docs/src/reductions/problem_schemas.json`, **block merge**. These files are auto-generated and must not be committed in PRs — they are rebuilt by CI/`make doc`/`make paper`. Flag immediately and recommend OnHold. - **Removed features**: Any existing model, rule, test, or example deleted? - **Unrelated changes**: Files modified that don't belong to this PR (e.g., changes to unrelated models/rules, CI config, Cargo.toml dependency changes not needed for this PR) - **Force push indicators**: Any sign of history rewriting - **Broad modifications**: Changes to core traits, macros, or shared infrastructure that could affect other features -- **No committed `examples.json`**: The example database is generated on demand by `make paper` (via `export_examples`). PRs should not commit `src/example_db/fixtures/examples.json` (legacy path, deleted) or `docs/paper/data/examples.json` (current output path) — both are gitignored build artifacts. +- **No committed `examples.json`**: The example database is generated on demand by `make paper` (via `export_examples`). Do not commit the gitignored `docs/paper/data/examples.json` build artifact. Report findings with fix options for each concern: diff --git a/.claude/skills/find-solver/SKILL.md b/.claude/skills/find-solver/SKILL.md index c9d221e36..704c76ced 100644 --- a/.claude/skills/find-solver/SKILL.md +++ b/.claude/skills/find-solver/SKILL.md @@ -86,9 +86,9 @@ Use `AskUserQuestion` for each question. Format options as **(a)**/**(b)**/**(c) 1. **Web search** the clarified problem description together with terms like "NP-hard", "computational complexity", or "reduction" to find formal problem names and known relationships in the literature. Use `WebSearch` tool. -2. **Run `pred list`** to get the full catalog of available models. Copy-paste the full output into your response. +2. **Search the catalog** with `pred list `. Use `pred list --json` when exhaustive machine-readable discovery is needed. Do not paste the full catalog into the response. -3. **Cross-reference** the web search results against the `pred list` catalog. For each candidate model that exists in the library (3-5 max), present a table: +3. **Cross-reference** the web search results against the catalog. For each candidate model that exists in the library (3-5 max), present a table: | # | Model | Why it might match | Caveat | |---|-------|--------------------|--------| diff --git a/.claude/skills/issue-to-pr/SKILL.md b/.claude/skills/issue-to-pr/SKILL.md index 2573bedd5..87734784e 100644 --- a/.claude/skills/issue-to-pr/SKILL.md +++ b/.claude/skills/issue-to-pr/SKILL.md @@ -92,12 +92,12 @@ Write implementation plan to `docs/plans/YYYY-MM-DD-.md` using `superpower The plan MUST reference the appropriate implementation skill and follow its steps: - **For ordinary `[Model]` issues:** Follow [add-model](../add-model/SKILL.md) Steps 1-7 as the action pipeline -- **For `[Model]` issues that explicitly claim direct ILP solving:** Follow [add-model](../add-model/SKILL.md) Steps 1-7 **and** [add-rule](../add-rule/SKILL.md) Steps 1-6 for the direct ` -> ILP` rule in the same plan / PR +- **For `[Model]` issues that explicitly claim direct ILP solving:** Follow [add-model](../add-model/SKILL.md) Steps 1-7 **and** [add-rule](../add-rule/SKILL.md) Steps 1-7 for the direct ` -> ILP` rule in the same plan / PR - **For `[Rule]` issues:** Follow [add-rule](../add-rule/SKILL.md) Steps 1-7 as the action pipeline. By default, `/add-rule` runs mathematical verification (Step 1) before implementation. If `--no-verify` was passed, include `--no-verify` when invoking `/add-rule` to skip verification. Include the concrete details from the issue (problem definition, reduction algorithm, example, etc.) mapped onto each step. -**Plan batching:** The paper writing step (add-model Step 6 / add-rule Step 5) MUST be in a **separate batch** from the implementation steps, so it gets its own subagent with fresh context. It depends on the implementation being complete (needs exports). Example batch structure for a `[Model]` plan: +**Plan batching:** The paper writing step (add-model Step 6 / add-rule Step 6) MUST be in a **separate batch** from the implementation steps, so it gets its own subagent with fresh context. It depends on the implementation being complete (needs exports). Example batch structure for a `[Model]` plan: - Batch 1: Steps 1-5.5 (implement model, register, CLI, tests) - Batch 2: Step 6 (write paper entry — depends on batch 1 for exports) @@ -112,8 +112,8 @@ For a `[Model]` issue with an explicit direct ILP claim, use: - Otherwise, ensure the information provided is enough to implement a solver. **Example rules:** -- Implement the user-provided example instance in the canonical `example_db` path for the issue (`src/example_db/model_builders.rs` or `src/example_db/rule_builders.rs`, as appropriate). -- Run the relevant export and fixture regeneration steps; verify the generated example data against the user-provided information. +- Implement the user-provided example in `src/example_db/model_builders.rs` for a model, or in the rule-local `canonical_rule_example_specs()` for a rule. +- Run the relevant exports and verify the generated example data against the user-provided information. - Present in `docs/paper/reductions.typ` in tutorial style with clear intuition (see KColoring->QUBO section for reference). ### 6. Create PR (or Resume Existing) diff --git a/.claude/skills/review-paper/SKILL.md b/.claude/skills/review-paper/SKILL.md index d1c98cdf6..e5a8e2378 100644 --- a/.claude/skills/review-paper/SKILL.md +++ b/.claude/skills/review-paper/SKILL.md @@ -46,7 +46,7 @@ For each of the 10 entries, read the full entry text and evaluate against the ch | M3. Self-contained notation | Every symbol in `def` is defined before first use | | M4. Background text | Body contains at least 2 sentences of background/motivation | | M5. Example present | Body contains `*Example.*` or `Example.` | -| M6. Example from fixture | Example data matches `src/example_db/fixtures/examples.json` (not invented) — check by loading the JSON and comparing | +| M6. Example from fixture | Example data matches `docs/paper/data/examples.json` (not invented) — check by loading the JSON and comparing | | M7. Figure present | Body contains `#figure(` | | M8. Pred commands | Body contains `pred-commands(` or `pred create` | | M9. Algorithm citation | Complexity claims have `@citation` or a footnote explaining absence | @@ -72,7 +72,7 @@ For each of the 10 entries, read the full entry text and evaluate against the ch | M3. Proof length | Proof is at least 3 sentences (not just "trivial" or a one-liner) | | M4. Overhead documented | Overhead is auto-generated from JSON (verify edge exists in `reduction_graph.json`) | | M5. Example present | `example: true` and example renders correctly | -| M6. Example from fixture | Example data matches `src/example_db/fixtures/examples.json` | +| M6. Example from fixture | Example data matches `docs/paper/data/examples.json` | | M7. Pred commands | Example section contains `pred-commands(` with create/reduce/evaluate pipeline | | M8. Both directions | If the reverse rule also exists in the graph, check it has its own entry | diff --git a/.claude/skills/review-pipeline/SKILL.md b/.claude/skills/review-pipeline/SKILL.md index 9849a5432..51930ca79 100644 --- a/.claude/skills/review-pipeline/SKILL.md +++ b/.claude/skills/review-pipeline/SKILL.md @@ -175,7 +175,7 @@ Invoke `/review-quality` (file: `.claude/skills/review-quality/SKILL.md`) with t 2. **Invoke `/agentic-tests:test-feature`** (file: `~/.claude/commands/agentic-tests:test-feature.md`) with the identified feature. This simulates a downstream user exercising the feature from docs and examples. **Minimum test checklist** for the agentic tester: - - `pred list` — verify the new model/rule appears in the catalog + - For models, `pred list `; for rules, `pred list --rules ` — verify the new catalog entry appears - `pred show ` — verify details display correctly - `pred create --example ` — verify example instance creation works - `pred solve ` — verify solving works on the example diff --git a/.claude/skills/review-structural/SKILL.md b/.claude/skills/review-structural/SKILL.md index 5c30e15b6..85b7d55c8 100644 --- a/.claude/skills/review-structural/SKILL.md +++ b/.claude/skills/review-structural/SKILL.md @@ -61,11 +61,12 @@ Only run if review type includes "model". Given: problem name `P`, category `C`, | 9 | Registered in `{C}/mod.rs` | `Grep("mod {F}", "src/models/{C}/mod.rs")` | | 10 | Re-exported in `models/mod.rs` | `Grep("{P}", "src/models/mod.rs")` | | 11 | Variant registration exists | `Grep("declare_variants!|VariantEntry", file)` | -| 12 | CLI `resolve_alias` entry | `Grep("{P}", "problemreductions-cli/src/problem_name.rs")` | -| 13 | CLI `create` support | Schema-driven: verify each `ProblemSchemaEntry` field has a matching CLI flag in `CreateArgs` (field `snake_case` → flag `kebab-case`). Check `flag_map()` includes the flag. If the field type is unusual, verify `parse_field_value()` handles it. | +| 12 | Alias registration | If aliases are claimed, verify problem aliases are in `ProblemSchemaEntry.aliases` and variant aliases are in `declare_variants!`; no frontend alias branch | +| 13 | CLI `create` support | Run `pred create --help` for the concrete variant. Verify its flags and types come from the registered construction inputs (`ProblemSchemaEntry.fields` or the model-local `CreateSpec`), with a reusable codec for any unusual transport syntax. | | 14 | Canonical model example registered | `Grep("{P}", "src/example_db/model_builders.rs")` | | 15 | Paper `display-name` entry | `Grep('"{P}"', "docs/paper/reductions.typ")` | | 16 | Paper `problem-def` block | `Grep('problem-def.*"{P}"', "docs/paper/reductions.typ")` | +| 17 | Numeric contract | Derive the expected representation from the mathematical definition, then compare schema types, Rust fields, aggregate/total type, constructor and serde validation, conversions, overflow behavior, and boundary tests against `docs/src/design.md#numeric-types-and-arithmetic` | ### Rule Checklist @@ -81,16 +82,17 @@ Only run if review type includes "rule". Given: source `S`, target `T`, rule fil | 6 | Test file exists | `Glob("src/unit_tests/rules/{R}.rs")` | | 7 | Closed-loop test present | `Grep("fn test_.*closed_loop\|fn test_.*to_.*basic", test_file)` | | 8 | Registered in `rules/mod.rs` | `Grep("mod {R}", "src/rules/mod.rs")` | -| 9 | Canonical rule example registered | `Grep("{S}|{T}|{R}", "src/example_db/rule_builders.rs")` | +| 9 | Canonical rule example registered | `Grep("canonical_rule_example_specs", rule file)` and verify it is included by `src/rules/mod.rs` | | 10 | Example-db lookup tests exist | `Grep("find_rule_example|build_rule_db", "src/unit_tests/example_db.rs")` | | 11 | Paper `reduction-rule` entry | `Grep('reduction-rule.*"{S}".*"{T}"', "docs/paper/reductions.typ")` | +| 12 | Extraction contract | Direct decoders call `validate_target_solution()`, enforce rule-specific structure, and test malformed cases; the helper does not establish feasibility or optimality. Composed extractors may delegate. | +| 13 | Numeric contract | Compare source/target types, size arithmetic, coefficients, bounds, auxiliary IDs, conversions, overflow behavior, and boundary tests against `docs/src/design.md#numeric-types-and-arithmetic` | ## Step 2b: Blacklisted File Check Scan the PR's changed files for auto-generated files that must never be committed: - `docs/src/reductions/reduction_graph.json` - `docs/src/reductions/problem_schemas.json` -- `src/example_db/fixtures/examples.json` (legacy path, deleted on main) - `docs/paper/data/examples.json` (current output path, gitignored) If any of these files appear in the diff, report **FAIL — blacklisted auto-generated file committed**. These files are rebuilt by CI/`make doc`/`make paper` and must not be in PRs. @@ -111,12 +113,14 @@ Report pass/fail. If tests fail, identify which tests. **Do NOT fix anything** 2. **`dims()` correctness** — Does it return the actual configuration space? (e.g., `vec![2; n]` for binary) 3. **Size getter consistency** — Do inherent getter methods (e.g., `num_vertices()`, `num_edges()`) match names used in overhead expressions? 4. **Weight handling** — Are weights managed via inherent methods, not traits? +5. **Numeric safety** — Are element and total types distinct where required, do serde and constructors enforce the same range, and are overflow and non-finite values rejected explicitly? ### For Rules: -1. **`extract_solution` correctness** — Does it correctly invert the reduction? Does the returned solution have the right length (source dimensions)? +1. **`extract_solution` correctness** — Does it implement the mathematical inverse? Is every branch either a defined mathematical case or an `ExtractionError`, with no defaulting, truncation, clamping, panic, or recovery? 2. **Overhead accuracy** — Does `overhead = { field = "expr" }` reflect the actual size relationship? 3. **Example quality** — Is it tutorial-style? Does the JSON export include both source and target data? 4. **Paper quality** — Is the reduction-rule statement precise? Is the proof sketch sound? +5. **Numeric safety** — Are target sizes and auxiliary IDs checked before construction, with no unchecked narrowing or exact-to-`f64` shortcut? ## Step 5: Issue Compliance Review diff --git a/.claude/skills/run-pipeline/SKILL.md b/.claude/skills/run-pipeline/SKILL.md index 1a0229a66..731b7e550 100644 --- a/.claude/skills/run-pipeline/SKILL.md +++ b/.claude/skills/run-pipeline/SKILL.md @@ -1,6 +1,6 @@ --- name: run-pipeline -description: Pick a Ready issue from the GitHub Project board, move it through In Progress -> issue-to-pr -> Review pool +description: Pick a Ready issue from the GitHub Project board, move it from In Progress through issue-to-pr into Review pool --- # Run Pipeline @@ -79,7 +79,7 @@ Score only **eligible** issues on three criteria. For `[Model]` issues, extract | Criterion | Weight | How to Assess | |-----------|--------|---------------| | **C1: Industrial/Theoretical Importance** | 3 | Read the report's issue summary for each eligible issue. Score 0-2: **2** = widely used in industry or foundational in complexity theory (e.g., ILP, SAT, MaxFlow, TSP, GraphColoring); **1** = moderately important or well-studied (e.g., SubsetSum, SetCover, Knapsack); **0** = niche or primarily academic | -| **C2: Related to Existing Problems** | 2 | Use the report's Ready/In-progress context plus `pred list` if needed. Score 0-2: **2** = directly related (shares input structure or has known reductions to/from ≥2 existing problems, but is NOT a trivial variant of an existing one); **1** = loosely related (same domain, connects to 1 existing problem); **0** = isolated or is essentially a variant/renaming of an existing problem | +| **C2: Related to Existing Problems** | 2 | Use the report's Ready/In-progress context plus `pred list ` or `pred list --json` if needed. Score 0-2: **2** = directly related (shares input structure or has known reductions to/from ≥2 existing problems, but is NOT a trivial variant of an existing one); **1** = loosely related (same domain, connects to 1 existing problem); **0** = isolated or is essentially a variant/renaming of an existing problem | | **C3: Unblocks Pending Rules** | 2 | Read the `Pending rules unblocked` count already printed in the report for each eligible issue. Score 0-2: **2** = unblocks ≥2 pending rules; **1** = unblocks 1 pending rule; **0** = does not unblock any pending rule | **Final score** = C1 × 3 + C2 × 2 + C3 × 2 (max = 12) diff --git a/.claude/skills/verify-reduction/SKILL.md b/.claude/skills/verify-reduction/SKILL.md index 76f1246f6..ad101fc7e 100644 --- a/.claude/skills/verify-reduction/SKILL.md +++ b/.claude/skills/verify-reduction/SKILL.md @@ -1,6 +1,6 @@ --- name: verify-reduction -description: Standalone mathematical verification of a reduction rule — generates Typst proof, constructor Python script (>=5000 checks), and adversary Python script (>=5000 independent checks). Reports verdict. No artifacts saved. +description: Standalone mathematical verification of a reduction rule — generates a Typst proof plus constructor and independent adversary scripts with at least 5000 checks each. Reports a verdict without saving artifacts. --- # Verify Reduction @@ -36,22 +36,61 @@ pred show --json ### Type compatibility gate — MANDATORY -Check source/target `Value` types before any work: +Check source/target `Value` types before any work. The `grep` only locates the definitions; it does +not resolve generic parameters or associated types: ```bash grep "type Value = " src/models/*/.rs src/models/*/.rs ``` +Resolve both concrete types completely before declaring compatibility: + +1. Substitute every concrete generic argument from the proposed rule. +2. Follow every type alias and associated type to its defining `impl`. +3. Record the substitution chain and the source file evidence in the verification report. +4. If any generic or associated type remains unresolved, run a compile-backed temporary Rust probe + using `std::any::type_name::<::Value>()`. Build the probe from `/tmp` + with a path dependency on this repository; do not modify the repository. + +Never infer a Rust value type from the mathematical problem name, from unit-weight terminology, or +from the Python verifier's integer representation. In particular, arbitrary-precision Python +integers do not establish that a Rust objective type is `usize` or that it is closed under all +legal source instances. + +Required report format: + +```text +TYPE RESOLUTION: + Source syntax: Min + Substitutions: W = One; ::Sum = i32 + Source resolved: Min + Target syntax: Min + Target resolved: Min + Full-domain compatibility: FAILED +``` + **Compatible pairs for `ReduceTo` (witness-capable):** -- `Or`->`Or`, `Min`->`Min`, `Max`->`Max` (same type) +- `Or`->`Or` +- `Min`->`Min`, `Max`->`Max` (identical resolved inner type) - `Or`->`Min`, `Or`->`Max` (feasibility embeds into optimization) +`Min`->`Min` or `Max`->`Max` with `S != T` is not automatically compatible. Proceed +only if the rule or source model declares a bound covering every legal source instance and the +verification proves a total, order-preserving conversion over that full declared domain. Otherwise +STOP and report a value-domain mismatch. + **Incompatible — STOP if any of these:** - `Min`->`Or` or `Max`->`Or` — optimization source has no threshold K; needs a decision-variant source model - `Max`->`Min` or `Min`->`Max` — opposite optimization directions; needs `ReduceToAggregate` or a decision-variant wrapper - `Or`->`Sum` or `Min`->`Sum` — Sum is aggregate-only; needs `ReduceToAggregate` - Any pair involving `And` or `Sum` on the target side +**Regression case:** `MinimumDominatingSet` resolves to `Min` because +`::Sum = i32`; `MinimumHittingSet` resolves to `Min`. Report +`Min -> Min`, not `Min -> Min`. Without an explicit source-size bound, +the full-domain type gate fails even though the classical cardinality reduction is mathematically +correct and exhaustive small-instance checks pass. + If incompatible, STOP and report the type mismatch and options. Do NOT proceed. ### If compatible @@ -199,6 +238,10 @@ Every item must be YES. If any is NO, go back and fix. - [ ] Zero hand-waving language - [ ] Zero scratch work +### Type gate +- [ ] Concrete Rust `Value` types fully resolved with substitution evidence +- [ ] Different numeric domains either rejected or covered by an explicit full-domain range proof + ### Constructor Python - [ ] 0 failures, >=5,000 total checks - [ ] All 7 sections present and non-empty diff --git a/.claude/skills/write-model-in-paper/SKILL.md b/.claude/skills/write-model-in-paper/SKILL.md index 9ded09507..e3c95d4dd 100644 --- a/.claude/skills/write-model-in-paper/SKILL.md +++ b/.claude/skills/write-model-in-paper/SKILL.md @@ -126,16 +126,16 @@ achieves $O^*(2^n)$ @bjorklund2009. ### 3c. Example with Visualization -A concrete small instance that illustrates the problem. **The example must use data from the checked-in canonical fixture DB**, not an independently invented instance. +A concrete small instance that illustrates the problem. **Use the generated canonical example data**, not an independently invented instance. #### Sourcing example data -1. If you changed example builders/specs, run `make regenerate-fixtures` to refresh `src/example_db/fixtures/examples.json`. -2. Find the problem's entry in `src/example_db/fixtures/examples.json` under `models` — it contains the canonical `instance`, `samples`, and `optimal` fields. +1. If you changed example builders/specs, run `cargo run --features "example-db" --example export_examples`. +2. Find the problem's entry in `docs/paper/data/examples.json` under `models` — it contains the canonical `instance`, `samples`, and `optimal` fields. 3. Use the values from `instance` in the paper example (translating 0-indexed code values to 1-indexed math notation where conventional, e.g., vertices {0,...,n-1} → {1,...,n}). 4. Use `optimal` configurations to show the solution. -**Do not invent a different instance.** If the canonical example is too large or not pedagogically ideal, fix it in `canonical_model_example_specs()` first, re-run `make regenerate-fixtures`, then write the paper entry from the updated JSON. +**Do not invent a different instance.** If the canonical example is unsuitable, fix it in `canonical_model_example_specs()`, re-run `export_examples`, then use the updated JSON. #### Requirements @@ -206,7 +206,7 @@ make paper - [ ] **Notation self-contained**: every symbol in `def` is defined before first use - [ ] **Background present**: historical context, applications, or structural properties - [ ] **Algorithms cited**: every complexity claim has `@citation` or footnote warning -- [ ] **Example from JSON**: instance data matches `src/example_db/fixtures/examples.json` canonical example (not independently invented) +- [ ] **Example from JSON**: instance data matches the canonical entry in `docs/paper/data/examples.json` - [ ] **Evaluation shown**: objective/verifier computed on the example solution - [ ] **Diagram included**: figure with caption and label for graph/matrix/set visualization - [ ] **Paper compiles**: `make paper` succeeds without errors diff --git a/.claude/skills/write-rule-in-paper/SKILL.md b/.claude/skills/write-rule-in-paper/SKILL.md index 0d9755b1b..b1bda1d9c 100644 --- a/.claude/skills/write-rule-in-paper/SKILL.md +++ b/.claude/skills/write-rule-in-paper/SKILL.md @@ -7,7 +7,7 @@ description: Use when writing or improving a reduction-rule entry in the Typst p Full authoring guide for writing a `reduction-rule` entry in `docs/paper/reductions.typ`. Covers Typst mechanics, writing quality, and verification. -> **Note:** This content is also inlined in `add-rule` Step 5 (condensed form). This standalone version has more detail and is useful for improving existing entries. +> **Note:** This content is also inlined in `add-rule` Step 6 (condensed form). This standalone version has more detail and is useful for improving existing entries. ## Reference Example @@ -17,8 +17,8 @@ Full authoring guide for writing a `reduction-rule` entry in `docs/paper/reducti Before using this skill, ensure: - The reduction is implemented and tested (`src/rules/_.rs`) -- A canonical example exists in `src/example_db/rule_builders.rs` -- If the canonical example changed, fixtures are regenerated (`make regenerate-fixtures`) +- A rule-local `canonical_rule_example_specs()` exists and is included by `src/rules/mod.rs` +- If the canonical example changed, regenerate the paper data with `cargo run --features "example-db" --example export_examples` - The reduction graph and schemas are up to date (`cargo run --example export_graph && cargo run --example export_schemas`) ## Source Material @@ -38,7 +38,7 @@ Do NOT invent proofs — always cross-check against the issue and derivation sou ``` Where: -- `load-example(source, target, ...)` looks up the canonical rule entry from `src/example_db/fixtures/examples.json` +- `load-example(source, target, ...)` looks up the canonical rule entry from `docs/paper/data/examples.json` - The returned record contains `source`, `target`, and `solutions` - Access fields: `src_tgt.source.instance`, `src_tgt.target.instance`, `src_tgt_sol.source_config`, `src_tgt_sol.target_config` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e703c4fcd..3ef1a9fd8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,16 +49,65 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - name: Run clippy - run: cargo clippy --all-targets --features ilp-highs -- -D warnings + run: cargo clippy --all-targets --features example-db -- -D warnings - # Cross-compile the portable Rust surface to RISC-V Linux, then execute the - # CLI under QEMU user-mode emulation to verify runtime behavior (not just - # that the binary links). + # Build and exercise the HiGHS-backed CLI natively on Apple Silicon. + macos-arm64: + name: macOS ARM64 build & run + runs-on: macos-15 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Verify ARM64 host + run: "rustc -vV | grep 'host: aarch64-apple-darwin'" + - name: Build workspace + run: cargo build --workspace + - name: Run CLI and ILP smoke test + run: | + target/debug/pred list | head -5 + target/debug/pred create MaximumIndependentSet --graph 0-1,1-2,2-3,3-4,4-0 -o mis.json + target/debug/pred solve mis.json --solver ilp | tee solve.out + grep -q '"kind": "ilp"' solve.out + grep -q '"evaluation": "Max(2)"' solve.out + + # Build and exercise the HiGHS-backed CLI natively on 64-bit Windows. + windows-x86_64: + name: Windows x86_64 build & run + runs-on: windows-2025 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Verify x86_64 host + shell: pwsh + run: | + $hostInfo = rustc -vV | Out-String + if ($hostInfo -notmatch 'host: x86_64-pc-windows-msvc') { + throw "Unexpected Rust host:`n$hostInfo" + } + - name: Build workspace + run: cargo build --workspace + - name: Run CLI and ILP smoke test + shell: pwsh + run: | + $pred = 'target/debug/pred.exe' + & $pred list | Select-Object -First 5 + & $pred create MaximumIndependentSet --graph 0-1,1-2,2-3,3-4,4-0 -o mis.json + & $pred solve mis.json --solver ilp | Tee-Object -FilePath solve.out + $solveOutput = Get-Content solve.out -Raw + if ($solveOutput -notmatch '"kind": "ilp"') { + throw 'Expected the ILP solver to run' + } + if ($solveOutput -notmatch '"evaluation": "Max\(2\)"') { + throw 'Expected the maximum independent set value to be 2' + } + + # Cross-compile the full HiGHS-backed CLI to RISC-V Linux, then execute an + # ILP solve under QEMU user-mode emulation (not just a link check). riscv: name: RISC-V build & run runs-on: ubuntu-latest - env: - FEATURES: "ilp-lp-solvers" steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable @@ -68,11 +117,13 @@ jobs: - name: Install RISC-V cross tools and QEMU run: | sudo apt-get update - sudo apt-get install -y gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu qemu-user + sudo apt-get install -y gcc-riscv64-linux-gnu g++-riscv64-linux-gnu binutils-riscv64-linux-gnu qemu-user - name: Build workspace for RISC-V env: CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc - run: cargo build --workspace --no-default-features --features "$FEATURES" --target riscv64gc-unknown-linux-gnu + CC_riscv64gc_unknown_linux_gnu: riscv64-linux-gnu-gcc + CXX_riscv64gc_unknown_linux_gnu: riscv64-linux-gnu-g++ + run: cargo build --workspace --target riscv64gc-unknown-linux-gnu - name: Verify RISC-V executable run: | riscv64-linux-gnu-readelf -h target/riscv64gc-unknown-linux-gnu/debug/pred \ @@ -83,9 +134,10 @@ jobs: run: | # Registry loads and the catalog renders on RISC-V. $PRED list | head -5 - # End-to-end create + brute-force solve: MIS of a 5-cycle is 2. + # End-to-end reduction and HiGHS solve: MIS of a 5-cycle is 2. $PRED create MaximumIndependentSet --graph 0-1,1-2,2-3,3-4,4-0 -o mis.json - $PRED solve mis.json --solver brute-force | tee solve.out + $PRED solve mis.json --solver ilp | tee solve.out + grep -q '"kind": "ilp"' solve.out grep -q '"evaluation": "Max(2)"' solve.out # Build, test (nextest), doc tests, and paper. @@ -95,7 +147,7 @@ jobs: # Single feature set across compile + test + doctest so artifacts are reused # (no redundant full recompile between steps). env: - FEATURES: "ilp-highs example-db" + FEATURES: "example-db" steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable @@ -109,14 +161,6 @@ jobs: - name: Compile tests run: cargo nextest run --no-run --workspace --features "$FEATURES" - # The subprocess example tests (tests/suites/examples.rs) shell out to - # `cargo run --example … --features ilp-highs`. Pre-build those example - # binaries with that exact feature set so the subprocess reuses artifacts - # instead of recompiling the whole crate mid-test (which otherwise adds - # 60s+ to a single test's wall-clock and would trip the nextest timeout). - - name: Build examples (for subprocess tests) - run: cargo build --examples --features ilp-highs - - name: Run tests run: cargo nextest run --workspace --features "$FEATURES" @@ -127,8 +171,8 @@ jobs: - name: Build paper run: make paper - # Coverage. Feature set intentionally matches the historical coverage gate - # (ilp-highs only) to keep the codecov baseline stable. + # Coverage intentionally excludes the optional example database to keep the + # historical codecov baseline stable. coverage: name: Code Coverage runs-on: ubuntu-latest @@ -142,7 +186,7 @@ jobs: tool: cargo-llvm-cov,nextest - uses: Swatinem/rust-cache@v2 - name: Generate coverage - run: cargo llvm-cov nextest --features ilp-highs --workspace --lcov --output-path lcov.info + run: cargo llvm-cov nextest --workspace --lcov --output-path lcov.info - name: Upload to codecov.io uses: codecov/codecov-action@v5 with: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7aa4e1bba..109d14db8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -57,7 +57,7 @@ jobs: run: typst compile --root . docs/paper/reductions.typ book/reductions.pdf - name: Build rustdoc - run: RUSTDOCFLAGS="--default-theme=dark" cargo doc --features ilp-highs --no-deps + run: RUSTDOCFLAGS="--default-theme=dark" cargo doc --no-deps - name: Combine documentation run: | diff --git a/Cargo.toml b/Cargo.toml index 3b0066232..781b9385e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,10 @@ [workspace] -members = [".", "problemreductions-macros", "problemreductions-cli"] +members = [ + ".", + "problemreductions-expr", + "problemreductions-macros", + "problemreductions-cli", +] [package] name = "problemreductions" @@ -12,13 +17,8 @@ keywords = ["np-hard", "optimization", "reduction", "sat", "graph"] categories = ["algorithms", "science"] [features] -default = ["ilp-highs"] example-db = [] -ilp = ["ilp-highs"] # backward compat shorthand -ilp-solver = [] # marker: enables ILP solver code -ilp-highs = ["ilp-solver", "dep:good_lp", "good_lp/highs"] -ilp-cplex = ["ilp-solver", "dep:good_lp", "good_lp/cplex-rs"] -ilp-lp-solvers = ["ilp-solver", "dep:good_lp", "good_lp/lp-solvers"] +benchmarks = ["dep:criterion"] [dependencies] petgraph = { version = "0.8", features = ["serde-1"] } @@ -27,26 +27,36 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "2.0" num-bigint = "0.4" +num-rational = "0.4" num-traits = "0.2" -good_lp = { version = "=1.14.2", default-features = false, optional = true } +good_lp = { version = "=1.14.2", default-features = false, features = ["highs"] } inventory = "0.3" ordered-float = "5.0" rand = "0.10" +criterion = { version = "0.8", optional = true } problemreductions-macros = { version = "0.6.0", path = "problemreductions-macros" } +problemreductions-expr = { version = "0.6.0", path = "problemreductions-expr" } [dev-dependencies] proptest = "1.0" -criterion = "0.8" [[bench]] name = "solver_benchmarks" harness = false +required-features = ["benchmarks"] [[example]] name = "export_examples" path = "examples/export_examples.rs" required-features = ["example-db"] +[profile.dev] +debug = "line-tables-only" + +[profile.debug-dev] +inherits = "dev" +debug = "full" + [profile.release] lto = true codegen-units = 1 diff --git a/Makefile b/Makefile index ce9056ca2..df36f63c8 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,11 @@ # Makefile for problemreductions -.PHONY: help build test mcp-test fmt clippy doc mdbook paper clean coverage rust-export compare qubo-testdata export-schemas release run-plan run-issue run-pipeline run-pipeline-forever run-review run-review-forever board-next board-claim board-ack board-move issue-context issue-guards pr-context pr-wait-ci worktree-issue worktree-pr diagrams jl-testdata cli cli-demo copilot-review papers papers-lookup papers-download papers-scihub papers-status papers-push papers-pull papers-index +.PHONY: help build test bench mcp-test fmt clippy doc mdbook paper clean coverage rust-export compare qubo-testdata export-schemas release run-plan run-issue run-pipeline run-pipeline-forever run-review run-review-forever board-next board-claim board-ack board-move issue-context issue-guards pr-context pr-wait-ci worktree-issue worktree-pr diagrams jl-testdata cli cli-demo copilot-review papers papers-lookup papers-download papers-scihub papers-status papers-push papers-pull papers-index RUNNER ?= codex CLAUDE_MODEL ?= opus CODEX_MODEL ?= gpt-5.4 +TEST_FEATURES := example-db # Cross-platform sed in-place: macOS needs -i '', Linux needs -i SED_I := sed -i$(shell if [ "$$(uname)" = "Darwin" ]; then echo " ''"; fi) @@ -14,6 +15,7 @@ help: @echo "Available targets:" @echo " build - Build the project" @echo " test - Run all tests" + @echo " bench - Run solver benchmarks" @echo " mcp-test - Run MCP server tests" @echo " fmt - Format code with rustfmt" @echo " fmt-check - Check code formatting" @@ -21,7 +23,7 @@ help: @echo " doc - Build mdBook documentation" @echo " diagrams - Generate SVG diagrams from Typst (light + dark)" @echo " mdbook - Build and serve mdBook (with live reload)" - @echo " paper - Build Typst paper from checked-in fixtures (requires typst)" + @echo " paper - Generate example data and build the Typst paper (requires typst)" @echo " coverage - Generate coverage report (requires cargo-llvm-cov)" @echo " clean - Clean build artifacts" @echo " check - Quick check (fmt + clippy + test)" @@ -62,11 +64,15 @@ help: # Build the project build: - cargo build --features ilp-highs + cargo build # Run all workspace tests (including ignored tests) test: - cargo test --features "ilp-highs example-db" --workspace -- --include-ignored + cargo test --features "$(TEST_FEATURES)" --workspace -- --include-ignored + +# Compile Criterion only when benchmarks are requested +bench: + cargo bench --features benchmarks # Run MCP server tests mcp-test: ## Run MCP server tests @@ -82,7 +88,7 @@ fmt-check: # Run clippy clippy: - cargo clippy --all-targets --features ilp-highs -- -D warnings + cargo clippy --all-targets --features "$(TEST_FEATURES)" -- -D warnings node_modules/elkjs/package.json: package.json package-lock.json npm ci @@ -95,7 +101,7 @@ doc: node_modules/elkjs/package.json cargo run --example export_module_graph bash scripts/generate_doc_snippets.sh target/release/pred mdbook build docs - RUSTDOCFLAGS="--default-theme=dark" cargo doc --features ilp-highs --no-deps + RUSTDOCFLAGS="--default-theme=dark" cargo doc --no-deps rm -rf docs/book/api cp -r target/doc docs/book/api @@ -123,7 +129,7 @@ mdbook: node_modules/elkjs/package.json @echo "Generating CLI doc snippets..." @bash scripts/generate_doc_snippets.sh target/release/pred 2>&1 | tail -1 @echo "Building API docs..." - @RUSTDOCFLAGS="--default-theme=dark" cargo doc --features ilp-highs --no-deps 2>&1 | tail -1 + @RUSTDOCFLAGS="--default-theme=dark" cargo doc --no-deps 2>&1 | tail -1 @echo "Building mdBook..." @mdbook build rm -rf book/api @@ -140,16 +146,16 @@ export-schemas: # Build Typst paper (generates example data on demand) paper: - cargo run --features "example-db" --example export_examples - cargo run --example export_petersen_mapping - cargo run --example export_graph - cargo run --example export_schemas + cargo run --features "$(TEST_FEATURES)" --example export_examples + cargo run --features "$(TEST_FEATURES)" --example export_petersen_mapping + cargo run --features "$(TEST_FEATURES)" --example export_graph + cargo run --features "$(TEST_FEATURES)" --example export_schemas typst compile --root . docs/paper/reductions.typ docs/paper/reductions.pdf # Generate coverage report (requires: cargo install cargo-llvm-cov) coverage: @command -v cargo-llvm-cov >/dev/null 2>&1 || { echo "Installing cargo-llvm-cov..."; cargo install cargo-llvm-cov; } - cargo llvm-cov --features ilp-highs --workspace --html --open + cargo llvm-cov --workspace --html --open # Clean build artifacts clean: @@ -289,16 +295,12 @@ cli-demo: cli $$PRED from QUBO --hops 1; \ \ echo ""; \ - echo "--- 5. path: find reduction paths ---"; \ + echo "--- 5. path: symbolic path enumeration ---"; \ $$PRED path MIS QUBO; \ - $$PRED path MIS QUBO -o $(CLI_DEMO_DIR)/path_mis_qubo.json; \ $$PRED path Factoring SpinGlass; \ - $$PRED path MIS QUBO --cost minimize:num_variables; \ - \ - echo ""; \ - echo "--- 6. path --all: enumerate all paths ---"; \ - $$PRED path MIS QUBO --all; \ - $$PRED path MIS QUBO --all -o $(CLI_DEMO_DIR)/all_paths/; \ + echo "--- 5b. explicitly choose one route from the path set ---"; \ + $$PRED path MIS QUBO -o $(CLI_DEMO_DIR)/paths_mis_qubo.json; \ + jq -e 'first(.paths[] | select(([.path[0].from.name] + [.path[].to.name]) == ["MaximumIndependentSet", "MaximumIndependentSet", "MaximumSetPacking", "MaximumSetPacking", "QUBO"]))' $(CLI_DEMO_DIR)/paths_mis_qubo.json > $(CLI_DEMO_DIR)/path_mis_qubo.json; \ \ echo ""; \ echo "--- 7. export-graph: full reduction graph ---"; \ @@ -307,7 +309,7 @@ cli-demo: cli echo ""; \ echo "--- 8. create: build problem instances ---"; \ $$PRED create MIS --graph 0-1,1-2,2-3,3-4,4-0 -o $(CLI_DEMO_DIR)/mis.json; \ - $$PRED create MIS --graph 0-1,1-2,2-3 --weights 2,1,3,1 -o $(CLI_DEMO_DIR)/mis_weighted.json; \ + $$PRED create MaximumIndependentSet/SimpleGraph/i32 --graph 0-1,1-2,2-3 --weights 2,1,3,1 -o $(CLI_DEMO_DIR)/mis_weighted.json; \ $$PRED create SAT --num-vars 3 --clauses "1,2;-1,3;2,-3" -o $(CLI_DEMO_DIR)/sat.json; \ $$PRED create 3SAT --num-vars 4 --clauses "1,2,3;-1,2,-3;1,-2,3" -o $(CLI_DEMO_DIR)/3sat.json; \ $$PRED create QUBO --matrix "1,-0.5;-0.5,2" -o $(CLI_DEMO_DIR)/qubo.json; \ @@ -340,8 +342,8 @@ cli-demo: cli $$PRED solve $(CLI_DEMO_DIR)/mis_weighted.json; \ \ echo ""; \ - echo "--- 13. reduce: MIS → QUBO (auto-discover path) ---"; \ - $$PRED reduce $(CLI_DEMO_DIR)/mis.json --to QUBO -o $(CLI_DEMO_DIR)/bundle_qubo.json; \ + echo "--- 13. reduce: MIS → QUBO along the explicitly chosen route ---"; \ + $$PRED reduce $(CLI_DEMO_DIR)/mis.json --via $(CLI_DEMO_DIR)/path_mis_qubo.json -o $(CLI_DEMO_DIR)/bundle_qubo.json; \ \ echo ""; \ echo "--- 14. solve bundle: brute-force on reduced QUBO ---"; \ @@ -353,7 +355,9 @@ cli-demo: cli \ echo ""; \ echo "--- 16. solve bundle with ILP: MIS → MVC → ILP ---"; \ - $$PRED reduce $(CLI_DEMO_DIR)/mis.json --to MVC -o $(CLI_DEMO_DIR)/bundle_mvc.json; \ + $$PRED path MIS MVC -o $(CLI_DEMO_DIR)/paths_mis_mvc.json; \ + jq -e 'first(.paths[] | select(([.path[0].from.name] + [.path[].to.name]) == ["MaximumIndependentSet", "MaximumIndependentSet", "MinimumVertexCover"]))' $(CLI_DEMO_DIR)/paths_mis_mvc.json > $(CLI_DEMO_DIR)/path_mis_mvc.json; \ + $$PRED reduce $(CLI_DEMO_DIR)/mis.json --via $(CLI_DEMO_DIR)/path_mis_mvc.json -o $(CLI_DEMO_DIR)/bundle_mvc.json; \ $$PRED solve $(CLI_DEMO_DIR)/bundle_mvc.json --solver ilp; \ \ echo ""; \ @@ -370,7 +374,7 @@ cli-demo: cli echo "Solving with ILP..."; \ $$PRED solve $(CLI_DEMO_DIR)/big.json -o $(CLI_DEMO_DIR)/big_sol.json; \ echo "Reducing to QUBO and solving with brute-force..."; \ - $$PRED reduce $(CLI_DEMO_DIR)/big.json --to QUBO -o $(CLI_DEMO_DIR)/big_qubo.json; \ + $$PRED reduce $(CLI_DEMO_DIR)/big.json --via $(CLI_DEMO_DIR)/path_mis_qubo.json -o $(CLI_DEMO_DIR)/big_qubo.json; \ $$PRED solve $(CLI_DEMO_DIR)/big_qubo.json --solver brute-force -o $(CLI_DEMO_DIR)/big_qubo_sol.json; \ echo "Verifying both solutions have the same evaluation..."; \ ILP_EVAL=$$(jq -r '.evaluation' $(CLI_DEMO_DIR)/big_sol.json); \ diff --git a/docs/agent-profiles/FEATURES.md b/docs/agent-profiles/FEATURES.md index 7980ec62f..65fae1cc9 100644 --- a/docs/agent-profiles/FEATURES.md +++ b/docs/agent-profiles/FEATURES.md @@ -6,5 +6,5 @@ - [Reduction Graph] — Automatic shortest-path search through registered reductions between problem types - [BruteForce Solver] — Enumerate all configurations to find optimal or satisfying solutions - [Variant System] — Graph/weight type parameterization with compile-time complexity registration -- [Overhead System] — Symbolic expressions describing how target problem size relates to source after reduction +- [Size Analysis] — Explain how problem size changes along a path and measure complete instances - [Serialization] — JSON schema export and serde-based serialization for all problem types diff --git a/docs/agent-profiles/SKILLS.md b/docs/agent-profiles/SKILLS.md index b7c7a6e3a..df3ffde16 100644 --- a/docs/agent-profiles/SKILLS.md +++ b/docs/agent-profiles/SKILLS.md @@ -1,20 +1,20 @@ # Skills -Example generation now goes through the example catalog and checked-in fixture DB. +Example generation goes through the example catalog and generated paper data. When a workflow needs a paper/example instance, prefer the catalog path over ad hoc `examples/reduction_*.rs` binaries: -- use `src/example_db/fixtures/examples.json` directly for paper/example data -- use `make regenerate-fixtures` when canonical examples change +- use `docs/paper/data/examples.json` directly for paper/example data +- run `cargo run --features "example-db" --example export_examples` when canonical examples change - use `pred create --example ` to materialize a canonical model example as normal problem JSON - use `pred create --example --to ` to materialize a canonical rule example as normal problem JSON - when adding new example coverage, register a catalog entry instead of creating a new standalone reduction example file Post-refactor extension points: -- new model load/serialize/brute-force dispatch comes from `declare_variants!` in the model file, with explicit `opt` or `sat` markers and an optional `default` +- new model load/serialize/brute-force dispatch comes from `declare_variants!` in the model file, with an optional `default` - alias resolution lives in `problemreductions-cli/src/problem_name.rs` - `pred create` UX lives in `problemreductions-cli/src/commands/create.rs` -- canonical examples live in `src/example_db/model_builders.rs` and `src/example_db/rule_builders.rs` +- model examples live in `src/example_db/model_builders.rs`; rule examples live beside their rules and are collected by `src/rules/mod.rs` - [issue-to-pr] — Convert a GitHub issue into a PR with an implementation plan - [add-model] — Add a new problem model to the codebase diff --git a/docs/agent-profiles/pred-sym-prof-yuki-tanaka.md b/docs/agent-profiles/pred-sym-prof-yuki-tanaka.md index 10ad103ed..bb0dccce7 100644 --- a/docs/agent-profiles/pred-sym-prof-yuki-tanaka.md +++ b/docs/agent-profiles/pred-sym-prof-yuki-tanaka.md @@ -6,7 +6,7 @@ pred-sym (symbolic expression CLI) ## Use Case Three combined scenarios: 1. **Complexity comparison** — Compare algorithm complexity expressions to determine asymptotic equivalence (e.g., O(n^2 + n) == O(n^2), O(n log n) != O(n^2)). -2. **Reduction overhead audit** — Parse and simplify overhead expressions from reduction rules to verify they match expected growth (e.g., '3*num_vertices + num_edges^2'). +2. **Reduction size-contract audit** — Parse and simplify each rule's exact or upper-bound size relation, and verify it against constructed examples. 3. **Teaching complexity notation** — Use pred-sym as a learning/demonstration tool to explore how expressions simplify, evaluate at concrete sizes, and compare growth rates. ## Expected Outcome diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 88cd07556..fe06421ad 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -8,7 +8,8 @@ target: e.target, source-name: graph-data.nodes.at(e.source).name, target-name: graph-data.nodes.at(e.target).name, - overhead: e.overhead, + size-fields: e.size_fields, + size-contract-error: e.size_contract_error, )) #let _edges-by-source-name = { @@ -65,7 +66,7 @@ #show: thmrules.with(qed-symbol: $square$) // === Example JSON helpers === -// Load canonical example database directly from the checked-in fixture file. +// Load the generated canonical example database. #let example-db = json("data/examples.json") // Pre-index rules by (source, target) and models by name so lookups are O(bucket) @@ -496,12 +497,6 @@ ] } -// Format target problem spec for pred reduce --to (handles empty variant dicts) -#let target-spec(data) = { - if data.target.variant.len() == 0 { data.target.problem } - else { data.target.problem + "/" + data.target.variant.values().join("/") } -} - // Format a canonical example's problem spec for pred create --example #let problem-spec(data) = { if data.variant.len() == 0 { data.problem } @@ -558,10 +553,14 @@ if parts.len() > 0 { [#base (#parts.join(", "))] } else { base } } -// Format overhead fields as inline text -#let format-overhead(overhead) = { - let parts = overhead.map(o => raw(o.field + " = " + o.formula)) - [_Overhead:_ #parts.join(", ").] +// Format explicitly classified size fields as inline text. +#let format-size-contract(fields) = { + let parts = fields.map(o => { + if o.contract == "exact" { raw(o.field + " = " + o.formula) } + else if o.contract == "bound-only" { raw(o.field + " <= " + o.formula) } + else { raw(o.field + " unavailable: " + o.reason) } + }) + [_Size contract:_ #parts.join(", ").] } // Unified function for reduction rules: theorem + proof + optional example @@ -582,7 +581,7 @@ else { display-name.at(target) } let src-lbl = label("def:" + source) let tgt-lbl = label("def:" + target) - let overhead = if edge != none and edge.overhead.len() > 0 { edge.overhead } else { none } + let size-fields = if edge != none and edge.size-fields.len() > 0 { edge.size-fields } else { none } let thm-lbl = label("thm:" + source + "-to-" + target) covered-rules.update(old => old + ((source, target),)) @@ -590,7 +589,7 @@ #v(1em) #theorem[ *(*#context { if query(src-lbl).len() > 0 { link(src-lbl)[#src-disp] } else [#src-disp] }* #arrow *#context { if query(tgt-lbl).len() > 0 { link(tgt-lbl)[#tgt-disp] } else [#tgt-disp] }*)* #theorem-body - #if overhead != none { linebreak(); format-overhead(overhead) } + #if size-fields != none { linebreak(); format-size-contract(size-fields) } ] #thm-lbl] proof[#proof-body] @@ -8147,7 +8146,6 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V| let sets = x.instance.sets let k = x.instance.k let bound = x.instance.bound - let config = x.optimal_config let m = sets.len() // Count qualifying tuples by enumerating the Cartesian product let total = sets.fold(1, (acc, s) => acc * s.len()) @@ -8157,12 +8155,11 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V| ][ The $K$th Largest $m$-Tuple problem is MP10 in Garey and Johnson's appendix @garey1979. It is _not known to be in NP_, because a "yes" certificate may need to exhibit $K$ qualifying tuples and $K$ can be exponentially large. The problem is PP-complete under polynomial-time Turing reductions @haase2016, though the special case $m = 2$, $K = 1$ is NP-complete via reduction from Subset Sum. In the general case, the only known exact approach is brute-force enumeration of all $product_(i=1)^m |X_i|$ tuples, so the registered catalog complexity is `total_tuples * num_sets`#footnote[No algorithm improving on brute-force is known for the general $K$th Largest $m$-Tuple problem.]. - *Example.* Let $m = #m$, $B = #bound$, and $K = #k$ with sets #sets.enumerate().map(((i, s)) => [$X_#(i+1) = {#s.map(str).join(", ")}$]).join([, ]). The Cartesian product has $#total$ tuples. For instance, the tuple $(#config.enumerate().map(((i, c)) => str(sets.at(i).at(c))).join(", "))$ has sum $#config.enumerate().map(((i, c)) => sets.at(i).at(c)).sum() >= #bound$, contributing 1 to the count. In total, #k of the #total tuples satisfy the bound, so the answer is _yes_ (count $= K$). + *Example.* Let $m = #m$, $B = #bound$, and $K = #k$ with sets #sets.enumerate().map(((i, s)) => [$X_#(i+1) = {#s.map(str).join(", ")}$]).join([, ]). The Cartesian product has $#total$ tuples. Exactly #k tuples have sum at least #bound, so the answer is _yes_ (count $= K$). The evaluator enumerates the Cartesian product internally and stops once it has found $K$ qualifying tuples. #pred-commands( "pred create --example KthLargestMTuple -o kth-largest-m-tuple.json", "pred solve kth-largest-m-tuple.json --solver brute-force", - "pred evaluate kth-largest-m-tuple.json --config " + config.map(str).join(","), ) ] ] @@ -11434,7 +11431,10 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V| = Reductions -Each reduction is presented as a *Rule* (with linked problem names and overhead from the graph data), followed by a *Proof* (construction, correctness, variable mapping, solution extraction), and optionally a *Concrete Example* (a small instance with verified solution). Problem names in the rule title link back to their definitions in @sec:problems. +Each reduction is presented as a *Rule* (with linked problem names and explicit size contracts from the graph data), followed by a *Proof* (construction, correctness, variable mapping, solution extraction), and optionally a *Concrete Example* (a small instance with verified solution). Problem names in the rule title link back to their definitions in @sec:problems. + +The command blocks assume `route.json` contains the explicitly chosen direct route for +the displayed rule, extracted from the corresponding `pred path` entry. #let max2sat_mc = load-example("Maximum2Satisfiability", "MaxCut") @@ -11445,7 +11445,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example " + problem-spec(max2sat_mc.source) + " -o max2sat.json", - "pred reduce max2sat.json --to " + target-spec(max2sat_mc) + " -o bundle.json", + "pred reduce max2sat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate max2sat.json --config " + max2sat_mc_sol.source_config.map(str).join(","), ) @@ -11496,7 +11496,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example Maximum2Satisfiability -o max2sat.json", - "pred reduce max2sat.json --to " + target-spec(max2sat_ilp) + " -o bundle.json", + "pred reduce max2sat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate max2sat.json --config " + max2sat_ilp_sol.source_config.map(str).join(","), ) @@ -11658,7 +11658,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example MVC -o mvc.json", - "pred reduce mvc.json --to " + target-spec(mvc_mis) + " -o bundle.json", + "pred reduce mvc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mvc.json --config " + mvc_mis_sol.source_config.map(str).join(","), ) @@ -11691,7 +11691,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example " + problem-spec(dmds_mmmc.source) + " -o dmds.json", - "pred reduce dmds.json --to " + target-spec(dmds_mmmc) + " -o bundle.json", + "pred reduce dmds.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate dmds.json --config " + dmds_mmmc_sol.source_config.map(str).join(","), ) @@ -11726,7 +11726,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example " + problem-spec(dmds_msmc.source) + " -o dmds.json", - "pred reduce dmds.json --to " + target-spec(dmds_msmc) + " -o bundle.json", + "pred reduce dmds.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate dmds.json --config " + dmds_msmc_sol.source_config.map(str).join(","), ) @@ -11807,7 +11807,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead [ #pred-commands( "pred create --example " + problem-spec(mvc_lcs.source) + " -o mvc.json", - "pred reduce mvc.json --to " + target-spec(mvc_lcs) + " -o bundle.json", + "pred reduce mvc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mvc.json --config " + mvc_lcs_sol.source_config.map(str).join(","), ) @@ -11848,7 +11848,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example MVC -o mvc.json", - "pred reduce mvc.json --to " + target-spec(mvc_fvs) + " -o bundle.json", + "pred reduce mvc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mvc.json --config " + mvc_fvs_sol.source_config.map(str).join(","), ) @@ -11892,7 +11892,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example MIS -o mis.json", - "pred reduce mis.json --to " + target-spec(mis_clique) + " -o bundle.json", + "pred reduce mis.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mis.json --config " + mis_clique_sol.source_config.map(str).join(","), ) @@ -11948,7 +11948,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example " + problem-spec(dmvc_cc.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(dmvc_cc) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + dmvc_cc_sol.source_config.map(str).join(","), ) @@ -11989,7 +11989,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example MVC -o mvc.json", - "pred reduce mvc.json --to " + target-spec(mvc_aog) + " -o bundle.json", + "pred reduce mvc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mvc.json --config " + mvc_aog_sol.source_config.map(str).join(","), ) @@ -12043,7 +12043,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example SpinGlass -o spinglass.json", - "pred reduce spinglass.json --to " + target-spec(sg_qubo) + " -o bundle.json", + "pred reduce spinglass.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate spinglass.json --config " + sg_qubo_sol.source_config.map(str).join(","), ) @@ -12092,7 +12092,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead extra: [ #pred-commands( "pred create --example CVP -o cvp.json", - "pred reduce cvp.json --to " + target-spec(cvp_qubo) + " -o bundle.json", + "pred reduce cvp.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate cvp.json --config " + cvp_qubo_sol.source_config.map(str).join(","), ) @@ -12115,7 +12115,7 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead $ w_(i,p) = 2^p quad (0 <= p < L_i - 1), quad w_(i,L_i-1) = r_i + 1 - 2^(L_i - 1) $ so that every bit vector represents an offset in ${0, dots, r_i}$. Then $ x_i = ell_i + sum_(p=0)^(L_i-1) w_(i,p) z_(i,p) $ - and the total number of QUBO variables is $N = sum_i L_i$, exactly the exported overhead `num_vars = num_encoding_bits`. + and the total number of QUBO variables is $N = sum_i L_i$, exactly the exported size map `num_vars = num_encoding_bits`. Let $G = A^top A$ and $h = A^top bold(t)$. Writing $bold(x) = bold(ell) + B bold(z)$ for the encoding matrix $B in RR^(n times N)$ gives $ norm(A bold(x) - bold(t))_2^2 = bold(z)^top (B^top G B) bold(z) + 2 bold(z)^top B^top (G bold(ell) - h) + "const" $ @@ -12144,7 +12144,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example " + problem-spec(kc_qubo.source) + " -o kcoloring.json", - "pred reduce kcoloring.json --to " + target-spec(kc_qubo) + " -o bundle.json", + "pred reduce kcoloring.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate kcoloring.json --config " + kc_qubo_sol.source_config.map(str).join(","), ) @@ -12242,7 +12242,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_qc.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_qc) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred evaluate ksat.json --config " + ksat_qc_sol.source_config.map(str).join(","), ) @@ -12305,7 +12305,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_ss.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_ss) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_ss_sol.source_config.map(str).join(","), ) @@ -12346,7 +12346,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example SubsetSum -o subsetsum.json", - "pred reduce subsetsum.json --to " + target-spec(ss-cvp) + " -o bundle.json", + "pred reduce subsetsum.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate subsetsum.json --config " + ss-cvp-sol.source_config.map(str).join(","), ) @@ -12432,7 +12432,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example " + problem-spec(part_ks.source) + " -o partition.json", - "pred reduce partition.json --to " + target-spec(part_ks) + " -o bundle.json", + "pred reduce partition.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate partition.json --config " + part_ks_sol.source_config.map(str).join(","), ) @@ -12474,7 +12474,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example " + problem-spec(part_ss.source) + " -o partition.json", - "pred reduce partition.json --to " + target-spec(part_ss) + " -o bundle.json", + "pred reduce partition.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate partition.json --config " + part_ss_sol.source_config.map(str).join(","), ) @@ -12516,7 +12516,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example " + problem-spec(part_ifwm.source) + " -o partition.json", - "pred reduce partition.json --to " + target-spec(part_ifwm) + " -o bundle.json", + "pred reduce partition.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate partition.json --config " + part_ifwm_sol.source_config.map(str).join(","), ) @@ -12559,7 +12559,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example Knapsack -o knapsack.json", - "pred reduce knapsack.json --to " + target-spec(ks_qubo) + " -o bundle.json", + "pred reduce knapsack.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate knapsack.json --config " + ks_qubo_sol.source_config.map(str).join(","), ) @@ -12600,7 +12600,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example MinimumDiscretePlanarInverseKinematics -o ik.json", - "pred reduce ik.json --to " + target-spec(mdpik_qubo) + " -o bundle.json", + "pred reduce ik.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ik.json --config " + mdpik_qubo_sol.source_config.map(str).join(","), ) @@ -12653,7 +12653,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example MinimumMultiwayCut -o minimummultiwaycut.json", - "pred reduce minimummultiwaycut.json --to " + target-spec(mwc_qubo) + " -o bundle.json", + "pred reduce minimummultiwaycut.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate minimummultiwaycut.json --config " + mwc_qubo_sol.source_config.map(str).join(","), ) @@ -12712,7 +12712,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example QUBO -o qubo.json", - "pred reduce qubo.json --to " + target-spec(qubo_ilp) + " -o bundle.json", + "pred reduce qubo.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate qubo.json --config " + qubo_ilp_sol.source_config.map(str).join(","), ) @@ -12754,7 +12754,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example CircuitSAT -o circuitsat.json", - "pred reduce circuitsat.json --to " + target-spec(cs_ilp) + " -o bundle.json", + "pred reduce circuitsat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate circuitsat.json --config " + cs_ilp_sol.source_config.map(str).join(","), ) @@ -12805,7 +12805,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example SAT -o sat.json", - "pred reduce sat.json --to " + target-spec(sat_mis) + " -o bundle.json", + "pred reduce sat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate sat.json --config " + sat_mis_sol.source_config.map(str).join(","), ) @@ -12835,7 +12835,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example SAT -o sat.json", - "pred reduce sat.json --to " + target-spec(sat_kc) + " -o bundle.json", + "pred reduce sat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate sat.json --config " + sat_kc_sol.source_config.map(str).join(","), ) @@ -12863,7 +12863,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example SAT -o sat.json", - "pred reduce sat.json --to " + target-spec(sat_ds) + " -o bundle.json", + "pred reduce sat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate sat.json --config " + sat_ds_sol.source_config.map(str).join(","), ) @@ -12889,7 +12889,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example " + problem-spec(sat_ifha.source) + " -o sat.json", - "pred reduce sat.json --to " + target-spec(sat_ifha) + " -o bundle.json", + "pred reduce sat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate sat.json --config " + sat_ifha_sol.source_config.map(str).join(","), ) @@ -12945,7 +12945,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example SAT -o sat.json", - "pred reduce sat.json --to " + target-spec(sat_ksat) + " -o bundle.json", + "pred reduce sat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate sat.json --config " + sat_ksat_sol.source_config.map(str).join(","), ) @@ -12976,7 +12976,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example " + problem-spec(sat_max2sat.source) + " -o sat.json", - "pred reduce sat.json --to " + target-spec(sat_max2sat) + " -o bundle.json", + "pred reduce sat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate sat.json --config " + sat_max2sat_sol.source_config.map(str).join(","), ) @@ -13041,7 +13041,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example SAT -o sat.json", - "pred reduce sat.json --to " + target-spec(sat_cs) + " -o bundle.json", + "pred reduce sat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate sat.json --config " + sat_cs_sol.source_config.map(str).join(","), ) @@ -13074,7 +13074,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example " + problem-spec(cs_sat.source) + " -o circuitsat.json", - "pred reduce circuitsat.json --to " + target-spec(cs_sat) + " -o bundle.json", + "pred reduce circuitsat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate circuitsat.json --config " + cs_sat_sol.source_config.map(str).join(","), ) @@ -13114,7 +13114,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example CircuitSAT -o circuitsat.json", - "pred reduce circuitsat.json --to " + target-spec(cs_sg) + " -o bundle.json", + "pred reduce circuitsat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate circuitsat.json --config " + cs_sg_sol.source_config.map(str).join(","), ) @@ -13166,7 +13166,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example Factoring -o factoring.json", - "pred reduce factoring.json --to " + target-spec(fact_cs) + " -o bundle.json", + "pred reduce factoring.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate factoring.json --config " + fact_cs_sol.source_config.map(str).join(","), ) @@ -13198,7 +13198,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example MaxCut -o maxcut.json", - "pred reduce maxcut.json --to " + target-spec(mc_sg) + " -o bundle.json", + "pred reduce maxcut.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate maxcut.json --config " + mc_sg_sol.source_config.map(str).join(","), ) @@ -13224,7 +13224,7 @@ where $P$ is a penalty weight large enough that any constraint violation costs m extra: [ #pred-commands( "pred create --example SpinGlass -o spinglass.json", - "pred reduce spinglass.json --to " + target-spec(sg_mc) + " -o bundle.json", + "pred reduce spinglass.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate spinglass.json --config " + sg_mc_sol.source_config.map(str).join(","), ) @@ -13380,7 +13380,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(mfdts_ilp.source) + " -o mfdts.json", - "pred reduce mfdts.json --to " + target-spec(mfdts_ilp) + " -o bundle.json", + "pred reduce mfdts.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mfdts.json --config " + mfdts_ilp_sol.source_config.map(str).join(","), ) @@ -13455,7 +13455,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example MinimumFeedbackVertexSet -o fvs.json", - "pred reduce fvs.json --to " + target-spec(fvs_cg) + " -o bundle.json", + "pred reduce fvs.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate fvs.json --config " + fvs_cg_sol.source_config.map(str).join(","), ) @@ -13506,7 +13506,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(mckp_ilp.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(mckp_ilp) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + mckp_ilp_sol.source_config.map(str).join(","), ) @@ -13540,7 +13540,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(mces_ilp.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(mces_ilp) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + mces_ilp_sol.source_config.map(str).join(","), ) @@ -13578,7 +13578,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(cmo_ilp.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(cmo_ilp) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + cmo_ilp_sol.source_config.map(str).join(","), ) @@ -13618,7 +13618,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(mewkc_ilp.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(mewkc_ilp) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + mewkc_ilp_sol.source_config.map(str).join(","), ) @@ -13654,7 +13654,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example Knapsack -o knapsack.json", - "pred reduce knapsack.json --to " + target-spec(ks_ilp) + " -o bundle.json", + "pred reduce knapsack.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate knapsack.json --config " + ks_ilp_sol.source_config.map(str).join(","), ) @@ -13704,7 +13704,7 @@ The following reductions to Integer Linear Programming are straightforward formu [ #pred-commands( "pred create --example " + problem-spec(ik_ilp.source) + " -o integer-knapsack.json", - "pred reduce integer-knapsack.json --to " + target-spec(ik_ilp) + " -o bundle.json", + "pred reduce integer-knapsack.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate integer-knapsack.json --config " + ik_ilp_sol.source_config.map(str).join(","), ) @@ -13759,7 +13759,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example MaximumClique -o maximumclique.json", - "pred reduce maximumclique.json --to " + target-spec(clique_mis) + " -o bundle.json", + "pred reduce maximumclique.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate maximumclique.json --config " + clique_mis_sol.source_config.map(str).join(","), ) @@ -13836,7 +13836,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(ola_seqmwct.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(ola_seqmwct) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + ola_seqmwct_sol.source_config.map(str).join(","), ) @@ -13872,7 +13872,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(dola_c1ma.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(dola_c1ma) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + dola_c1ma_sol.source_config.map(str).join(","), ) @@ -13937,7 +13937,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(hc_tsp.source) + " -o hc.json", - "pred reduce hc.json --to " + target-spec(hc_tsp) + " -o bundle.json", + "pred reduce hc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hc.json --config " + hc_tsp_sol.source_config.map(str).join(","), ) @@ -13968,7 +13968,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example TSP -o tsp.json", - "pred reduce tsp.json --to " + target-spec(tsp_ilp) + " -o bundle.json", + "pred reduce tsp.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate tsp.json --config " + tsp_ilp_sol.source_config.map(str).join(","), ) @@ -14014,7 +14014,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example LongestPath -o longest-path.json", - "pred reduce longest-path.json --to " + target-spec(lp_ilp) + " -o bundle.json", + "pred reduce longest-path.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate longest-path.json --config " + lp_ilp_sol.source_config.map(str).join(","), ) @@ -14059,7 +14059,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example TSP -o tsp.json", - "pred reduce tsp.json --to " + target-spec(tsp_qubo) + " -o bundle.json", + "pred reduce tsp.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate tsp.json --config " + tsp_qubo_sol.source_config.map(str).join(","), ) @@ -14096,7 +14096,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example LCS -o lcs.json", - "pred reduce lcs.json --to " + target-spec(lcs_mis) + " -o bundle.json", + "pred reduce lcs.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate lcs.json --config " + lcs_mis_sol.source_config.map(str).join(","), ) @@ -14131,7 +14131,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(cs_ilp_str.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(cs_ilp_str) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + cs_ilp_str_sol.source_config.map(str).join(","), ) @@ -14174,7 +14174,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(css_ilp.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(css_ilp) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + css_ilp_sol.source_config.map(str).join(","), ) @@ -14276,7 +14276,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example SteinerTree -o steinertree.json", - "pred reduce steinertree.json --to " + target-spec(st_ilp) + " -o bundle.json", + "pred reduce steinertree.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate steinertree.json --config " + st_ilp_sol.source_config.map(str).join(","), ) @@ -14331,7 +14331,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example 'MVC {weight: One}' -o mvc.json", - "pred reduce mvc.json --to " + target-spec(mvc_hs) + " -o bundle.json", + "pred reduce mvc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mvc.json --config " + mvc_hs_sol.source_config.map(str).join(","), ) @@ -14426,7 +14426,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(mono_ilp.source) + " -o monochromatic-triangle.json", - "pred reduce monochromatic-triangle.json --to " + target-spec(mono_ilp) + " -o bundle.json", + "pred reduce monochromatic-triangle.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate monochromatic-triangle.json --config " + mono_ilp_sol.source_config.map(str).join(","), ) @@ -14463,7 +14463,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(ss_bt.source) + " -o set-splitting.json", - "pred reduce set-splitting.json --to " + target-spec(ss_bt) + " -o bundle.json", + "pred reduce set-splitting.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate set-splitting.json --config " + ss_bt_sol.source_config.map(str).join(","), ) @@ -14538,7 +14538,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(kc_bcbs.source) + " -o kclique.json", - "pred reduce kclique.json --to " + target-spec(kc_bcbs) + " -o bundle.json", + "pred reduce kclique.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate kclique.json --config " + kc_bcbs_sol.source_config.map(str).join(","), ) @@ -14603,7 +14603,7 @@ The following reductions to Integer Linear Programming are straightforward formu [ #pred-commands( "pred create --example " + problem-spec(mmm_ach.source) + " -o mmm.json", - "pred reduce mmm.json --to " + target-spec(mmm_ach) + " -o bundle.json", + "pred reduce mmm.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mmm.json --config " + mmm_ach_sol.source_config.map(str).join(","), ) @@ -14664,7 +14664,7 @@ The following reductions to Integer Linear Programming are straightforward formu [ #pred-commands( "pred create --example " + problem-spec(mmm_mmd.source) + " -o mmm.json", - "pred reduce mmm.json --to " + target-spec(mmm_mmd) + " -o bundle.json", + "pred reduce mmm.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mmm.json --config " + s-cfg.map(str).join(","), ) @@ -14946,7 +14946,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example PartitionIntoPathsOfLength2 -o ppl2.json", - "pred reduce ppl2.json --to " + target-spec(ppl2_bcsf) + " -o bundle.json", + "pred reduce ppl2.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ppl2.json --config " + ppl2_bcsf_sol.source_config.map(str).join(","), ) @@ -15592,7 +15592,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(hcd_ilp.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(hcd_ilp) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + hcd_ilp_sol.source_config.map(str).join(","), ) @@ -15626,7 +15626,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(ep_ilp.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(ep_ilp) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + ep_ilp_sol.source_config.map(str).join(","), ) @@ -15688,7 +15688,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(hc_lc.source) + " -o hc.json", - "pred reduce hc.json --to " + target-spec(hc_lc) + " -o bundle.json", + "pred reduce hc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hc.json --config " + hc_lc_sol.source_config.map(str).join(","), ) @@ -16283,7 +16283,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(ps_qubo.source) + " -o paintshop.json", - "pred reduce paintshop.json --to " + target-spec(ps_qubo) + " -o bundle.json", + "pred reduce paintshop.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate paintshop.json --config " + ps_qubo_sol.source_config.map(str).join(","), ) @@ -16363,7 +16363,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(rta_rtsa.source) + " -o rta.json", - "pred reduce rta.json --to " + target-spec(rta_rtsa) + " -o bundle.json", + "pred reduce rta.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate rta.json --config " + rta_rtsa_sol.source_config.map(str).join(","), ) @@ -16486,7 +16486,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(mcmf_mcc.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(mcmf_mcc) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + mcmf_mcc_sol.source_config.map(str).join(","), ) @@ -16555,7 +16555,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example " + problem-spec(mfas_mlr.source) + " -o mfas.json", - "pred reduce mfas.json --to " + target-spec(mfas_mlr) + " -o bundle.json", + "pred reduce mfas.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mfas.json --config " + mfas_mlr_sol.source_config.map(str).join(","), ) @@ -16607,7 +16607,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example MaximumLikelihoodRanking -o mlr.json", - "pred reduce mlr.json --to " + target-spec(mlr_ilp) + " -o bundle.json", + "pred reduce mlr.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mlr.json --config " + mlr_ilp_sol.source_config.map(str).join(","), ) @@ -16651,7 +16651,7 @@ The following reductions to Integer Linear Programming are straightforward formu extra: [ #pred-commands( "pred create --example OptimumCommunicationSpanningTree -o ocst.json", - "pred reduce ocst.json --to " + target-spec(ocst_ilp) + " -o bundle.json", + "pred reduce ocst.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ocst.json --config " + ocst_ilp_sol.source_config.map(str).join(","), ) @@ -16762,10 +16762,10 @@ See #link("https://github.com/CodingThrust/problem-reductions/blob/main/examples == Variant Cast Reductions -Problems parameterized by graph type, weight type, or clause-width ($k$) admit identity reductions between specialised and general variants. Each cast preserves the problem structure exactly (same number of vertices/variables, same constraints), converting only the type parameter to a more general one. These are registered as self-edges in the reduction graph with identity overhead. +Problems parameterized by graph type, weight type, or clause-width ($k$) admit identity reductions between specialised and general variants. Each cast preserves the problem structure exactly (same number of vertices/variables, same constraints), converting only the type parameter to a more general one. These are registered as self-edges in the reduction graph with exact identity size maps. #reduction-rule("MaximumIndependentSet", "MaximumIndependentSet")[ - The graph hierarchy $"KingsSubgraph" subset "UnitDiskGraph" subset "SimpleGraph"$ and weight hierarchy $"One" subset ZZ subset RR$ induce identity-overhead casts between MIS variants. Graph casts discard geometric information (grid coordinates $arrow.r$ Euclidean coordinates $arrow.r$ adjacency list); weight casts embed unit weights into integers ($1 arrow.r 1_ZZ$) or integers into floats ($w arrow.r w_RR$). All edges and weights are preserved verbatim. + The graph hierarchy $"KingsSubgraph" subset "UnitDiskGraph" subset "SimpleGraph"$ and weight hierarchy $"One" subset ZZ subset RR$ induce exact identity size maps between MIS variants. Graph casts discard geometric information (grid coordinates $arrow.r$ Euclidean coordinates $arrow.r$ adjacency list); weight casts embed unit weights into integers ($1 arrow.r 1_ZZ$) or integers into floats ($w arrow.r w_RR$). All edges and weights are preserved verbatim. ][ _Construction._ Given $"MIS"(G, bold(w))$ with graph type $G_"sub"$ and weight type $W_"sub"$, construct $"MIS"(G', bold(w)')$ where $G' = "cast"(G_"sub")$ lifts the graph to its parent type and $bold(w)' = "cast"(bold(w))$ lifts each weight. The `CastToParent` trait defines the concrete maps: - _KingsSubgraph $arrow.r$ UnitDiskGraph:_ integer grid positions $(i, j)$ map to float coordinates with radius $r = 1.5$. @@ -16854,7 +16854,7 @@ Problems parameterized by graph type, weight type, or clause-width ($k$) admit i == Resource Estimation from Examples -The following table shows concrete variable overhead for example instances, taken directly from the canonical fixture examples. +The following table shows concrete target-variable counts for example instances, taken directly from the canonical fixture examples. #let example-files = ( (source: "MaximumIndependentSet", target: "MinimumVertexCover"), @@ -17094,7 +17094,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(hc_hp.source) + " -o hc.json", - "pred reduce hc.json --to " + target-spec(hc_hp) + " -o bundle.json", + "pred reduce hc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hc.json --config " + hc_hp_sol.source_config.map(str).join(","), ) @@ -17125,7 +17125,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(kc_si.source) + " -o kclique.json", - "pred reduce kclique.json --to " + target-spec(kc_si) + " -o bundle.json", + "pred reduce kclique.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate kclique.json --config " + kc_si_sol.source_config.map(str).join(","), ) @@ -17189,7 +17189,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(part_mps.source) + " -o partition.json", - "pred reduce partition.json --to " + target-spec(part_mps) + " -o bundle.json", + "pred reduce partition.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate partition.json --config " + part_mps_sol.source_config.map(str).join(","), ) @@ -17229,7 +17229,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(part_sosp.source) + " -o partition.json", - "pred reduce partition.json --to " + target-spec(part_sosp) + " -o bundle.json", + "pred reduce partition.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate partition.json --config " + part_sosp_sol.source_config.map(str).join(","), ) @@ -17262,7 +17262,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(hc_btsp.source) + " -o hc.json", - "pred reduce hc.json --to " + target-spec(hc_btsp) + " -o bundle.json", + "pred reduce hc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hc.json --config " + hc_btsp_sol.source_config.map(str).join(","), ) @@ -17293,7 +17293,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(kc_cbq.source) + " -o kclique.json", - "pred reduce kclique.json --to " + target-spec(kc_cbq) + " -o bundle.json", + "pred reduce kclique.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate kclique.json --config " + kc_cbq_sol.source_config.map(str).join(","), ) @@ -17342,7 +17342,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(x3c_ss.source) + " -o x3c.json", - "pred reduce x3c.json --to " + target-spec(x3c_ss) + " -o bundle.json", + "pred reduce x3c.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate x3c.json --config " + x3c_ss_sol.source_config.map(str).join(","), ) @@ -17381,7 +17381,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_dmvc.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_dmvc) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_dmvc_sol.source_config.map(str).join(","), ) @@ -17417,7 +17417,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(dmvc_hc.source) + " -o dmvc.json", - "pred reduce dmvc.json --to " + target-spec(dmvc_hc) + " -o bundle.json", + "pred reduce dmvc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate dmvc.json --config " + dmvc_hc_sol.source_config.map(str).join(","), ) @@ -17448,7 +17448,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_mvc.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_mvc) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_mvc_sol.source_config.map(str).join(","), ) @@ -17489,7 +17489,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_mono.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_mono) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_mono_sol.source_config.map(str).join(","), ) @@ -17520,7 +17520,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_1in3.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_1in3) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_1in3_sol.source_config.map(str).join(","), ) @@ -17572,7 +17572,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_d2cif.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_d2cif) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_d2cif_sol.source_config.map(str).join(","), ) @@ -17617,7 +17617,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_rs.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_rs) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_rs_sol.source_config.map(str).join(","), ) @@ -17678,7 +17678,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(mvc_mfas.source) + " -o mvc.json", - "pred reduce mvc.json --to " + target-spec(mvc_mfas) + " -o bundle.json", + "pred reduce mvc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mvc.json --config " + mvc_mfas_sol.source_config.map(str).join(","), ) @@ -17722,7 +17722,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_kc.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_kc) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_kc_sol.source_config.map(str).join(","), ) @@ -17761,7 +17761,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_co.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_co) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_co_sol.source_config.map(str).join(","), ) @@ -17800,7 +17800,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_ps.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_ps) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_ps_sol.source_config.map(str).join(","), ) @@ -17853,7 +17853,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_td.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_td) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_td_sol.source_config.map(str).join(","), ) @@ -17896,7 +17896,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_ap.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_ap) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_ap_sol.source_config.map(str).join(","), ) @@ -17940,7 +17940,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(hc_bicon.source) + " -o hc.json", - "pred reduce hc.json --to " + target-spec(hc_bicon) + " -o bundle.json", + "pred reduce hc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hc.json --config " + hc_bicon_sol.source_config.map(str).join(","), ) @@ -17986,7 +17986,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(hc_sca.source) + " -o hc.json", - "pred reduce hc.json --to " + target-spec(hc_sca) + " -o bundle.json", + "pred reduce hc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hc.json --config " + hc_sca_sol.source_config.map(str).join(","), ) @@ -18021,7 +18021,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(hc_sc.source) + " -o hc.json", - "pred reduce hc.json --to " + target-spec(hc_sc) + " -o bundle.json", + "pred reduce hc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hc.json --config " + hc_sc_sol.source_config.map(str).join(","), ) @@ -18053,7 +18053,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(hc_rp.source) + " -o hc.json", - "pred reduce hc.json --to " + target-spec(hc_rp) + " -o bundle.json", + "pred reduce hc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hc.json --config " + hc_rp_sol.source_config.map(str).join(","), ) @@ -18084,7 +18084,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(mis_ifb.source) + " -o mis.json", - "pred reduce mis.json --to " + target-spec(mis_ifb) + " -o bundle.json", + "pred reduce mis.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate mis.json --config " + mis_ifb_sol.source_config.map(str).join(","), ) @@ -18136,7 +18136,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(hc_qa.source) + " -o hc.json", - "pred reduce hc.json --to " + target-spec(hc_qa) + " -o bundle.json", + "pred reduce hc.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hc.json --config " + hc_qa_sol.source_config.map(str).join(","), ) @@ -18179,7 +18179,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(part_bp.source) + " -o partition.json", - "pred reduce partition.json --to " + target-spec(part_bp) + " -o bundle.json", + "pred reduce partition.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate partition.json --config " + part_bp_sol.source_config.map(str).join(","), ) @@ -18210,7 +18210,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(x3c_msp.source) + " -o x3c.json", - "pred reduce x3c.json --to " + target-spec(x3c_msp) + " -o bundle.json", + "pred reduce x3c.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate x3c.json --config " + x3c_msp_sol.source_config.map(str).join(","), ) @@ -18250,7 +18250,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(x3c_mfdts.source) + " -o x3c.json", - "pred reduce x3c.json --to " + target-spec(x3c_mfdts) + " -o bundle.json", + "pred reduce x3c.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate x3c.json --config " + x3c_mfdts_sol.source_config.map(str).join(","), ) @@ -18285,7 +18285,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(x3c_mas.source) + " -o x3c.json", - "pred reduce x3c.json --to " + target-spec(x3c_mas) + " -o bundle.json", + "pred reduce x3c.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate x3c.json --config " + x3c_mas_sol.source_config.map(str).join(","), ) @@ -18334,7 +18334,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ss_part.source) + " -o subsetsum.json", - "pred reduce subsetsum.json --to " + target-spec(ss_part) + " -o bundle.json", + "pred reduce subsetsum.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate subsetsum.json --config " + ss_part_sol.source_config.map(str).join(","), ) @@ -18435,7 +18435,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(sat_nt.source) + " -o sat.json", - "pred reduce sat.json --to " + target-spec(sat_nt) + " -o bundle.json", + "pred reduce sat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate sat.json --config " + sat_nt_sol.source_config.map(str).join(","), ) @@ -18467,7 +18467,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(kc_pic.source) + " -o kcoloring.json", - "pred reduce kcoloring.json --to " + target-spec(kc_pic) + " -o bundle.json", + "pred reduce kcoloring.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate kcoloring.json --config " + kc_pic_sol.source_config.map(str).join(","), ) @@ -18557,7 +18557,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(clustering_ilp.source) + " -o clustering.json", - "pred reduce clustering.json --to " + target-spec(clustering_ilp) + " -o bundle.json", + "pred reduce clustering.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate clustering.json --config " + clustering_ilp_sol.source_config.map(str).join(","), ) @@ -18602,7 +18602,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(pic_mcbc.source) + " -o partition-into-cliques.json", - "pred reduce partition-into-cliques.json --to " + target-spec(pic_mcbc) + " -o bundle.json", + "pred reduce partition-into-cliques.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate partition-into-cliques.json --config " + pic_mcbc_sol.source_config.map(str).join(","), ) @@ -18641,7 +18641,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(mcbc_migb.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(mcbc_migb) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + mcbc_migb_sol.source_config.map(str).join(","), ) @@ -18668,7 +18668,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_ker.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_ker) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_ker_sol.source_config.map(str).join(","), ) @@ -18713,7 +18713,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(hp_dcst.source) + " -o hampath.json", - "pred reduce hampath.json --to " + target-spec(hp_dcst) + " -o bundle.json", + "pred reduce hampath.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hampath.json --config " + hp_dcst_sol.source_config.map(str).join(","), ) @@ -18745,7 +18745,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(nae_ss.source) + " -o naesat.json", - "pred reduce naesat.json --to " + target-spec(nae_ss) + " -o bundle.json", + "pred reduce naesat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate naesat.json --config " + nae_ss_sol.source_config.map(str).join(","), ) @@ -18784,7 +18784,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(nae_ppm.source) + " -o naesat.json", - "pred reduce naesat.json --to " + target-spec(nae_ppm) + " -o bundle.json", + "pred reduce naesat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate naesat.json --config " + nae_ppm_sol.source_config.map(str).join(","), ) @@ -18828,7 +18828,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(x3c_sp.source) + " -o x3c.json", - "pred reduce x3c.json --to " + target-spec(x3c_sp) + " -o bundle.json", + "pred reduce x3c.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate x3c.json --config " + x3c_sp_sol.source_config.map(str).join(","), ) @@ -18869,7 +18869,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(x3c_bdst.source) + " -o x3c.json", - "pred reduce x3c.json --to " + target-spec(x3c_bdst) + " -o bundle.json", + "pred reduce x3c.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate x3c.json --config " + x3c_bdst_sol.source_config.map(str).join(","), ) @@ -18915,7 +18915,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ss_iem.source) + " -o subsetsum.json", - "pred reduce subsetsum.json --to " + target-spec(ss_iem) + " -o bundle.json", + "pred reduce subsetsum.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate subsetsum.json --config " + ss_iem_sol.source_config.map(str).join(","), ) @@ -18956,7 +18956,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(ksat_si.source) + " -o ksat.json", - "pred reduce ksat.json --to " + target-spec(ksat_si) + " -o bundle.json", + "pred reduce ksat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate ksat.json --config " + ksat_si_sol.source_config.map(str).join(","), ) @@ -18995,7 +18995,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(n3dm_nmts.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(n3dm_nmts) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + n3dm_nmts_sol.source_config.map(str).join(","), ) @@ -19020,7 +19020,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(part_stw.source) + " -o partition.json", - "pred reduce partition.json --to " + target-spec(part_stw) + " -o bundle.json", + "pred reduce partition.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate partition.json --config " + part_stw_sol.source_config.map(str).join(","), ) @@ -19074,7 +19074,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(part_oss.source) + " -o partition.json", - "pred reduce partition.json --to " + target-spec(part_oss) + " -o bundle.json", + "pred reduce partition.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate partition.json --config " + part_oss_sol.source_config.map(str).join(","), ) @@ -19125,7 +19125,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(nae_mc.source) + " -o naesat.json", - "pred reduce naesat.json --to " + target-spec(nae_mc) + " -o bundle.json", + "pred reduce naesat.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate naesat.json --config " + nae_mc_sol.source_config.map(str).join(","), ) @@ -19171,7 +19171,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(tdm_tmi.source) + " -o source.json", - "pred reduce source.json --to " + target-spec(tdm_tmi) + " -o bundle.json", + "pred reduce source.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate source.json --config " + tdm_tmi_sol.source_config.map(str).join(","), ) @@ -19199,7 +19199,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(tdm_tp.source) + " -o three-dimensional-matching.json", - "pred reduce three-dimensional-matching.json --to " + target-spec(tdm_tp) + " -o bundle.json", + "pred reduce three-dimensional-matching.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate three-dimensional-matching.json --config " + tdm_tp_sol.source_config.map(str).join(","), ) @@ -19269,7 +19269,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(tdm_ilp.source) + " -o three-dimensional-matching.json", - "pred reduce three-dimensional-matching.json --to " + target-spec(tdm_ilp) + " -o bundle.json", + "pred reduce three-dimensional-matching.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate three-dimensional-matching.json --config " + tdm_ilp_sol.source_config.map(str).join(","), ) @@ -19314,7 +19314,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(tdm_mwd.source) + " -o three-dimensional-matching.json", - "pred reduce three-dimensional-matching.json --to " + target-spec(tdm_mwd) + " -o bundle.json", + "pred reduce three-dimensional-matching.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate three-dimensional-matching.json --config " + tdm_mwd_sol.source_config.map(str).join(","), ) @@ -19361,7 +19361,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(tp_rcs.source) + " -o threepartition.json", - "pred reduce threepartition.json --to " + target-spec(tp_rcs) + " -o bundle.json", + "pred reduce threepartition.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate threepartition.json --config " + tp_rcs_sol.source_config.map(str).join(","), ) @@ -19396,7 +19396,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(tp_srd.source) + " -o tp.json", - "pred reduce tp.json --to " + target-spec(tp_srd) + " -o bundle.json", + "pred reduce tp.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate tp.json --config " + tp_srd_sol.source_config.map(str).join(","), ) @@ -19435,7 +19435,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(mc_mcbs.source) + " -o maxcut.json", - "pred reduce maxcut.json --to " + target-spec(mc_mcbs) + " -o bundle.json", + "pred reduce maxcut.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate maxcut.json --config " + mc_mcbs_sol.source_config.map(str).join(","), ) @@ -19474,7 +19474,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(mc_mmc.source) + " -o maxcut.json", - "pred reduce maxcut.json --to " + target-spec(mc_mmc) + " -o bundle.json", + "pred reduce maxcut.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate maxcut.json --config " + mc_mmc_sol.source_config.map(str).join(","), ) @@ -19512,7 +19512,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(hp_ist.source) + " -o hampath.json", - "pred reduce hampath.json --to " + target-spec(hp_ist) + " -o bundle.json", + "pred reduce hampath.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hampath.json --config " + hp_ist_sol.source_config.map(str).join(","), ) @@ -19544,7 +19544,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(x3c_gf2.source) + " -o x3c.json", - "pred reduce x3c.json --to " + target-spec(x3c_gf2) + " -o bundle.json", + "pred reduce x3c.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate x3c.json --config " + x3c_gf2_sol.source_config.map(str).join(","), ) @@ -19587,7 +19587,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(part_pp.source) + " -o partition.json", - "pred reduce partition.json --to " + target-spec(part_pp) + " -o bundle.json", + "pred reduce partition.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate partition.json --config " + part_pp_sol.source_config.map(str).join(","), ) @@ -19639,7 +19639,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(hpbtv_lp.source) + " -o hampath2v.json", - "pred reduce hampath2v.json --to " + target-spec(hpbtv_lp) + " -o bundle.json", + "pred reduce hampath2v.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate hampath2v.json --config " + hpbtv_lp_sol.source_config.map(str).join(","), ) @@ -19671,7 +19671,7 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example " + problem-spec(gp_mc.source) + " -o graphpart.json", - "pred reduce graphpart.json --to " + target-spec(gp_mc) + " -o bundle.json", + "pred reduce graphpart.json --via route.json -o bundle.json", "pred solve bundle.json", "pred evaluate graphpart.json --config " + gp_mc_sol.source_config.map(str).join(","), ) @@ -19729,10 +19729,10 @@ The following table shows concrete variable overhead for example instances, take extra: [ #pred-commands( "pred create --example PrizeCollectingSteinerForest -o pcsf.json", - "pred reduce pcsf.json --to " + target-spec(pcsf_st) + " -o bundle.json", + "pred reduce pcsf.json --via route.json -o bundle.json", "pred solve bundle.json", ) - The canonical PCSF source has $beta = #pcsf_st.source.instance.beta$, $omega = #pcsf_st.source.instance.omega$, and prizes $p = (#pcsf_st_prizes.at(0), #pcsf_st_prizes.at(1), #pcsf_st_prizes.at(2))$. The target SteinerTree has $|V_H| = n + k + 1 = #(pcsf_st_n + pcsf_st_k + 1)$ vertices, $|E_H| = m + n + 2 k = #(pcsf_st_m + pcsf_st_n + 2 * pcsf_st_k)$ edges, and $|T_H| = k + 1 = #(pcsf_st_k + 1)$ terminals, matching the registered overhead formulas. + The canonical PCSF source has $beta = #pcsf_st.source.instance.beta$, $omega = #pcsf_st.source.instance.omega$, and prizes $p = (#pcsf_st_prizes.at(0), #pcsf_st_prizes.at(1), #pcsf_st_prizes.at(2))$. The target SteinerTree has $|V_H| = n + k + 1 = #(pcsf_st_n + pcsf_st_k + 1)$ vertices, $|E_H| = m + n + 2 k = #(pcsf_st_m + pcsf_st_n + 2 * pcsf_st_k)$ edges, and $|T_H| = k + 1 = #(pcsf_st_k + 1)$ terminals, matching the registered exact size formulas. ], )[ Bienstock, Goemans, Simchi-Levi, Williamson @BienstockGoemansSimchiLeviWilliamson1993 introduced the prize/penalty framework for prize-collecting network design; Tuncbag and coauthors @TuncbagEtAl2013PCSF @TuncbagEtAl2012RECOMB used the same artificial-root idea to translate PCSF into a rooted prize-collecting Steiner tree on biological networks. The combined construction recorded here adds a per-vertex auxiliary-terminal gadget that compiles the remaining omitted-prize term `beta * p(v)` into ordinary Steiner-tree edge costs, so the target is a plain (unweighted-prize) Steiner Tree instance. diff --git a/docs/src/cli.md b/docs/src/cli.md index e94f4456e..5e94294c7 100644 --- a/docs/src/cli.md +++ b/docs/src/cli.md @@ -33,15 +33,7 @@ cargo run -p problemreductions-cli --bin pred -- --version ### ILP Backend -The default ILP backend is HiGHS. To use a different backend: - -```bash -cargo install problemreductions-cli --features coin-cbc -cargo install problemreductions-cli --features scip -cargo install problemreductions-cli --no-default-features --features clarabel -``` - -Available backends: `highs` (default), `coin-cbc`, `clarabel`, `scip`, `lpsolve`, `microlp`. +ILP problems are solved with the bundled HiGHS backend. ## Quick Start @@ -88,14 +80,14 @@ pred solve lbdp.json --solver brute-force # Evaluate a specific configuration (shows the aggregate value, e.g. Max(2) or Min(None)) pred evaluate problem.json --config 1,0,1,0 -# Reduce to another problem type and solve via brute-force -pred reduce problem.json --to QUBO -o reduced.json +# Reduce along an explicitly chosen route and solve via brute-force +pred reduce problem.json --via route.json -o reduced.json pred solve reduced.json --solver brute-force # Pipe commands together (use - to read from stdin) pred create MIS --graph 0-1,1-2,2-3 | pred solve - # when an ILP reduction path exists pred create StringToStringCorrection --source-string "0,1,2,3,1,0" --target-string "0,1,3,2,1" --bound 2 | pred solve - --solver brute-force -pred create MIS --graph 0-1,1-2,2-3 | pred reduce - --to QUBO | pred solve - +pred create MIS --graph 0-1,1-2,2-3 | pred reduce - --via route.json | pred solve - ``` > **Note:** When you provide `--weights` with non-unit values (e.g., `3,1,2,1`), the variant is @@ -144,9 +136,9 @@ Explore which problems the given problem can reduce to, starting **from** it: {{#include generated/pred-from-qubo.txt}} ``` -### `pred path` — Find a reduction path +### `pred path` — Find reduction paths -Find the cheapest chain of reductions between two problems: +Enumerate paths between two problems: ```text {{#include generated/pred-path-mis-qubo.txt}} @@ -158,25 +150,21 @@ Multi-step paths are discovered automatically: {{#include generated/pred-path-factoring-spinglass.txt}} ``` -Show all paths or save for later use with `pred reduce --via`: +Inspect reduction paths or save the path set for later route selection: ```bash -pred path MIS QUBO --all # all paths (up to 20) -pred path MIS QUBO --all --max-paths 50 # increase limit -pred path MIS QUBO -o path.json # save path for `pred reduce --via` -pred path MIS QUBO --all -o paths/ # save all paths to a folder +pred path MIS QUBO # paths (up to 20) +pred path MIS QUBO --max-paths 50 # increase the cap +pred path MIS MaximumClique mis.json # execute paths on a complete instance +pred path MIS QUBO -o paths.json # save the path set ``` -When using `--all`, the output is capped at `--max-paths` (default: 20). If more paths exist, the output indicates truncation. - -Use `--cost` to change the optimization strategy: - -```bash -pred path MIS QUBO --cost minimize-steps # default -pred path MIS QUBO --cost minimize:num_variables # minimize a size field -``` - -Use `pred show ` to see which size fields are available. +Without an instance file, each route explains how problem size changes. With a +problem JSON file, every returned path is executed on the complete source instance +and the actual size of each constructed intermediate is reported. Discovery never +ranks or discards routes based on size. Output is capped by `--max-paths` (default: 20); +extract one route from the path-set envelope before passing it to +`pred reduce --via`. ### `pred export-graph` — Export the reduction graph @@ -320,13 +308,7 @@ pred create MIS --graph 0-1,1-2 | pred inspect - ### `pred reduce` — Reduce a problem -Reduce a problem to a target type. Outputs a reduction bundle containing source, target, and path: - -```bash -pred reduce problem.json --to QUBO -o reduced.json -``` - -Use a specific reduction path (from `pred path -o`). The target is inferred from the path file, so `--to` is not needed: +Reduce a problem along a specific route. The target is inferred from the route file: ```bash pred reduce problem.json --via path.json -o reduced.json @@ -335,7 +317,7 @@ pred reduce problem.json --via path.json -o reduced.json Stdin is supported with `-`: ```bash -pred create MIS --graph 0-1,1-2,2-3 | pred reduce - --to QUBO +pred create MIS --graph 0-1,1-2,2-3 | pred reduce - --via route.json ``` The bundle contains everything needed to map solutions back: @@ -429,7 +411,7 @@ This is useful for scripting and piping: ```bash pred list --json | jq '.variants[].name' -pred path MIS QUBO --json | jq '.path' +pred path MIS QUBO --json | jq '.paths[] | {overall_size, path}' ``` ## Problem Name Aliases diff --git a/docs/src/design.md b/docs/src/design.md index 7f709edfc..6b2ad2f7e 100644 --- a/docs/src/design.md +++ b/docs/src/design.md @@ -2,6 +2,9 @@ This guide covers the library internals for contributors. +See [Numeric types and arithmetic](#numeric-types-and-arithmetic) before +choosing numeric fields or implementing arithmetic in a model or reduction. + ## Module Architecture @@ -39,12 +42,101 @@ trait Problem: Clone { } ``` -- **`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. +- **`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 size 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`. +## Numeric types and arithmetic + +Every numeric field needs a mathematical domain, a supported range, and an +overflow rule. `NumericSize` only lists operations required by aggregate value +types; it does not make those operations overflow-safe. + +| Quantity | Normal Rust type | Supported range and rule | Repository example | +|---|---|---|---| +| Collection index, length, or in-memory configuration dimension | `usize` | Values supported by the current target. Convert external fixed-width values with `usize::try_from`; reject values that do not fit. | `Problem::dims()` and graph vertex indices | +| Individual exact signed weight or cost | `i32` | The `i32` range, narrowed further when the problem requires nonnegative input. | A vertex weight in `MinimumDominatingSet<_, i32>` | +| Total of `i32` weights | `i64` | Accumulate exactly in `i64`; reject a derived value that would exceed `i64`. | `WeightElement for i32` uses `Sum = i64` | +| Unit-weight count | `i64` | Use the same total and bound representation as exact weighted variants. | `WeightElement for One` uses `Sum = i64` | +| Approximate numeric input | `f64` | Only when approximation belongs to the model or solver interface; model constructors reject NaN and infinity. | Floating-point QUBO coefficients | +| Fixed-width serialized nonnegative domain value | `u64` | The same JSON range on every target. Convert to `usize` before indexing and reject failure. | Large integer sizes in arithmetic problems | +| Exact signed objective bound | The objective total type, normally `i64` | A decision bound and the optimization result it compares against use the same type. | `Decision>` has an `i64` bound | +| SAT variable count | `usize`, at most `i32::MAX` | Reject larger formulas at construction because signed literals cannot encode them. | `Satisfiability::try_new` | +| SAT literal | nonzero `i32` | Its magnitude must be in `1..=num_vars`; `0` and `i32::MIN` are invalid. | `CNFClause` literals | + +### Indices and collection sizes + +Use `usize` for values passed to indexing, collection allocation, and +configuration dimensions. A serialized `usize` is intentionally machine-sized: +loading rejects a JSON value that does not fit the target. Use `u64` instead +when the problem definition requires a fixed serialized range, then perform an +explicit checked conversion before using it as an index. + +### Weights, costs, times, capacities, and bounds + +Choose an input type from the mathematical domain, not from the type of a later +index. Exact signed element weights normally use `i32`. A quantity that bounds +or compares with a total uses the total's type. Negative values are accepted +only when the problem definition gives them meaning; otherwise reject them in +the constructor. + +### Totals and derived arithmetic + +Do not assume one input element's type can hold a sum or product of many +elements. `WeightElement` is the source of truth for weight totals: `i32` and +`One` accumulate into `i64`, while `f64` accumulates into `f64`. For other +derived integers, choose a result type from the largest supported value and use +`checked_add`, `checked_sub`, or `checked_mul` when the operation may reach its +boundary. Overflow is an input/construction error, not an infeasible solution. + +### Conversions + +Use `From` for conversions that cannot change the value and `TryFrom` when +range or sign can change. Do not use `as` for a user/model-derived narrowing, +signedness change, SAT variable number, coefficient, or bound. A failed +conversion must report the value, destination range, and model or reduction +that rejected it. + +### JSON, CLI, and MCP boundaries + +The schema field type is the external contract. Rust constructors and serde +deserialization must apply the same validation, and schema-driven CLI/MCP +creation must parse the declared type rather than a smaller intermediate type. +Do not deserialize directly into private validated fields when doing so bypasses +the constructor invariant. + +### SAT and compact signed encodings + +CNF uses one-indexed signed `i32` literals. All CNF-backed models validate the +same range during construction and deserialization. Reductions that create +auxiliary SAT variables allocate them through the checked SAT allocator; they +must stop before constructing a target if the next ID would exceed +`i32::MAX`. Apply the same explicit-range rule to any new compact signed +encoding. + +### Exact integers and floating point + +Keep exact integer calculations in integer types. Do not convert an exact sum, +product, identifier, or comparison bound to `f64` merely to obtain more range. +An integer-to-floating conversion is permitted only at an explicitly +approximate solver boundary, where the exactly representable input range and +out-of-range behavior are documented. + +### Numeric implementation review checklist + +Issue authors describe mathematical objects, domains, and constraints; they are +not expected to choose Rust types. During implementation and review, derive and +record: + +1. every numeric input, its meaning, and its mathematical domain; +2. every computed total/product and its result type; +3. the largest supported input and derived value; +4. every narrowing or signedness-changing conversion; +5. how construction, deserialization, and reduction report overflow; +6. whether arithmetic is exact or approximate, with justification for `f64`. + ## 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. @@ -162,16 +254,50 @@ impl ReductionResult for ReductionISToVC { type Target = MinimumVertexCover; fn target_problem(&self) -> &Self::Target { &self.target } - fn extract_solution(&self, target_sol: &[usize]) -> Vec { - target_sol.iter().map(|&x| 1 - x).collect() // complement + fn extract_solution( + &self, + target_sol: &[usize], + ) -> crate::rules::ExtractionResult> { + crate::rules::traits::validate_target_solution(self.target_problem(), target_sol)?; + Ok(target_sol.iter().map(|&x| 1 - x).collect()) } } ``` +### Solution extraction contract + +`ReductionResult::extract_solution` accepts one complete target configuration +and returns the source configuration defined by the reduction. Extraction is a +fallible boundary, not a recovery mechanism: + +1. In every direct extractor, call `validate_target_solution()` once before + indexing or decoding. Composed extractors delegate this check. +2. Validate any structure required by the inverse mapping, such as exactly-one + blocks, permutations, paths, flows, or schedules. +3. Apply the reduction's mathematical inverse once and return a source + configuration with the required length and domains. +4. Return `ExtractionError` when a precondition is not satisfied. + +Do not truncate or pad input, substitute zero for missing data, select the +first of several invalid candidates, retry with another mapping, or panic on +caller-provided configuration data. Empty and singleton instances should flow +through the same mathematical mapping unless the reduction itself has a +genuine mathematical case distinction. + +Zero and sentinel values remain valid when the source model explicitly gives +them meaning. For example, `MaximumCommonEdgeSubgraph` includes an "unmapped" +sentinel in its source dimensions. Missing target data must never be +interpreted as that sentinel. + +Each conditional in an extractor should therefore either reject a named +invariant violation or implement a case in the reduction's mathematics. A +normal extractor has one validation phase followed by one decoding phase; it +does not accumulate compatibility or fallback branches. + The `#[reduction]` attribute on the `ReduceTo` impl registers the reduction in the global registry (via `inventory`): ```rust,ignore -#[reduction(overhead = { +#[reduction(size = exact { num_vertices = "num_vertices", num_edges = "num_edges", })] @@ -195,11 +321,13 @@ inventory::submit! { target_name: "MinimumVertexCover", source_variant_fn: || as Problem>::variant(), target_variant_fn: || as Problem>::variant(), - overhead_fn: || ReductionOverhead { - output_size: vec![ + size_declarations_fn: || ReductionSizeDeclarations { + relation: Some(SizeRelation::Exact), + fields: vec![ ("num_vertices", Expr::Var("num_vertices")), ("num_edges", Expr::Var("num_edges")), ], + unavailable: vec![], }, module_path: module_path!(), reduce_fn: |src: &dyn Any| -> Box { @@ -235,20 +363,14 @@ All path-finding operates on **exact variant nodes**. Use `ReductionGraph::varia | Method | Algorithm | Use case | |--------|-----------|----------| -| `find_cheapest_path(src, src_var, dst, dst_var, input_size, cost_fn)` | Dijkstra | Optimal path under a cost function | | `find_all_paths(src, src_var, dst, dst_var)` | All simple paths | Enumerate every route | +| `compose_path_size_transform(path)` | Symbolic composition | Compose each rule's exact or upper-bound size relation while preserving its promise | -Use `find_cheapest_path` with `MinimizeSteps` for fewest-hops search. - -The `PathCostFn` trait (used by `find_cheapest_path`) computes edge cost from overhead and current problem size: - -| Cost function | Strategy | -|--------------|----------| -| `MinimizeSteps` | Minimize number of hops (unit edge cost) | -| `Minimize("field")` | Minimize a single output field (e.g., `Minimize("num_variables")`) | -| `CustomCost(closure)` | User-defined: `\|overhead: &ReductionOverhead, size: &ProblemSize\| -> f64` | - -`CustomCost` wraps a closure that receives the edge's `ReductionOverhead` (polynomial mapping from input to output size fields) and the current `ProblemSize` (accumulated field values at that point in the path), and returns an `f64` edge cost. Dijkstra minimizes the total cost along the path. +Symbolic path discovery does not rank or prune routes. A rule has one relation for all of +its formulas: either an exact equality or an upper bound. Composition performs only +substitution and relation propagation: exact composed with exact stays exact; every other +combination is an upper bound. Concrete-instance measurement remains a separate execution +API. **Example:** Finding a path from `MIS{KingsSubgraph, i32}` to `VC{SimpleGraph, i32}`: @@ -262,9 +384,12 @@ MIS{KingsSubgraph,i32} -> MIS{UnitDiskGraph,i32} -> MIS{SimpleGraph,i32} -> VC{S Convert a `ReductionPath` into a typed `ExecutablePath` via `make_executable()`, then call `reduce()`: ```rust,ignore -// find_cheapest_path returns a ReductionPath (list of variant node IDs) -let rpath = graph.find_cheapest_path("Factoring", &src_var, - "SpinGlass", &dst_var, &ProblemSize::new(vec![]), &MinimizeSteps).unwrap(); +let paths = graph.find_all_paths_mode( + "Factoring", &src_var, "SpinGlass", &dst_var, ReductionMode::Witness, +); +let rpath = paths.iter() + .find(|path| path.type_names() == ["Factoring", "CircuitSAT", "SpinGlass"]) + .expect("required route"); // make_executable converts it into a typed, callable chain let path = graph.make_executable::>(&rpath).unwrap(); @@ -280,28 +405,42 @@ let solution: Vec = reduction.extract_solution(&target_solution); For full type control, you can also chain `ReduceTo::reduce_to()` calls manually at each step.

-Overhead evaluation +Size contracts -Each reduction declares how the output problem size relates to the input, expressed as symbolic `Expr` expressions. The `#[reduction]` macro parses overhead strings at compile time: +Each reduction declares one relation for all represented target-size fields and may mark +other fields unavailable with a reason. The `#[reduction]` macro parses every formula into +the canonical `Expr` DAG at compile time: ```rust,ignore -#[reduction(overhead = { +#[reduction( +size = upper_bound { num_vars = "num_vertices + num_edges", num_clauses = "3 * num_edges", +}, +unavailable = { + encoding_bits = "coefficient magnitudes are not tracked", +}, })] impl ReduceTo for Source { ... } ``` -Expressions support: constants, variables, `+`, `*`, `^`, `exp()`, `log()`, `sqrt()`. Each problem type provides inherent getter methods (e.g., `num_vertices()`, `num_edges()`) that the overhead expressions reference. +`SizeTransform` uses exact rational and arbitrary-precision integer arithmetic. Exact +relations must evaluate to non-negative integers. Upper-bound relations accept only +non-negative monotone formulas and round rational results upward. Missing fields, negative +or non-integral exact results, division by zero, and explicit conversion outside `usize` +are errors. -`evaluate_output_size(input)` substitutes input values: +Transforms can be evaluated with an explicit source size: ``` Input: ProblemSize { num_vertices: 10, num_edges: 15 } -Output: ProblemSize { num_vars: 25, num_clauses: 45 } +Output: ProblemSize { num_vars: 25 } ``` -For multi-step paths, overhead composes: the output of step N becomes the input of step N+1. Variant cast edges use `ReductionOverhead::identity()`, passing through all fields unchanged. +For multi-step paths, `compose_path_size_transform` substitutes each step into the next +without expanding the shared expression DAG. An upper bound cannot pass through a +non-monotone downstream formula. Projection to `Growth` is an explicit terminal operation, +and its exact/upper-bound relation is preserved in the result.
@@ -321,7 +460,7 @@ pub trait Solver { | Solver | Description | |--------|-------------| | **BruteForce** | Enumerates all configurations. `solve()` works for any aggregate problem; `find_witness()`, `find_all_witnesses()`, and `solve_with_witnesses()` are available when `P::Value` supports witnesses. Used for testing and verification. | -| **ILPSolver** | Enabled by default. Solves ILP instances directly with HiGHS via `good_lp`. Also provides `solve_reduced()` for witness-capable problems that implement `ReduceTo>`. | +| **ILPSolver** | Solves `ILP` and `ILP` instances directly with HiGHS via `good_lp`. Also provides `solve_reduced::()` for witness-capable problems that implement `ReduceTo>`. | ## JSON Serialization diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index 5afc10916..d8db70fc6 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -100,10 +100,17 @@ For convenience, `ILPSolver::solve_reduced` combines reduce + solve + extract in a single call: ```rust,ignore -let solution = ILPSolver::new().solve_reduced(&problem).unwrap(); +let solution = ILPSolver::new() + .solve_reduced::(&problem) + .unwrap(); assert!(problem.evaluate(&solution).is_valid()); ``` +The ILP domain is explicit because a source type may provide more than one +direct ILP reduction. Both `bool` and `i32` are supported. `solve` and +`solve_reduced` return `ILPSolveError`, which distinguishes infeasibility, +timeout, unboundedness, unsupported dynamic input, and backend failure. + ### Example 2: Reduction path search — integer factoring to spin glass Real-world problems often require **chaining** multiple reductions. Here we factor the integer 6 by reducing `Factoring` through the reduction graph to `SpinGlass`, through automatic reduction path search. ([full source](https://github.com/CodingThrust/problem-reductions/blob/main/examples/chained_reduction_factoring_to_spinglass.rs)) @@ -112,9 +119,10 @@ Let's walk through each step. #### Step 1 — Discover the reduction path -`ReductionGraph` holds every registered reduction. `find_cheapest_path` -searches for the shortest chain from a source problem variant to a target -variant. +`ReductionGraph` holds every registered reduction. The example enumerates the +witness-capable simple paths and explicitly selects the documented +`Factoring -> CircuitSAT -> SpinGlass` route. Path discovery does not rank or +automatically select a route. ```rust,ignore {{#include ../../examples/chained_reduction_factoring_to_spinglass.rs:step1}} @@ -158,21 +166,6 @@ factors. {{#include generated/factoring-result.txt}} ``` -#### Step 5 — Inspect the overhead - -Each reduction edge carries a polynomial overhead mapping source problem -sizes to target sizes. `path_overheads` returns the per-edge -polynomials, and `compose_path_overhead` composes them symbolically into a -single end-to-end formula. - -```rust,ignore -{{#include ../../examples/chained_reduction_factoring_to_spinglass.rs:overhead}} -``` - -```text -{{#include generated/factoring-overhead.txt}} -``` - ## Solvers Three solvers are available: @@ -180,14 +173,10 @@ Three solvers are available: | Solver | Use Case | Notes | |--------|----------|-------| | [`BruteForce`](api/problemreductions/solvers/struct.BruteForce.html) | Small instances (<20 variables) | Enumerates all configurations | -| [`ILPSolver`](api/problemreductions/solvers/ilp/struct.ILPSolver.html) | Larger instances | Enabled by default (`ilp` feature) | +| [`ILPSolver`](api/problemreductions/solvers/ilp/struct.ILPSolver.html) | Larger instances | Uses the bundled HiGHS backend | | [`CustomizedSolver`](api/problemreductions/solvers/customized/struct.CustomizedSolver.html) | Structure-exploiting | Uses problem-specific exact algorithms | -ILP support is enabled by default. To disable it: - -```bash -cargo add problemreductions --no-default-features -``` +ILP support through HiGHS is part of the library and is always available. ## JSON Resources diff --git a/docs/src/mcp.md b/docs/src/mcp.md index 05913595c..396b3b675 100644 --- a/docs/src/mcp.md +++ b/docs/src/mcp.md @@ -79,8 +79,8 @@ The MCP server provides 10 tools organized into two categories: **graph query to | `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) | 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) | +| `find_path` | `source` (string), `target` (string), `max_paths` (int, default: 20), `problem_json` (optional string) | Find reduction paths and explain how size changes. With a complete source instance, execute each returned path and report the actual constructed sizes. | +| `export_graph` | *(none)* | Export the full reduction graph as JSON | ### Instance Tools @@ -89,7 +89,7 @@ The MCP server provides 10 tools organized into two categories: **graph query to | `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 | +| `reduce` | `problem_json` (string), `path_json` (string) | Reduce a problem instance along an explicitly supplied route, returning a bundle with the transformed instance and path metadata | | `solve` | `problem_json` (string), `solver` ("ilp"\|"brute-force", default: "ilp"), `timeout` (int, default: 0) | Solve a problem instance or reduction bundle using ILP or brute-force, with optional timeout | ## Available Prompts @@ -103,5 +103,5 @@ The server provides 7 task-oriented prompt templates: | `compare` | `problem_a` (required), `problem_b` (required) | Compare two problem types | | `reduce` | `source` (required), `target` (required) | Step-by-step reduction walkthrough | | `solve` | `problem_type` (required), `params` (required) | Create and solve a problem instance | -| `find_reduction` | `source` (required), `target` (required) | Find the best reduction path between two problems | +| `find_reduction` | `source` (required), `target` (required) | Find reduction paths between two problems and explain how size changes | | `overview` | *(none)* | Explore the full landscape of NP-hard problems | diff --git a/docs/src/static/reduction-graph.js b/docs/src/static/reduction-graph.js index 3e2c0d0b9..3e382efe9 100644 --- a/docs/src/static/reduction-graph.js +++ b/docs/src/static/reduction-graph.js @@ -176,7 +176,7 @@ if (srcName === dstName) return; var key = srcName + '->' + dstName; if (!nameLevelEdges[key]) { - nameLevelEdges[key] = { count: 0, overhead: e.overhead, doc_path: e.doc_path }; + nameLevelEdges[key] = { count: 0, sizeFields: e.size_fields, doc_path: e.doc_path }; } nameLevelEdges[key].count++; }); @@ -191,7 +191,7 @@ target: problemNodeIds[parts[1]], label: info.count > 1 ? '\u00d7' + info.count : '', edgeLevel: 'collapsed', - overhead: info.overhead, + sizeFields: info.sizeFields, doc_path: info.doc_path } }); @@ -208,7 +208,7 @@ edgeMap[key] = { source: srcId, target: dstId, - overhead: e.overhead || [], + sizeFields: e.size_fields || [], doc_path: e.doc_path || '' }; } @@ -219,16 +219,18 @@ var srcName = e.source.split('/')[0]; var dstName = e.target.split('/')[0]; var isVariantCast = srcName === dstName && - e.overhead && - e.overhead.length > 0 && - e.overhead.every(function(o) { return o.field === o.formula; }); + e.sizeFields && + e.sizeFields.length > 0 && + e.sizeFields.every(function(o) { + return o.contract === 'exact' && o.field === o.formula; + }); return { data: { id: 'variant_' + key, source: e.source, target: e.target, edgeLevel: 'variant', - overhead: e.overhead, + sizeFields: e.sizeFields, doc_path: e.doc_path, isVariantCast: isVariantCast } @@ -531,8 +533,12 @@ cy.on('mouseover', 'edge', function(evt) { var d = evt.target.data(); var html = '' + evt.target.source().data('label') + ' \u2192 ' + evt.target.target().data('label') + ''; - if (d.overhead && d.overhead.length > 0) { - html += '
' + d.overhead.map(function(o) { return '' + o.field + ' = ' + o.formula + ''; }).join('
'); + if (d.sizeFields && d.sizeFields.length > 0) { + html += '
' + d.sizeFields.map(function(o) { + if (o.contract === 'exact') return '' + o.field + ' = ' + o.formula + ' (exact)'; + if (o.contract === 'upper_bound') return '' + o.field + '' + o.formula + ' (upper bound)'; + return '' + o.field + ' unavailable: ' + o.reason; + }).join('
'); } html += '
Click to highlight, double-click for source code'; tooltip.innerHTML = html; @@ -642,8 +648,12 @@ edge.source().addClass('highlighted'); edge.target().addClass('highlighted'); var text = edge.source().data('label') + ' \u2192 ' + edge.target().data('label'); - if (d.overhead && d.overhead.length > 0) { - text += ' | ' + d.overhead.map(function(o) { return o.field + ' = ' + o.formula; }).join(', '); + if (d.sizeFields && d.sizeFields.length > 0) { + text += ' | ' + d.sizeFields.map(function(o) { + if (o.contract === 'exact') return o.field + ' = ' + o.formula + ' (exact)'; + if (o.contract === 'upper_bound') return o.field + ' <= ' + o.formula + ' (upper bound)'; + return o.field + ' unavailable: ' + o.reason; + }).join(', '); } instructions.textContent = text; clearBtn.style.display = 'inline'; diff --git a/examples/chained_reduction_factoring_to_spinglass.rs b/examples/chained_reduction_factoring_to_spinglass.rs index 8374906e7..8ec746d49 100644 --- a/examples/chained_reduction_factoring_to_spinglass.rs +++ b/examples/chained_reduction_factoring_to_spinglass.rs @@ -7,28 +7,28 @@ // ANCHOR: imports use problemreductions::models::algebraic::ILP; use problemreductions::prelude::*; -use problemreductions::rules::{MinimizeSteps, ReductionGraph}; +use problemreductions::rules::{ReductionGraph, ReductionMode}; use problemreductions::solvers::ILPSolver; use problemreductions::topology::SimpleGraph; -use problemreductions::types::ProblemSize; // ANCHOR_END: imports -pub fn run() { +pub fn run() -> std::result::Result<(), Box> { // ANCHOR: example // ANCHOR: step1 let graph = ReductionGraph::new(); // all registered reductions let src_var = ReductionGraph::variant_to_map(&Factoring::variant()); // {} (no variant params) let dst_var = ReductionGraph::variant_to_map(&SpinGlass::::variant()); // {graph: "SimpleGraph", weight: "f64"} - let rpath = graph - .find_cheapest_path( - "Factoring", // source problem name - &src_var, // source variant map - "SpinGlass", // target problem name - &dst_var, // target variant map - &ProblemSize::new(vec![]), // input size (empty = unknown) - &MinimizeSteps, // cost function: fewest hops - ) - .unwrap(); + let paths = graph.find_all_paths_mode( + "Factoring", + &src_var, + "SpinGlass", + &dst_var, + ReductionMode::Witness, + ); + let rpath = paths + .iter() + .find(|path| path.type_names() == ["Factoring", "CircuitSAT", "SpinGlass"]) + .expect("explicit Factoring -> CircuitSAT -> SpinGlass route"); println!(" {}", rpath); // ANCHOR_END: step1 @@ -45,7 +45,7 @@ pub fn run() { let solver = ILPSolver::new(); let reduction = ReduceTo::>::reduce_to(&factoring); let ilp_solution = solver.solve(reduction.target_problem()).unwrap(); - let solution = reduction.extract_solution(&ilp_solution); + let solution = reduction.extract_solution(&ilp_solution).unwrap(); // ANCHOR_END: step3 // ANCHOR: step4 @@ -54,26 +54,10 @@ pub fn run() { assert_eq!(p * q, 6, "Factors should multiply to 6"); // ANCHOR_END: step4 - // ANCHOR: overhead - // Print per-edge overhead polynomials - let edge_overheads = graph.path_overheads(&rpath); - for (i, overhead) in edge_overheads.iter().enumerate() { - println!("{} → {}:", rpath.steps[i], rpath.steps[i + 1]); - for (field, poly) in &overhead.output_size { - println!(" {} = {}", field, poly); - } - } - - // Compose overheads symbolically along the full path - let composed = graph.compose_path_overhead(&rpath); - println!("Composed (source → target):"); - for (field, poly) in &composed.output_size { - println!(" {} = {}", field, poly); - } - // ANCHOR_END: overhead // ANCHOR_END: example + Ok(()) } -fn main() { +fn main() -> std::result::Result<(), Box> { run() } diff --git a/examples/export_graph.rs b/examples/export_graph.rs index 2b1b6e4dc..5a1ef241d 100644 --- a/examples/export_graph.rs +++ b/examples/export_graph.rs @@ -3,9 +3,9 @@ //! Run with: `cargo run --example export_graph [output_path]` use problemreductions::rules::ReductionGraph; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; -fn main() { +pub fn run(output_path: &Path) { let graph = ReductionGraph::new(); // Print statistics @@ -13,19 +13,13 @@ fn main() { println!(" Problem types: {}", graph.num_types()); println!(" Reductions: {}", graph.num_reductions()); - // Export to JSON (single source for both mdBook and paper) - let output_path = std::env::args() - .nth(1) - .map(PathBuf::from) - .unwrap_or_else(|| PathBuf::from("docs/src/reductions/reduction_graph.json")); - // Create parent directories if needed if let Some(parent) = output_path.parent() { std::fs::create_dir_all(parent).expect("Failed to create output directory"); } graph - .to_json_file(&output_path) + .to_json_file(output_path) .expect("Failed to write JSON file"); println!("\nExported to: {}", output_path.display()); @@ -34,3 +28,11 @@ fn main() { println!("\nJSON content:"); println!("{}", graph.to_json_string().unwrap()); } + +fn main() { + let output_path = std::env::args() + .nth(1) + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from("docs/src/reductions/reduction_graph.json")); + run(&output_path); +} diff --git a/examples/export_petersen_mapping.rs b/examples/export_petersen_mapping.rs index d8a9ec56f..e2c2de05b 100644 --- a/examples/export_petersen_mapping.rs +++ b/examples/export_petersen_mapping.rs @@ -90,7 +90,7 @@ fn write_json(data: &T, path: &Path) { println!(" Wrote: {}", path.display()); } -fn main() { +pub fn run(output_dir: &Path) { println!("\n=== Independent Set to Grid Graph IS (Unit Disk Mapping) ===\n"); // Petersen graph: n=10, MIS=4 @@ -133,7 +133,7 @@ fn main() { edges: petersen_edges.clone(), mis: petersen_mis, }; - write_json(&source, Path::new("docs/paper/static/petersen_source.json")); + write_json(&source, &output_dir.join("petersen_source.json")); println!("\n=== Mapping to Grid Graphs ===\n"); @@ -155,7 +155,7 @@ fn main() { ); write_json( &square_weighted_viz, - Path::new("docs/paper/static/petersen_square_weighted.json"), + &output_dir.join("petersen_square_weighted.json"), ); // Map to unweighted King's subgraph (square lattice) @@ -179,7 +179,7 @@ fn main() { ); write_json( &square_unweighted_viz, - Path::new("docs/paper/static/petersen_square_unweighted.json"), + &output_dir.join("petersen_square_unweighted.json"), ); // Map to weighted triangular lattice @@ -200,7 +200,7 @@ fn main() { ); write_json( &triangular_viz, - Path::new("docs/paper/static/petersen_triangular.json"), + &output_dir.join("petersen_triangular.json"), ); println!("\n=== Summary ===\n"); @@ -228,3 +228,7 @@ fn main() { println!("\n✓ Unit disk mapping demonstrated successfully"); println!(" JSON files exported for paper visualization"); } + +fn main() { + run(Path::new("docs/paper/static")); +} diff --git a/examples/export_schemas.rs b/examples/export_schemas.rs index 19024dbb7..427386ca3 100644 --- a/examples/export_schemas.rs +++ b/examples/export_schemas.rs @@ -3,22 +3,25 @@ //! Run with: `cargo run --example export_schemas [output_path]` use problemreductions::registry::collect_schemas; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; -fn main() { +pub fn run(output_path: &Path) { let schemas = collect_schemas(); println!("Collected {} problem schemas", schemas.len()); - // Single source for both mdBook and paper - let output_path = std::env::args() - .nth(1) - .map(PathBuf::from) - .unwrap_or_else(|| PathBuf::from("docs/src/reductions/problem_schemas.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_pretty(&schemas).expect("Failed to serialize"); - std::fs::write(&output_path, &json).expect("Failed to write file"); + std::fs::write(output_path, &json).expect("Failed to write file"); println!("Exported to: {}", output_path.display()); } + +fn main() { + let output_path = std::env::args() + .nth(1) + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from("docs/src/reductions/problem_schemas.json")); + run(&output_path); +} diff --git a/problemreductions-cli/Cargo.toml b/problemreductions-cli/Cargo.toml index 234f607fd..be37a9085 100644 --- a/problemreductions-cli/Cargo.toml +++ b/problemreductions-cli/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" description = "CLI tool for exploring NP-hard problem reductions" license = "MIT" repository = "https://github.com/CodingThrust/problem-reductions" +default-run = "pred" [[bin]] name = "pred" @@ -15,16 +16,12 @@ name = "pred-sym" path = "src/bin/pred_sym.rs" [features] -default = ["highs"] -all = ["highs", "mcp"] -highs = ["problemreductions/ilp-highs"] +all = ["mcp"] mcp = ["dep:rmcp", "dep:tokio", "dep:schemars", "dep:tracing", "dep:tracing-subscriber"] -cplex = ["problemreductions/ilp-cplex"] -lp-solvers = ["problemreductions/ilp-lp-solvers"] [dependencies] -problemreductions = { version = "0.6.0", path = "..", default-features = false, features = ["example-db"] } -clap = { version = "4", features = ["derive"] } +problemreductions = { version = "0.6.0", path = "..", features = ["example-db"] } +clap = { version = "4", features = ["derive", "string"] } anyhow = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/problemreductions-cli/src/bin/pred_sym.rs b/problemreductions-cli/src/bin/pred_sym.rs index daa28bf7a..23fc71287 100644 --- a/problemreductions-cli/src/bin/pred_sym.rs +++ b/problemreductions-cli/src/bin/pred_sym.rs @@ -1,5 +1,5 @@ use clap::{Parser, Subcommand}; -use problemreductions::{big_o_normal_form, canonical_form, Expr, ProblemSize}; +use problemreductions::{big_o_normal_form, evaluate_approximate, Expr, ProblemSize}; #[derive(Parser)] #[command( @@ -19,11 +19,6 @@ enum Commands { /// Expression string expr: String, }, - /// Compute exact canonical form - Canon { - /// Expression string - expr: String, - }, /// Compute Big-O normal form BigO { /// Expression string @@ -33,7 +28,7 @@ enum Commands { #[arg(long)] raw: bool, }, - /// Compare two expressions (exits with code 1 if neither exact nor Big-O equal) + /// Compare two expressions for Big-O equivalence (exits 1 if not equal) Compare { /// First expression a: String, @@ -69,16 +64,6 @@ fn main() { let parsed = parse_expr_or_exit(&expr); println!("{parsed}"); } - Commands::Canon { expr } => { - let parsed = parse_expr_or_exit(&expr); - match canonical_form(&parsed) { - Ok(result) => println!("{result}"), - Err(e) => { - eprintln!("Error: {e}"); - std::process::exit(1); - } - } - } Commands::BigO { expr, raw } => { let parsed = parse_expr_or_exit(&expr); match big_o_normal_form(&parsed) { @@ -98,49 +83,49 @@ fn main() { Commands::Compare { a, b } => { let expr_a = parse_expr_or_exit(&a); let expr_b = parse_expr_or_exit(&b); - let canon_a = canonical_form(&expr_a); - let canon_b = canonical_form(&expr_b); let big_o_a = big_o_normal_form(&expr_a); let big_o_b = big_o_normal_form(&expr_b); println!("Expression A: {a}"); println!("Expression B: {b}"); - let mut exact_equal = false; - let mut big_o_equal = false; - if let (Ok(ca), Ok(cb)) = (&canon_a, &canon_b) { - exact_equal = ca == cb; - println!("Canonical A: {ca}"); - println!("Canonical B: {cb}"); - println!("Exact equal: {exact_equal}"); - } - if let (Ok(ba), Ok(bb)) = (&big_o_a, &big_o_b) { - big_o_equal = ba == bb; - println!("Big-O A: O({ba})"); - println!("Big-O B: O({bb})"); - println!("Big-O equal: {big_o_equal}"); - } - if !exact_equal && !big_o_equal { - std::process::exit(1); + match (&big_o_a, &big_o_b) { + (Ok(ba), Ok(bb)) => { + // Rendering is canonical, so equal growth ⇒ equal Big-O expr. + let big_o_equal = ba == bb; + println!("Big-O A: O({ba})"); + println!("Big-O B: O({bb})"); + println!("Big-O equal: {big_o_equal}"); + if !big_o_equal { + std::process::exit(1); + } + } + _ => { + if let Err(e) = &big_o_a { + println!("Big-O A: "); + } + if let Err(e) = &big_o_b { + println!("Big-O B: "); + } + std::process::exit(1); + } } } Commands::Eval { expr, vars } => { let parsed = parse_expr_or_exit(&expr); - let bindings: Vec<(&str, usize)> = vars + let bindings: Vec<(String, usize)> = vars .split(',') .filter_map(|pair| { let mut parts = pair.splitn(2, '='); let name = parts.next()?.trim(); let value: usize = parts.next()?.trim().parse().ok()?; - // Leak the name for &'static str compatibility - let leaked: &'static str = Box::leak(name.to_string().into_boxed_str()); - Some((leaked, value)) + Some((name.to_string(), value)) }) .collect(); // Check for unbound variables let expr_vars = parsed.variables(); let bound_vars: std::collections::HashSet<&str> = - bindings.iter().map(|(k, _)| *k).collect(); + bindings.iter().map(|(name, _)| name.as_str()).collect(); let mut unbound: Vec<&str> = expr_vars .iter() .filter(|v| !bound_vars.contains(*v)) @@ -156,8 +141,13 @@ fn main() { std::process::exit(1); } - let size = ProblemSize::new(bindings); - let result = parsed.eval(&size); + let size = ProblemSize { + components: bindings, + }; + let result = evaluate_approximate(&parsed, &size).unwrap_or_else(|error| { + eprintln!("Error: {error}"); + std::process::exit(1); + }); // Format as integer if it's a whole number if (result - result.round()).abs() < 1e-10 { diff --git a/problemreductions-cli/src/cli.rs b/problemreductions-cli/src/cli.rs index 70fa1e5af..78c5c024e 100644 --- a/problemreductions-cli/src/cli.rs +++ b/problemreductions-cli/src/cli.rs @@ -1,7 +1,10 @@ -use clap::{CommandFactory, Parser, Subcommand, ValueEnum}; -use std::collections::HashMap; +use clap::{CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum}; +use problemreductions::registry::ProblemCategory; +use std::ffi::OsString; use std::path::PathBuf; +pub use crate::create_args::CreateArgs; + #[derive(Parser)] #[command( name = "pred", @@ -17,7 +20,7 @@ Piping (use - to read from stdin): pred create MIS --graph 0-1,1-2 | pred solve - # when an ILP reduction path exists pred create StringToStringCorrection --source-string \"0,1,2,3,1,0\" --target-string \"0,1,3,2,1\" --bound 2 | pred solve - --solver brute-force pred create MIS --graph 0-1,1-2 | pred evaluate - --config 1,0,1 - pred create MIS --graph 0-1,1-2 | pred reduce - --to QUBO + pred create MIS --graph 0-1,1-2 | pred reduce - --via route.json JSON output (any command): pred list --json # JSON to stdout @@ -46,18 +49,65 @@ pub struct Cli { pub command: Commands, } +impl Cli { + pub fn try_parse() -> Result { + Self::try_parse_from(std::env::args_os()) + } + + pub fn try_parse_from(args: I) -> Result + where + I: IntoIterator, + T: Into, + { + // The discovery command treats the problem spec as an external subcommand, + // so it can capture the selected model without registering the whole catalog. + let args = args.into_iter().map(Into::into).collect::>(); + let command = ::command(); + let discovery_matches = command.clone().try_get_matches_from(args.clone())?; + let selected = discovery_matches + .subcommand_matches("create") + .and_then(|matches| matches.subcommand_name()); + + let mut matches = if let Some(selected) = selected { + crate::create_args::command_for_selected_problem(command, selected)? + .try_get_matches_from(args)? + } else { + discovery_matches + }; + Self::from_arg_matches_mut(&mut matches) + } +} + #[derive(Subcommand)] pub enum Commands { - /// List all registered problem types (or reduction rules with --rules) + /// Browse registered problem types (or reduction rules with --rules) #[command(after_help = "\ Examples: - pred list # list problem types - pred list --rules # list all reduction rules + pred list # show catalog summary and categories + pred list matching # search names and aliases + pred list --category graph # list graph problems + pred list --all # list every problem compactly + pred list --rules --all # list every reduction rule pred list -o problems.json # save as JSON")] List { + /// Case-insensitive substring to search in names and aliases + query: Option, + /// List reduction rules instead of problem types #[arg(long)] rules: bool, + + /// Restrict problems to a model category such as graph, set, or misc + #[arg(long, conflicts_with = "rules")] + category: Option, + + /// List the complete catalog instead of the summary + #[arg(long)] + all: bool, + + /// Include per-variant complexity, rule counts, or rule size contracts + #[arg(long)] + verbose: bool, }, /// Show details for a problem type or variant (fields, reductions, complexity) @@ -109,14 +159,13 @@ Use `pred to ` for incoming neighbors (what reduces to this).")] hops: usize, }, - /// Find the cheapest reduction path between two problems + /// Find reduction paths between two problems #[command(after_help = "\ Examples: - pred path MIS QUBO # cheapest path - pred path MIS QUBO --all # all paths - pred path MIS QUBO -o path.json # save for `pred reduce --via` - pred path MIS QUBO --all -o paths/ # save all paths to a folder - pred path MIS QUBO --cost minimize:num_variables + pred path MIS QUBO # inspect reduction paths + pred path MIS Clique mis.json # execute paths on an instance + pred path MIS QUBO --max-paths 50 # increase the output cap + pred path MIS QUBO -o paths.json # save the path set Use `pred list` to see available problems.")] Path { @@ -126,15 +175,11 @@ Use `pred list` to see available problems.")] /// Target problem (e.g., QUBO) #[arg(value_parser = crate::problem_name::ProblemNameParser)] target: String, - /// Cost function [default: minimize-steps] - #[arg(long, default_value = "minimize-steps")] - cost: String, - /// Show all paths instead of just the cheapest - #[arg(long)] - all: bool, - /// Maximum paths to return in --all mode + /// Maximum paths to return #[arg(long, default_value_t = 20)] max_paths: usize, + /// Source problem instance JSON. When present, execute every returned path and measure each constructed problem. + instance: Option, }, /// Export the reduction graph to JSON @@ -227,1008 +272,16 @@ pub enum ExampleSide { #[derive(clap::Args)] #[command(after_help = "\ -TIP: Run `pred create ` (no other flags) to see problem-specific help. - Not every flag applies to every problem — the above list shows ALL flags. - -Flags by problem type: - MIS, MVC, MaxClique, MinDomSet --graph, --weights - MaxCut, MaxMatching, TSP, BottleneckTravelingSalesman --graph, --edge-weights - LongestPath --graph, --edge-lengths, --source-vertex, --target-vertex - HamiltonianPathBetweenTwoVertices --graph, --source-vertex, --target-vertex - ShortestWeightConstrainedPath --graph, --edge-lengths, --edge-weights, --source-vertex, --target-vertex, --weight-bound - GraphPartitioning --graph, --num-partitions - MaximalIS --graph, --weights - SAT, NAESAT --num-vars, --clauses - KSAT --num-vars, --clauses [--k] - NonTautology --num-vars, --disjuncts - QUBO --matrix - SpinGlass --graph, --couplings, --fields - KColoring --graph, --k - KClique --graph, --k - DecisionMinimumVertexCover --graph, --weights, --bound - MinimumMultiwayCut --graph, --terminals, --edge-weights - MonochromaticTriangle --graph - PartitionIntoTriangles --graph - GeneralizedHex --graph, --source, --sink - IntegralFlowWithMultipliers --arcs, --capacities, --source, --sink, --multipliers, --requirement - MinimumEdgeCostFlow --arcs, --edge-weights (prices), --capacities, --source, --sink, --requirement - MinimumCostMaximumFlow --arcs, --capacities, --costs, --source, --sink - MinimumCostCirculation, MCC --arcs, --capacities, --costs - MinimumCutIntoBoundedSets --graph, --edge-weights, --source, --sink, --size-bound - HamiltonianCircuit, HC --graph - MaximumLeafSpanningTree --graph - LongestCircuit --graph, --edge-weights - BoundedComponentSpanningForest --graph, --weights, --k, --max-weight - UndirectedFlowLowerBounds --graph, --capacities, --lower-bounds, --source, --sink, --requirement - IntegralFlowBundles --arcs, --bundles, --bundle-capacities, --source, --sink, --requirement [--num-vertices] - UndirectedTwoCommodityIntegralFlow --graph, --capacities, --source-1, --sink-1, --source-2, --sink-2, --requirement-1, --requirement-2 - DisjointConnectingPaths --graph, --terminal-pairs - IntegralFlowHomologousArcs --arcs, --capacities, --source, --sink, --requirement, --homologous-pairs - IsomorphicSpanningTree --graph, --tree - KthBestSpanningTree --graph, --edge-weights, --k, --bound - LengthBoundedDisjointPaths --graph, --source, --sink, --max-length - PathConstrainedNetworkFlow --arcs, --capacities, --source, --sink, --paths, --requirement - Factoring --target, --m, --n - BinPacking --sizes, --capacity - Clustering --distance-matrix, --k, --diameter-bound - CapacityAssignment --capacities, --cost-matrix, --delay-matrix, --cost-budget, --delay-budget - ProductionPlanning --num-periods, --demands, --capacities, --setup-costs, --production-costs, --inventory-costs, --cost-bound - SubsetProduct --sizes, --target - SubsetSum --sizes, --target - MinimumAxiomSet --n, --true-sentences, --implications - Numerical3DimensionalMatching --w-sizes, --x-sizes, --y-sizes, --bound - Betweenness --n, --sets (triples a,b,c) - CyclicOrdering --n, --sets (triples a,b,c) - ThreePartition --sizes, --bound - DynamicStorageAllocation --release-times, --deadlines, --sizes, --capacity - KthLargestMTuple --sets, --k, --bound - QuadraticCongruences --coeff-a, --coeff-b, --coeff-c - QuadraticDiophantineEquations --coeff-a, --coeff-b, --coeff-c - SimultaneousIncongruences --pairs (semicolon-separated a,b pairs) - SumOfSquaresPartition --sizes, --num-groups - ExpectedRetrievalCost --probabilities, --num-sectors - PaintShop --sequence - MaximumSetPacking --subsets [--weights] - MinimumHittingSet --universe-size, --subsets - MinimumSetCovering --universe-size, --subsets [--weights] - EnsembleComputation --universe-size, --subsets, --budget - ComparativeContainment --universe-size, --r-sets, --s-sets [--r-weights] [--s-weights] - X3C (ExactCoverBy3Sets) --universe-size, --subsets (3 elements each) - 3DM (ThreeDimensionalMatching) --universe-size, --subsets (triples w,x,y) - ThreeMatroidIntersection --universe-size, --partitions, --bound - SetBasis --universe-size, --subsets, --k - MinimumCardinalityKey --num-attributes, --dependencies - PrimeAttributeName --universe-size, --dependencies, --query-attribute - RootedTreeStorageAssignment --universe-size, --subsets, --bound - TwoDimensionalConsecutiveSets --alphabet-size, --subsets - BicliqueCover --left, --right, --biedges, --k - BalancedCompleteBipartiteSubgraph --left, --right, --biedges, --k - BiconnectivityAugmentation --graph, --potential-weights, --budget [--num-vertices] - PartialFeedbackEdgeSet --graph, --budget, --max-cycle-length [--num-vertices] - BMF --matrix (0/1), --rank - ConsecutiveBlockMinimization --matrix (JSON 2D bool), --bound-k - ConsecutiveOnesMatrixAugmentation --matrix (0/1), --bound - ConsecutiveOnesSubmatrix --matrix (0/1), --k - SparseMatrixCompression --matrix (0/1), --bound - MaximumLikelihoodRanking --matrix (i32 rows, semicolon-separated) - MinimumMatrixCover --matrix (i64 rows, semicolon-separated) - MinimumWeightDecoding --matrix (JSON 2D bool), --rhs (comma-separated booleans) - FeasibleBasisExtension --matrix (JSON 2D i64), --rhs, --required-columns - SteinerTree --graph, --edge-weights, --terminals - MultipleCopyFileAllocation --graph, --usage, --storage - AcyclicPartition --arcs [--weights] [--arc-weights] --weight-bound --cost-bound [--num-vertices] - CVP --basis, --target-vec [--bounds] - MultiprocessorScheduling --lengths, --num-processors, --deadline - SchedulingToMinimizeWeightedCompletionTime --lengths, --weights, --num-processors - SequencingWithinIntervals --release-times, --deadlines, --lengths - OptimalLinearArrangement --graph - RootedTreeArrangement --graph, --bound - MinMaxMulticenter (pCenter) --graph, --weights, --edge-weights, --k - MixedChinesePostman (MCPP) --graph, --arcs, --edge-weights, --arc-weights [--num-vertices] - RuralPostman (RPP) --graph, --edge-weights, --required-edges - StackerCrane --arcs, --graph, --arc-lengths, --edge-lengths [--num-vertices] - MultipleChoiceBranching --arcs [--weights] --partition --threshold [--num-vertices] - AdditionalKey --num-attributes, --dependencies, --relation-attrs [--known-keys] - ConsistencyOfDatabaseFrequencyTables --num-objects, --attribute-domains, --frequency-tables [--known-values] - SubgraphIsomorphism --graph (host), --pattern (pattern) - GroupingBySwapping --string, --bound [--alphabet-size] - LCS --strings [--alphabet-size] - ClosestString --alphabet-size, --strings - ClosestSubstring --alphabet-size, --strings, --substring-length - FAS --arcs [--weights] [--num-vertices] - FVS --arcs [--weights] [--num-vertices] - QBF --num-vars, --clauses, --quantifiers - SteinerTreeInGraphs --graph, --edge-weights, --terminals - PartitionIntoPathsOfLength2 --graph - ResourceConstrainedScheduling --num-processors, --resource-bounds, --resource-requirements, --deadline - IntegerKnapsack --sizes, --values, --capacity - PartiallyOrderedKnapsack --sizes, --values, --capacity, --precedences - QAP --matrix (cost), --distance-matrix - StrongConnectivityAugmentation --arcs, --candidate-arcs, --bound [--num-vertices] - JobShopScheduling --jobs [--num-processors] - FlowShopScheduling --task-lengths, --deadline [--num-processors] - StaffScheduling --schedules, --requirements, --num-workers, --k - TimetableDesign --num-periods, --num-craftsmen, --num-tasks, --craftsman-avail, --task-avail, --requirements - MinimumTardinessSequencing --num-tasks, --deadlines [--precedences] - RectilinearPictureCompression --matrix (0/1), --k - SchedulingWithIndividualDeadlines --num-tasks, --num-processors/--m, --deadlines [--precedences] - SequencingToMinimizeMaximumCumulativeCost --costs [--precedences] - SequencingToMinimizeTardyTaskWeight --lengths, --weights, --deadlines - SequencingToMinimizeWeightedCompletionTime --lengths, --weights [--precedences] - SequencingToMinimizeWeightedTardiness --lengths, --weights, --deadlines, --bound - SequencingWithDeadlinesAndSetUpTimes --lengths, --deadlines, --compilers, --setup-times - MinimumExternalMacroDataCompression --string, --pointer-cost [--alphabet-size] - MinimumInternalMacroDataCompression --string, --pointer-cost [--alphabet-size] - SCS --strings [--alphabet-size] - StringToStringCorrection --source-string, --target-string, --bound [--alphabet-size] - D2CIF --arcs, --capacities, --source-1, --sink-1, --source-2, --sink-2, --requirement-1, --requirement-2 - MinimumDummyActivitiesPert --arcs [--num-vertices] - FeasibleRegisterAssignment --arcs, --assignment, --k [--num-vertices] - MinimumFaultDetectionTestSet --arcs, --inputs, --outputs [--num-vertices] - MinimumWeightAndOrGraph --arcs, --source, --gate-types, --weights [--num-vertices] - MinimumCodeGenerationOneRegister --arcs [--num-vertices] - MinimumCodeGenerationParallelAssignments --num-variables, --assignments - MinimumCodeGenerationUnlimitedRegisters --left-arcs, --right-arcs [--num-vertices] - MinimumRegisterSufficiencyForLoops --loop-length, --loop-variables - RegisterSufficiency --arcs, --bound [--num-vertices] - CBQ --domain-size, --relations, --conjuncts-spec - IntegerExpressionMembership --expression (JSON), --target - MinimumGeometricConnectedDominatingSet --positions (float x,y pairs), --radius - MinimumDecisionTree --test-matrix (JSON 2D bool), --num-objects, --num-tests - MinimumDisjunctiveNormalForm (MinDNF) --num-vars, --truth-table - SquareTiling (WangTiling) --num-colors, --tiles, --grid-size - ILP, CircuitSAT (via reduction only) - -Geometry graph variants (use slash notation, e.g., MIS/KingsSubgraph): - KingsSubgraph, TriangularSubgraph --positions (integer x,y pairs) - UnitDiskGraph --positions (float x,y pairs) [--radius] - -Random generation: - --random --num-vertices N [--edge-prob 0.5] [--seed 42] - Examples: - pred create --example MIS/SimpleGraph/i32 - pred create --example MVC/SimpleGraph/i32 --to MIS/SimpleGraph/i32 - pred create --example MVC/SimpleGraph/i32 --to MIS/SimpleGraph/i32 --example-side target - pred create MIS --graph 0-1,1-2,2-3 --weights 1,1,1 - pred create SAT --num-vars 3 --clauses \"1,2;-1,3\" - pred create NonTautology --num-vars 3 --disjuncts \"1,2,3;-1,-2,-3\" - pred create QUBO --matrix \"1,0.5;0.5,2\" - pred create CapacityAssignment --capacities 1,2,3 --cost-matrix \"1,3,6;2,4,7;1,2,5\" --delay-matrix \"8,4,1;7,3,1;6,3,1\" --cost-budget 10 --delay-budget 12 - pred create ProductionPlanning --num-periods 6 --demands 5,3,7,2,8,5 --capacities 12,12,12,12,12,12 --setup-costs 10,10,10,10,10,10 --production-costs 1,1,1,1,1,1 --inventory-costs 1,1,1,1,1,1 --cost-bound 80 - pred create GeneralizedHex --graph 0-1,0-2,0-3,1-4,2-4,3-4,4-5 --source 0 --sink 5 - pred create IntegralFlowWithMultipliers --arcs \"0>1,0>2,1>3,2>3\" --capacities 1,1,2,2 --source 0 --sink 3 --multipliers 1,2,3,1 --requirement 2 - pred create MultipleChoiceBranching/i32 --arcs \"0>1,0>2,1>3,2>3,1>4,3>5,4>5,2>4\" --weights 3,2,4,1,2,3,1,3 --partition \"0,1;2,3;4,7;5,6\" --bound 10 - pred create GroupingBySwapping --string \"0,1,2,0,1,2\" --bound 5 | pred solve - --solver brute-force - pred create StringToStringCorrection --source-string \"0,1,2,3,1,0\" --target-string \"0,1,3,2,1\" --bound 2 | pred solve - --solver brute-force - pred create MIS/KingsSubgraph --positions \"0,0;1,0;1,1;0,1\" - pred create MIS/UnitDiskGraph --positions \"0,0;1,0;0.5,0.8\" --radius 1.5 - pred create MIS --random --num-vertices 10 --edge-prob 0.3 - pred create MultiprocessorScheduling --lengths 4,5,3,2,6 --num-processors 2 --deadline 10 - pred create SchedulingToMinimizeWeightedCompletionTime --lengths 1,2,3,4,5 --weights 6,4,3,2,1 --num-processors 2 - pred create UndirectedFlowLowerBounds --graph 0-1,0-2,1-3,2-3,1-4,3-5,4-5 --capacities 2,2,2,2,1,3,2 --lower-bounds 1,1,0,0,1,0,1 --source 0 --sink 5 --requirement 3 - pred create ConsistencyOfDatabaseFrequencyTables --num-objects 6 --attribute-domains \"2,3,2\" --frequency-tables \"0,1:1,1,1|1,1,1;1,2:1,1|0,2|1,1\" --known-values \"0,0,0;3,0,1;1,2,1\" - pred create BiconnectivityAugmentation --graph 0-1,1-2,2-3 --potential-weights 0-2:3,0-3:4,1-3:2 --budget 5 - pred create FVS --arcs \"0>1,1>2,2>0\" --weights 1,1,1 - pred create MinimumDummyActivitiesPert --arcs \"0>2,0>3,1>3,1>4,2>5\" --num-vertices 6 - pred create UndirectedTwoCommodityIntegralFlow --graph 0-2,1-2,2-3 --capacities 1,1,2 --source-1 0 --sink-1 3 --source-2 1 --sink-2 3 --requirement-1 1 --requirement-2 1 - pred create IntegralFlowHomologousArcs --arcs \"0>1,0>2,1>3,2>3,1>4,2>4,3>5,4>5\" --capacities 1,1,1,1,1,1,1,1 --source 0 --sink 5 --requirement 2 --homologous-pairs \"2=5;4=3\" - pred create X3C --universe 9 --subsets \"0,1,2;0,2,4;3,4,5;3,5,7;6,7,8;1,4,6;2,5,8\" - pred create SetBasis --universe 4 --subsets \"0,1;1,2;0,2;0,1,2\" --k 3 - pred create MinimumCardinalityKey --num-attributes 6 --dependencies \"0,1>2;0,2>3;1,3>4;2,4>5\" - pred create PrimeAttributeName --universe 6 --dependencies \"0,1>2,3,4,5;2,3>0,1,4,5\" --query-attribute 3 - pred create TwoDimensionalConsecutiveSets --alphabet-size 6 --subsets \"0,1,2;3,4,5;1,3;2,4;0,5\"")] -pub struct CreateArgs { - /// Problem type (e.g., MIS, QUBO, SAT). Omit when using --example. - #[arg(value_parser = crate::problem_name::ProblemNameParser)] - pub problem: Option, - /// Build a problem from the canonical example database using a structural problem spec. - #[arg(long, value_parser = crate::problem_name::ProblemNameParser)] - pub example: Option, - /// Target problem spec for canonical rule example lookup. - #[arg(long = "to", value_parser = crate::problem_name::ProblemNameParser)] - pub example_target: Option, - /// Which side of a rule example to emit [default: source]. - #[arg(long, value_enum, default_value = "source")] - pub example_side: ExampleSide, - /// Graph edge list (e.g., 0-1,1-2,2-3) - #[arg(long)] - pub graph: Option, - /// Vertex weights (e.g., 1,1,1,1) [default: all 1s] - #[arg(long)] - pub weights: Option, - /// Edge weights (e.g., 2,3,1) [default: all 1s] - #[arg(long)] - pub edge_weights: Option, - /// Edge lengths (e.g., 2,3,1) [default: all 1s] - #[arg(long)] - pub edge_lengths: Option, - /// Capacities (edge capacities for flow problems, capacity levels for CapacityAssignment) - #[arg(long)] - pub capacities: Option, - /// Demands for ProductionPlanning (comma-separated, e.g., "5,3,7,2,8,5") - #[arg(long)] - pub demands: Option, - /// Setup costs for ProductionPlanning (comma-separated, e.g., "10,10,10,10,10,10") - #[arg(long)] - pub setup_costs: Option, - /// Per-unit production costs for ProductionPlanning (comma-separated, e.g., "1,1,1,1,1,1") - #[arg(long)] - pub production_costs: Option, - /// Per-unit inventory costs for ProductionPlanning (comma-separated, e.g., "1,1,1,1,1,1") - #[arg(long)] - pub inventory_costs: Option, - /// Bundle capacities for IntegralFlowBundles (e.g., 1,1,1) - #[arg(long)] - pub bundle_capacities: Option, - /// Cost matrix for CapacityAssignment (semicolon-separated rows, e.g., "1,3,6;2,4,7") - #[arg(long)] - pub cost_matrix: Option, - /// Delay matrix for CapacityAssignment (semicolon-separated rows, e.g., "8,4,1;7,3,1") - #[arg(long)] - pub delay_matrix: Option, - /// Edge lower bounds for lower-bounded flow problems (e.g., 1,1,0,0,1,0,1) - #[arg(long)] - pub lower_bounds: Option, - /// Vertex multipliers in vertex order (e.g., 1,2,3,1) - #[arg(long)] - pub multipliers: Option, - /// Source vertex for path-based graph problems and MinimumCutIntoBoundedSets - #[arg(long)] - pub source: Option, - /// Sink vertex for path-based graph problems and MinimumCutIntoBoundedSets - #[arg(long)] - pub sink: Option, - /// Required total flow R for IntegralFlowBundles, IntegralFlowHomologousArcs, IntegralFlowWithMultipliers, PathConstrainedNetworkFlow, and UndirectedFlowLowerBounds - #[arg(long)] - pub requirement: Option, - /// Required number of paths for LengthBoundedDisjointPaths - #[arg(long)] - pub num_paths_required: Option, - /// Prescribed directed s-t paths as semicolon-separated arc-index sequences (e.g., "0,2,5;1,4,6") - #[arg(long)] - pub paths: Option, - /// Pairwise couplings J_ij for SpinGlass (e.g., 1,-1,1) [default: all 1s] - #[arg(long)] - pub couplings: Option, - /// On-site fields h_i for SpinGlass (e.g., 0,0,1) [default: all 0s] - #[arg(long)] - pub fields: Option, - /// Clauses for SAT problems (semicolon-separated, e.g., "1,2;-1,3") - #[arg(long)] - pub clauses: Option, - /// Disjuncts for NonTautology (semicolon-separated, e.g., "1,2;-1,3") - #[arg(long)] - pub disjuncts: Option, - /// Number of variables (for SAT/KSAT) - #[arg(long)] - pub num_vars: Option, - /// Matrix input. QUBO uses semicolon-separated numeric rows ("1,0.5;0.5,2"); - /// ConsecutiveBlockMinimization uses a JSON 2D bool array ('[[true,false],[false,true]]') - #[arg(long)] - pub matrix: Option, - /// Shared integer parameter (use `pred create ` for the problem-specific meaning) - #[arg(long)] - pub k: Option, - /// Number of partitions for GraphPartitioning (currently must be 2) - #[arg(long)] - pub num_partitions: Option, - /// Generate a random instance (graph-based problems only) - #[arg(long)] - pub random: bool, - /// Number of vertices for random graph generation - #[arg(long)] - pub num_vertices: Option, - /// Source vertex for path problems - #[arg(long)] - pub source_vertex: Option, - /// Target vertex for path problems - #[arg(long)] - pub target_vertex: Option, - /// Edge probability for random graph generation (0.0 to 1.0) [default: 0.5] - #[arg(long)] - pub edge_prob: Option, - /// Random seed for reproducibility - #[arg(long)] - pub seed: Option, - /// Target value (for Factoring, SubsetSum, and SubsetProduct) - #[arg(long)] - pub target: Option, - /// Bits for first factor (for Factoring); also accepted as a processor-count alias for scheduling create commands - #[arg(long)] - pub m: Option, - /// Bits for second factor (for Factoring) - #[arg(long)] - pub n: Option, - /// Vertex positions for geometry-based graphs (semicolon-separated x,y pairs, e.g., "0,0;1,0;1,1") - #[arg(long)] - pub positions: Option, - /// Radius for UnitDiskGraph [default: 1.0] - #[arg(long)] - pub radius: Option, - /// Source vertex s_1 for commodity 1 - #[arg(long)] - pub source_1: Option, - /// Sink vertex t_1 for commodity 1 - #[arg(long)] - pub sink_1: Option, - /// Source vertex s_2 for commodity 2 - #[arg(long)] - pub source_2: Option, - /// Sink vertex t_2 for commodity 2 - #[arg(long)] - pub sink_2: Option, - /// Required flow R_1 for commodity 1 - #[arg(long)] - pub requirement_1: Option, - /// Required flow R_2 for commodity 2 - #[arg(long)] - pub requirement_2: Option, - /// Item sizes for BinPacking (comma-separated, e.g., "3,3,2,2") - #[arg(long)] - pub sizes: Option, - /// Record access probabilities for ExpectedRetrievalCost (comma-separated, e.g., "0.2,0.15,0.15,0.2,0.1,0.2") - #[arg(long)] - pub probabilities: Option, - /// Link lengths for MinimumDiscretePlanarInverseKinematics (comma-separated positive reals, e.g., "2.0,1.0") - #[arg(long)] - pub link_lengths: Option, - /// Target point (x,y) for MinimumDiscretePlanarInverseKinematics (e.g., "2.0,1.0") - #[arg(long)] - pub target_point: Option, - /// Sampled absolute orientations per link for MinimumDiscretePlanarInverseKinematics (semicolon-separated angle lists, e.g., "0.0,1.5707963267948966;0.0,1.5707963267948966") - #[arg(long)] - pub orientation_samples: Option, - /// Admissible (a_{j-1}, a_j) pair sets per junction for MinimumDiscretePlanarInverseKinematics (pipe-separated junctions, each comma-separated "i-j" pairs, e.g., "0-0,0-1,1-1") - #[arg(long)] - pub allowed_pairs: Option, - /// Source labelled digraph G1 for MaximumCommonEdgeSubgraph. Format: ":,,..." with each arc "-