diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 94987bc1..3b3dce3e 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -72,7 +72,8 @@ enum Direction { Maximize, Minimize } ### Key Patterns - Problems parameterized by weight type `W` and graph type `G` - `ReductionResult` provides `target_problem()` and `extract_solution()` -- `Solver::find_best()` for optimization problems, `Solver::find_satisfying()` for `Metric = bool` +- `Solver::find_best()` → `Option>` for optimization problems; `Solver::find_satisfying()` → `Option>` for `Metric = bool` +- `BruteForce::find_all_best()` / `find_all_satisfying()` return `Vec>` for all optimal/satisfying solutions - Graph types: SimpleGraph, GridGraph, UnitDiskGraph, Hypergraph - Weight types: `Unweighted` (marker), `i32`, `f64` - Weight management via inherent methods (`weights()`, `set_weights()`, `is_weighted()`), not traits diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md index f8d13a30..3c313760 100644 --- a/.claude/rules/testing.md +++ b/.claude/rules/testing.md @@ -20,8 +20,10 @@ New code must have >95% test coverage. Run `make coverage` to check. Follow the reference files above for exact API usage. Summary: -- `solver.find_best(&problem)` — for optimization problems (`OptimizationProblem`, `Metric = SolutionSize`) -- `solver.find_satisfying(&problem)` — for satisfaction problems (`Metric = bool`) +- `solver.find_best(&problem)` → `Option>` — one optimal solution for optimization problems +- `solver.find_satisfying(&problem)` → `Option>` — one satisfying assignment +- `solver.find_all_best(&problem)` → `Vec>` — all optimal solutions (BruteForce only) +- `solver.find_all_satisfying(&problem)` → `Vec>` — all satisfying assignments (BruteForce only) - `problem.evaluate(&config)` — returns `SolutionSize::Valid(value)` / `SolutionSize::Invalid` for optimization, `bool` for satisfaction ## File Organization diff --git a/Makefile b/Makefile index b5d01e62..20dcef63 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Makefile for problemreductions -.PHONY: help build test fmt clippy doc mdbook paper examples clean coverage rust-export compare qubo-testdata export-schemas release run-plan +.PHONY: help build test fmt clippy doc mdbook paper examples clean coverage rust-export compare qubo-testdata export-schemas release run-plan diagrams # Default target help: @@ -11,6 +11,7 @@ help: @echo " fmt-check - Check code formatting" @echo " clippy - Run clippy lints" @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 (requires typst)" @echo " coverage - Generate coverage report (requires cargo-llvm-cov)" @@ -47,16 +48,26 @@ clippy: # Build mdBook documentation doc: cargo run --example export_graph - cp docs/paper/reduction_graph.json docs/src/reductions/ + cargo run --example export_schemas mdbook build docs RUSTDOCFLAGS="--default-theme=dark" cargo doc --all-features --no-deps rm -rf docs/book/api cp -r target/doc docs/book/api +# Generate SVG diagrams from Typst sources (light + dark themes) +TYPST_DIAGRAMS := $(wildcard docs/src/static/*.typ) +diagrams: + @for src in $(TYPST_DIAGRAMS); do \ + base=$$(basename $$src .typ); \ + echo "Compiling $$base..."; \ + typst compile $$src --input dark=false docs/src/static/$$base.svg; \ + typst compile $$src --input dark=true docs/src/static/$$base-dark.svg; \ + done + # Build and serve mdBook with API docs mdbook: cargo run --example export_graph - cp docs/paper/reduction_graph.json docs/src/reductions/ + cargo run --example export_schemas RUSTDOCFLAGS="--default-theme=dark" cargo doc --all-features --no-deps mdbook build rm -rf book/api @@ -84,7 +95,7 @@ export-schemas: paper: examples cargo run --example export_graph cargo run --example export_schemas - cd docs/paper && typst compile reductions.typ reductions.pdf + cd docs/paper && typst compile --root .. reductions.typ reductions.pdf # Generate coverage report (requires: cargo install cargo-llvm-cov) coverage: diff --git a/docs/paper/reduction_graph.json b/docs/paper/reduction_graph.json deleted file mode 100644 index eece8803..00000000 --- a/docs/paper/reduction_graph.json +++ /dev/null @@ -1,935 +0,0 @@ -{ - "nodes": [ - { - "name": "CircuitSAT", - "variant": {}, - "category": "satisfiability", - "doc_path": "models/specialized/struct.CircuitSAT.html" - }, - { - "name": "CircuitSAT", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "satisfiability", - "doc_path": "models/specialized/struct.CircuitSAT.html" - }, - { - "name": "CircuitSAT", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - }, - "category": "satisfiability", - "doc_path": "models/specialized/struct.CircuitSAT.html" - }, - { - "name": "Factoring", - "variant": {}, - "category": "specialized", - "doc_path": "models/specialized/struct.Factoring.html" - }, - { - "name": "Factoring", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "specialized", - "doc_path": "models/specialized/struct.Factoring.html" - }, - { - "name": "ILP", - "variant": {}, - "category": "optimization", - "doc_path": "models/optimization/struct.ILP.html" - }, - { - "name": "ILP", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "optimization", - "doc_path": "models/optimization/struct.ILP.html" - }, - { - "name": "KColoring", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.KColoring.html" - }, - { - "name": "KColoring", - "variant": { - "graph": "SimpleGraph", - "k": "N", - "weight": "i32" - }, - "category": "graph", - "doc_path": "models/graph/struct.KColoring.html" - }, - { - "name": "KColoring", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "graph", - "doc_path": "models/graph/struct.KColoring.html" - }, - { - "name": "KColoring", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - }, - "category": "graph", - "doc_path": "models/graph/struct.KColoring.html" - }, - { - "name": "KSatisfiability", - "variant": {}, - "category": "satisfiability", - "doc_path": "models/satisfiability/struct.KSatisfiability.html" - }, - { - "name": "KSatisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "satisfiability", - "doc_path": "models/satisfiability/struct.KSatisfiability.html" - }, - { - "name": "MaxCut", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MaxCut.html" - }, - { - "name": "MaxCut", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "graph", - "doc_path": "models/graph/struct.MaxCut.html" - }, - { - "name": "MaximumIndependentSet", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MaximumIndependentSet.html" - }, - { - "name": "MaximumIndependentSet", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "graph", - "doc_path": "models/graph/struct.MaximumIndependentSet.html" - }, - { - "name": "MaximumIndependentSet", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - }, - "category": "graph", - "doc_path": "models/graph/struct.MaximumIndependentSet.html" - }, - { - "name": "MaximumMatching", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MaximumMatching.html" - }, - { - "name": "MaximumMatching", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "graph", - "doc_path": "models/graph/struct.MaximumMatching.html" - }, - { - "name": "MaximumSetPacking", - "variant": {}, - "category": "set", - "doc_path": "models/set/struct.MaximumSetPacking.html" - }, - { - "name": "MaximumSetPacking", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "set", - "doc_path": "models/set/struct.MaximumSetPacking.html" - }, - { - "name": "MaximumSetPacking", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - }, - "category": "set", - "doc_path": "models/set/struct.MaximumSetPacking.html" - }, - { - "name": "MinimumDominatingSet", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MinimumDominatingSet.html" - }, - { - "name": "MinimumDominatingSet", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - }, - "category": "graph", - "doc_path": "models/graph/struct.MinimumDominatingSet.html" - }, - { - "name": "MinimumSetCovering", - "variant": {}, - "category": "set", - "doc_path": "models/set/struct.MinimumSetCovering.html" - }, - { - "name": "MinimumSetCovering", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "set", - "doc_path": "models/set/struct.MinimumSetCovering.html" - }, - { - "name": "MinimumVertexCover", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MinimumVertexCover.html" - }, - { - "name": "MinimumVertexCover", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "graph", - "doc_path": "models/graph/struct.MinimumVertexCover.html" - }, - { - "name": "MinimumVertexCover", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - }, - "category": "graph", - "doc_path": "models/graph/struct.MinimumVertexCover.html" - }, - { - "name": "QUBO", - "variant": {}, - "category": "optimization", - "doc_path": "models/optimization/struct.QUBO.html" - }, - { - "name": "QUBO", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - }, - "category": "optimization", - "doc_path": "models/optimization/struct.QUBO.html" - }, - { - "name": "Satisfiability", - "variant": {}, - "category": "satisfiability", - "doc_path": "models/satisfiability/struct.Satisfiability.html" - }, - { - "name": "Satisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "satisfiability", - "doc_path": "models/satisfiability/struct.Satisfiability.html" - }, - { - "name": "SpinGlass", - "variant": {}, - "category": "optimization", - "doc_path": "models/optimization/struct.SpinGlass.html" - }, - { - "name": "SpinGlass", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - }, - "category": "optimization", - "doc_path": "models/optimization/struct.SpinGlass.html" - }, - { - "name": "SpinGlass", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - }, - "category": "optimization", - "doc_path": "models/optimization/struct.SpinGlass.html" - } - ], - "edges": [ - { - "source": { - "name": "CircuitSAT", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "SpinGlass", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_spins", - "formula": "num_assignments" - }, - { - "field": "num_interactions", - "formula": "num_assignments" - } - ], - "doc_path": "rules/circuit_spinglass/index.html" - }, - { - "source": { - "name": "Factoring", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "CircuitSAT", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - } - }, - "overhead": [ - { - "field": "num_gates", - "formula": "num_bits_first * num_bits_second" - } - ], - "doc_path": "rules/factoring_circuit/index.html" - }, - { - "source": { - "name": "Factoring", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "ILP", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_vars", - "formula": "2 * num_bits_first + 2 * num_bits_second + num_bits_first * num_bits_second" - }, - { - "field": "num_constraints", - "formula": "3 * num_bits_first * num_bits_second + num_bits_first + num_bits_second + 1" - } - ], - "doc_path": "rules/factoring_ilp/index.html" - }, - { - "source": { - "name": "ILP", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "QUBO", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - } - }, - "overhead": [ - { - "field": "num_vars", - "formula": "num_vars" - } - ], - "doc_path": "rules/ilp_qubo/index.html" - }, - { - "source": { - "name": "KColoring", - "variant": { - "graph": "SimpleGraph", - "k": "N", - "weight": "i32" - } - }, - "target": { - "name": "ILP", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_vars", - "formula": "num_vertices * num_colors" - }, - { - "field": "num_constraints", - "formula": "num_vertices + num_edges * num_colors" - } - ], - "doc_path": "rules/coloring_ilp/index.html" - }, - { - "source": { - "name": "KColoring", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "QUBO", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - } - }, - "overhead": [ - { - "field": "num_vars", - "formula": "num_vertices * num_colors" - } - ], - "doc_path": "rules/coloring_qubo/index.html" - }, - { - "source": { - "name": "KSatisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "QUBO", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - } - }, - "overhead": [ - { - "field": "num_vars", - "formula": "num_vars + num_clauses" - } - ], - "doc_path": "rules/ksatisfiability_qubo/index.html" - }, - { - "source": { - "name": "KSatisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "Satisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_clauses", - "formula": "num_clauses" - }, - { - "field": "num_vars", - "formula": "num_vars" - } - ], - "doc_path": "rules/sat_ksat/index.html" - }, - { - "source": { - "name": "MaxCut", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "SpinGlass", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_spins", - "formula": "num_vertices" - }, - { - "field": "num_interactions", - "formula": "num_edges" - } - ], - "doc_path": "rules/spinglass_maxcut/index.html" - }, - { - "source": { - "name": "MaximumIndependentSet", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "MaximumSetPacking", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_sets", - "formula": "num_vertices" - }, - { - "field": "num_elements", - "formula": "num_vertices" - } - ], - "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" - }, - { - "source": { - "name": "MaximumIndependentSet", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "MinimumVertexCover", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_vertices", - "formula": "num_vertices" - }, - { - "field": "num_edges", - "formula": "num_edges" - } - ], - "doc_path": "rules/minimumvertexcover_maximumindependentset/index.html" - }, - { - "source": { - "name": "MaximumIndependentSet", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - } - }, - "target": { - "name": "QUBO", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - } - }, - "overhead": [ - { - "field": "num_vars", - "formula": "num_vertices" - } - ], - "doc_path": "rules/maximumindependentset_qubo/index.html" - }, - { - "source": { - "name": "MaximumMatching", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "MaximumSetPacking", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_sets", - "formula": "num_edges" - }, - { - "field": "num_elements", - "formula": "num_vertices" - } - ], - "doc_path": "rules/maximummatching_maximumsetpacking/index.html" - }, - { - "source": { - "name": "MaximumSetPacking", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "MaximumIndependentSet", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_vertices", - "formula": "num_sets" - }, - { - "field": "num_edges", - "formula": "num_sets" - } - ], - "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" - }, - { - "source": { - "name": "MaximumSetPacking", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - } - }, - "target": { - "name": "QUBO", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - } - }, - "overhead": [ - { - "field": "num_vars", - "formula": "num_sets" - } - ], - "doc_path": "rules/maximumsetpacking_qubo/index.html" - }, - { - "source": { - "name": "MinimumVertexCover", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "MaximumIndependentSet", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_vertices", - "formula": "num_vertices" - }, - { - "field": "num_edges", - "formula": "num_edges" - } - ], - "doc_path": "rules/minimumvertexcover_maximumindependentset/index.html" - }, - { - "source": { - "name": "MinimumVertexCover", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "MinimumSetCovering", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_sets", - "formula": "num_vertices" - }, - { - "field": "num_elements", - "formula": "num_edges" - } - ], - "doc_path": "rules/minimumvertexcover_minimumsetcovering/index.html" - }, - { - "source": { - "name": "MinimumVertexCover", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - } - }, - "target": { - "name": "QUBO", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - } - }, - "overhead": [ - { - "field": "num_vars", - "formula": "num_vertices" - } - ], - "doc_path": "rules/minimumvertexcover_qubo/index.html" - }, - { - "source": { - "name": "QUBO", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - } - }, - "target": { - "name": "SpinGlass", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - } - }, - "overhead": [ - { - "field": "num_spins", - "formula": "num_vars" - } - ], - "doc_path": "rules/spinglass_qubo/index.html" - }, - { - "source": { - "name": "Satisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "KColoring", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - } - }, - "overhead": [ - { - "field": "num_vertices", - "formula": "2 * num_vars + 5 * num_literals - 5 * num_clauses + 3" - }, - { - "field": "num_colors", - "formula": "3" - } - ], - "doc_path": "rules/sat_coloring/index.html" - }, - { - "source": { - "name": "Satisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "KSatisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_clauses", - "formula": "num_clauses + num_literals" - }, - { - "field": "num_vars", - "formula": "num_vars + num_literals" - } - ], - "doc_path": "rules/sat_ksat/index.html" - }, - { - "source": { - "name": "Satisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "MaximumIndependentSet", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - } - }, - "overhead": [ - { - "field": "num_vertices", - "formula": "num_literals" - }, - { - "field": "num_edges", - "formula": "num_literals^2" - } - ], - "doc_path": "rules/sat_maximumindependentset/index.html" - }, - { - "source": { - "name": "Satisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "MinimumDominatingSet", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - } - }, - "overhead": [ - { - "field": "num_vertices", - "formula": "3 * num_vars + num_clauses" - }, - { - "field": "num_edges", - "formula": "3 * num_vars + num_literals" - } - ], - "doc_path": "rules/sat_minimumdominatingset/index.html" - }, - { - "source": { - "name": "SpinGlass", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "target": { - "name": "MaxCut", - "variant": { - "graph": "SimpleGraph", - "weight": "Unweighted" - } - }, - "overhead": [ - { - "field": "num_vertices", - "formula": "num_spins" - }, - { - "field": "num_edges", - "formula": "num_interactions" - } - ], - "doc_path": "rules/spinglass_maxcut/index.html" - }, - { - "source": { - "name": "SpinGlass", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - } - }, - "target": { - "name": "QUBO", - "variant": { - "graph": "SimpleGraph", - "weight": "f64" - } - }, - "overhead": [ - { - "field": "num_vars", - "formula": "num_spins" - } - ], - "doc_path": "rules/spinglass_qubo/index.html" - } - ] -} \ No newline at end of file diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 9135f278..8dcd87be 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -1,5 +1,5 @@ // Problem Reductions: A Mathematical Reference -#let graph-data = json("reduction_graph.json") +#let graph-data = json("../src/reductions/reduction_graph.json") #import "@preview/cetz:0.4.2": canvas, draw #import "@preview/ctheorems:1.1.3": thmbox, thmplain, thmproof, thmrules @@ -21,7 +21,7 @@ // Load result JSON: { solutions: [{ source_config, target_config }, ...] } #let load-results(name) = json("examples/" + name + ".result.json") -#let problem-schemas = json("problem_schemas.json") +#let problem-schemas = json("../src/reductions/problem_schemas.json") // Problem display names for theorem headers #let display-name = ( diff --git a/docs/plans/2026-02-13-documentation-improvements-design.md b/docs/plans/2026-02-13-documentation-improvements-design.md new file mode 100644 index 00000000..814a4a8d --- /dev/null +++ b/docs/plans/2026-02-13-documentation-improvements-design.md @@ -0,0 +1,136 @@ +# Documentation Improvements Design + +## Goal + +Improve the "Getting Started" and "Architecture" sections of the mdBook documentation. + +- **Getting Started**: For all audiences (researchers, developers, students, contributors) +- **Architecture**: For contributors and developers + +## Getting Started + +### Current Problems + +- Jumps straight into code without explaining what the library does +- No high-level workflow overview +- Missing JSON resource documentation + +### Proposed Structure + +1. **What This Library Does** (~50 words) + - One paragraph explaining: "Reduce hard problems to solver-friendly forms" + - Link to Introduction page for the interactive reduction graph + +2. **Installation** (keep existing content) + +3. **The Reduction Workflow** + - Cetz diagram showing: `Problem A → reduce → Problem B → solve → extract → Solution for A` + - One complete code example walking through each step with comments + - Brief mention: chaining reductions works the same way + - Note: automated reduction path optimization for connected problems coming in the future + +4. **Solvers** (brief, with links) + - `BruteForce` — for small instances (<20 variables) + - `ILPSolver` — for larger instances (requires `ilp` feature) + - Link to API docs for details + +5. **JSON Resources** (new section) + - `reduction_graph.json` — all problems and reduction edges; useful for tooling, visualization, and research + - `problem_schemas.json` — field definitions for each problem type + - Location: `docs/src/reductions/` in the built docs, or generate via `cargo run --example export_graph` + +6. **Next Steps** + - Link to Architecture for internals + - Link to API Reference for full documentation + +## Architecture + +### Current Problems + +- Outdated trait references (`solution_size()` → `evaluate()`, `ConstraintSatisfactionProblem` removed) +- No visual diagram of module relationships +- Unclear entry points for contributors + +### Proposed Structure + +1. **Module Overview** (new) + - Cetz diagram showing module relationships: + ``` + models/ ←→ rules/ → registry/ + ↑ + solvers/ + ``` + - One sentence description per module + +2. **Trait Hierarchy** (updated) + - Cetz diagram showing trait relationships + - Updated to current API: + - `Problem`: `NAME`, `Metric`, `dims()`, `evaluate()`, `variant()`, `num_variables()` + - `OptimizationProblem`: `Value`, `direction()` + - Remove references to `ConstraintSatisfactionProblem` + +3. **Problems** (streamlined) + - Keep graph types table (SimpleGraph, GridGraph, UnitDiskGraph, HyperGraph) + - Keep variant ID explanation + - Update code examples to use `evaluate()` instead of `solution_size()` + +4. **Reductions** (streamlined) + - Keep reduce → solve → extract explanation + - Update `#[reduction]` macro example to current syntax + - Keep overhead tracking mention + +5. **Registry** (keep mostly as-is) + - JSON schema details are good + - Keep `reduction_graph.json` and `problem_schemas.json` schema examples + +6. **Solvers** (keep mostly as-is) + - Update trait signature if needed + +7. **Contributing** (new section, replaces scattered links) + + Priority order: + + a. **Recommended: Issue-based workflow** + - Open an issue using [Problem template](link) or [Rule template](link) + - Fill in all sections (definition, algorithm, size overhead, example instance) + - AI handles implementation automatically + + b. **Optional: Plan + automated PR** + - Use `superpowers:brainstorming` to create a detailed plan + - Create PR with `[action]` prefix in description to trigger automated implementation + + c. **Last resort: Manual implementation** + - See `adding-models.md` for adding problem types + - See `adding-reductions.md` for adding reduction rules + - See `testing.md` for test requirements + +## Diagrams + +Three separate Cetz diagrams in Typst, output to `docs/src/static/`: + +1. **`module-overview.typ`** → `module-overview.svg` + - Shows relationships between `src/models/`, `src/rules/`, `src/registry/`, `src/solvers/` + - Arrows showing data flow and dependencies + +2. **`trait-hierarchy.typ`** → `trait-hierarchy.svg` + - `Problem` trait with key methods + - `OptimizationProblem` extension + - Type parameters (`Metric`, `Value`) + +3. **`reduction-workflow.typ`** → `reduction-workflow.svg` + - Linear flow: Create Problem → Reduce → Solve Target → Extract Solution + - Shows the round-trip nature + +## Files to Modify + +- `docs/src/getting-started.md` — rewrite +- `docs/src/arch.md` — update +- `docs/src/static/module-overview.typ` — new +- `docs/src/static/trait-hierarchy.typ` — new +- `docs/src/static/reduction-workflow.typ` — new + +## Out of Scope + +- Introduction page (already has reduction graph) +- API reference (auto-generated) +- CLAUDE.md (separate concern) diff --git a/docs/plans/2026-02-13-documentation-improvements-impl.md b/docs/plans/2026-02-13-documentation-improvements-impl.md new file mode 100644 index 00000000..b0665f75 --- /dev/null +++ b/docs/plans/2026-02-13-documentation-improvements-impl.md @@ -0,0 +1,642 @@ +# Documentation Improvements Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Improve Getting Started and Architecture documentation with diagrams, updated API references, and clearer structure. + +**Architecture:** Three Typst diagrams (using fletcher) compiled to SVG, then referenced in markdown docs. Getting Started focuses on workflow; Architecture focuses on internals and contribution paths. + +**Tech Stack:** Typst with fletcher, mdBook markdown, SVG output + +--- + +## Task 1: Create Reduction Workflow Diagram + +**Files:** +- Create: `docs/src/static/reduction-workflow.typ` +- Output: `docs/src/static/reduction-workflow.svg`, `docs/src/static/reduction-workflow-dark.svg` + +**Step 1: Create the Typst diagram** + +Create `docs/src/static/reduction-workflow.typ`: + +```typst +#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge +#set page(width: auto, height: auto, margin: (top: 5pt, bottom: 5pt, left: 5pt, right: 5pt), fill: none) +#set text(font: "Noto Sans CJK SC") + +#let reduction-workflow(dark: false) = { + let (fg, box-fill) = if dark { + (rgb("#e2e8f0"), rgb("#1e293b")) + } else { + (rgb("#1e293b"), rgb("#f8fafc")) + } + + set text(fill: fg, size: 10pt) + + diagram( + node-stroke: 1.5pt + fg, + edge-stroke: 1.5pt, + spacing: (20mm, 10mm), + + let accent = rgb("#3b82f6"), + let success = rgb("#22c55e"), + + // Nodes + node((0, 0), box(width: 28mm, align(center)[*Problem A*\ #text(size: 8pt)[source problem]]), fill: box-fill, corner-radius: 6pt, inset: 10pt, name: ), + node((1, 0), box(width: 28mm, align(center)[*Problem B*\ #text(size: 8pt)[target problem]]), fill: box-fill, corner-radius: 6pt, inset: 10pt, name: ), + node((2, 0), box(width: 28mm, align(center)[*Solution B*\ #text(size: 8pt)[solver output]]), fill: box-fill, corner-radius: 6pt, inset: 10pt, name: ), + node((1, 1), box(width: 28mm, align(center)[*Solution A*\ #text(size: 8pt)[extracted result]]), fill: rgb("#dcfce7"), stroke: 1.5pt + success, corner-radius: 6pt, inset: 10pt, name: ), + + // Edges with labels + edge(, , "->", stroke: 1.5pt + accent, label: text(size: 9pt)[`reduce_to()`], label-pos: 0.5, label-side: center), + edge(, , "->", stroke: 1.5pt + accent, label: text(size: 9pt)[`find_best()`], label-pos: 0.5, label-side: center), + edge(, , "->", stroke: 1.5pt + success, label: text(size: 9pt)[`extract_solution()`], label-pos: 0.5, label-side: center), + ) +} + +#let standalone-dark = sys.inputs.at("dark", default: "false") == "true" +#reduction-workflow(dark: standalone-dark) +``` + +**Step 2: Compile to SVG (light and dark)** + +```bash +cd docs/src/static +typst compile reduction-workflow.typ --input dark=false reduction-workflow.svg +typst compile reduction-workflow.typ --input dark=true reduction-workflow-dark.svg +``` + +**Step 3: Verify output** + +```bash +ls -la docs/src/static/reduction-workflow*.svg +``` + +Expected: Two SVG files created + +**Step 4: Commit** + +```bash +git add docs/src/static/reduction-workflow.typ docs/src/static/reduction-workflow.svg docs/src/static/reduction-workflow-dark.svg +git commit -m "docs: add reduction workflow diagram" +``` + +--- + +## Task 2: Create Module Overview Diagram + +**Files:** +- Create: `docs/src/static/module-overview.typ` +- Output: `docs/src/static/module-overview.svg`, `docs/src/static/module-overview-dark.svg` + +**Step 1: Create the Typst diagram** + +Create `docs/src/static/module-overview.typ`: + +```typst +#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge +#set page(width: auto, height: auto, margin: (top: 5pt, bottom: 5pt, left: 5pt, right: 5pt), fill: none) +#set text(font: "Noto Sans CJK SC") + +#let module-overview(dark: false) = { + let (fg, box-fill) = if dark { + (rgb("#e2e8f0"), rgb("#1e293b")) + } else { + (rgb("#1e293b"), rgb("#f8fafc")) + } + + set text(fill: fg, size: 10pt) + + diagram( + node-stroke: 1.5pt + fg, + edge-stroke: 1.5pt, + spacing: (25mm, 15mm), + + let model-color = rgb("#c8f0c8"), + let rule-color = rgb("#c8c8f0"), + let registry-color = rgb("#f0f0a0"), + let solver-color = rgb("#f0c8c8"), + + // Module nodes + node((0, 0), box(width: 30mm, align(center)[*models/*\ #text(size: 8pt)[Problem types]]), fill: model-color, corner-radius: 6pt, inset: 10pt, name: ), + node((1, 0), box(width: 30mm, align(center)[*rules/*\ #text(size: 8pt)[Reductions]]), fill: rule-color, corner-radius: 6pt, inset: 10pt, name: ), + node((2, 0), box(width: 30mm, align(center)[*registry/*\ #text(size: 8pt)[Graph metadata]]), fill: registry-color, corner-radius: 6pt, inset: 10pt, name: ), + node((1, 1), box(width: 30mm, align(center)[*solvers/*\ #text(size: 8pt)[BruteForce, ILP]]), fill: solver-color, corner-radius: 6pt, inset: 10pt, name: ), + + // Relationships + edge(, , "<->", label: text(size: 8pt)[imports], label-side: center), + edge(, , "->", label: text(size: 8pt)[registers], label-side: center), + edge(, , "->", label: text(size: 8pt)[solves], label-side: center), + ) +} + +#let standalone-dark = sys.inputs.at("dark", default: "false") == "true" +#module-overview(dark: standalone-dark) +``` + +**Step 2: Compile to SVG** + +```bash +cd docs/src/static +typst compile module-overview.typ --input dark=false module-overview.svg +typst compile module-overview.typ --input dark=true module-overview-dark.svg +``` + +**Step 3: Verify output** + +```bash +ls -la docs/src/static/module-overview*.svg +``` + +**Step 4: Commit** + +```bash +git add docs/src/static/module-overview.typ docs/src/static/module-overview.svg docs/src/static/module-overview-dark.svg +git commit -m "docs: add module overview diagram" +``` + +--- + +## Task 3: Create Trait Hierarchy Diagram + +**Files:** +- Create: `docs/src/static/trait-hierarchy.typ` +- Output: `docs/src/static/trait-hierarchy.svg`, `docs/src/static/trait-hierarchy-dark.svg` + +**Step 1: Create the Typst diagram** + +Create `docs/src/static/trait-hierarchy.typ`: + +```typst +#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge +#set page(width: auto, height: auto, margin: (top: 5pt, bottom: 5pt, left: 5pt, right: 5pt), fill: none) +#set text(font: "Noto Sans CJK SC") + +#let trait-hierarchy(dark: false) = { + let (fg, box-fill) = if dark { + (rgb("#e2e8f0"), rgb("#1e293b")) + } else { + (rgb("#1e293b"), rgb("#f8fafc")) + } + + set text(fill: fg, size: 9pt) + + diagram( + node-stroke: 1.5pt + fg, + edge-stroke: 1.5pt, + spacing: (8mm, 12mm), + + let trait-fill = rgb("#e0e7ff"), + let type-fill = rgb("#fef3c7"), + + // Problem trait (main) + node((0, 0), box(width: 55mm, align(left)[ + *trait Problem*\ + #text(size: 8pt, fill: rgb("#6b7280"))[ + `const NAME: &str`\ + `type Metric: Clone`\ + `fn dims() -> Vec`\ + `fn evaluate(&config) -> Metric`\ + `fn variant() -> Vec<(&str, &str)>` + ] + ]), fill: trait-fill, corner-radius: 6pt, inset: 10pt, name: ), + + // OptimizationProblem trait + node((0, 1), box(width: 55mm, align(left)[ + *trait OptimizationProblem*\ + #text(size: 8pt, fill: rgb("#6b7280"))[ + `type Value: PartialOrd + Clone`\ + `fn direction() -> Direction`\ + #text(style: "italic")[requires `Metric = SolutionSize`] + ] + ]), fill: trait-fill, corner-radius: 6pt, inset: 10pt, name: ), + + // Type boxes on the right + node((1.3, 0), box(width: 38mm, align(left)[ + *SolutionSize\*\ + #text(size: 8pt, fill: rgb("#6b7280"))[`Valid(T) | Invalid`] + ]), fill: type-fill, corner-radius: 6pt, inset: 8pt, name: ), + + node((1.3, 1), box(width: 38mm, align(left)[ + *Direction*\ + #text(size: 8pt, fill: rgb("#6b7280"))[`Maximize | Minimize`] + ]), fill: type-fill, corner-radius: 6pt, inset: 8pt, name: ), + + // Inheritance arrow + edge(, , "->", stroke: 1.5pt + fg, label: text(size: 8pt)[extends], label-side: center), + + // Type associations (dashed) + edge(, , "-->", stroke: (paint: fg, dash: "dashed")), + edge(, , "-->", stroke: (paint: fg, dash: "dashed")), + ) +} + +#let standalone-dark = sys.inputs.at("dark", default: "false") == "true" +#trait-hierarchy(dark: standalone-dark) +``` + +**Step 2: Compile to SVG** + +```bash +cd docs/src/static +typst compile trait-hierarchy.typ --input dark=false trait-hierarchy.svg +typst compile trait-hierarchy.typ --input dark=true trait-hierarchy-dark.svg +``` + +**Step 3: Verify output** + +```bash +ls -la docs/src/static/trait-hierarchy*.svg +``` + +**Step 4: Commit** + +```bash +git add docs/src/static/trait-hierarchy.typ docs/src/static/trait-hierarchy.svg docs/src/static/trait-hierarchy-dark.svg +git commit -m "docs: add trait hierarchy diagram" +``` + +--- + +## Task 4: Rewrite Getting Started + +**Files:** +- Modify: `docs/src/getting-started.md` + +**Step 1: Rewrite the file** + +Replace contents of `docs/src/getting-started.md` with: + +```markdown +# Getting Started + +## What This Library Does + +**problemreductions** transforms hard computational problems into forms that efficient solvers can handle. You define a problem, reduce it to another problem type (like QUBO or ILP), solve the reduced problem, and extract the solution back. The [interactive reduction graph](./introduction.html) shows all available problem types and transformations. + +## Installation + +Add to your `Cargo.toml`: + +```toml +[dependencies] +problemreductions = "0.1" +``` + +## The Reduction Workflow + +The core workflow is: **create** a problem, **reduce** it to a target, **solve** the target, and **extract** the solution back. + +
+ +![Reduction Workflow](static/reduction-workflow.svg) + +
+
+ +![Reduction Workflow](static/reduction-workflow-dark.svg) + +
+ +### Complete Example + +```rust +use problemreductions::prelude::*; + +// 1. Create: Independent Set on a path graph (4 vertices) +let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + +// 2. Reduce: Transform to Minimum Vertex Cover +let reduction = ReduceTo::>::reduce_to(&problem); +let target = reduction.target_problem(); + +// 3. Solve: Find optimal solution to the target problem +let solver = BruteForce::new(); +let target_solutions = solver.find_best(target); + +// 4. Extract: Map solution back to original problem +let solution = reduction.extract_solution(&target_solutions[0]); + +// Verify: solution is valid for the original problem +let metric = problem.evaluate(&solution); +assert!(metric.is_valid()); +``` + +### Chaining Reductions + +Reductions can be chained. Each step preserves the solution mapping: + +```rust +use problemreductions::prelude::*; + +// SetPacking -> IndependentSet -> VertexCover +let sp = MaximumSetPacking::::new(vec![vec![0, 1], vec![1, 2], vec![2, 3]]); + +let r1 = ReduceTo::>::reduce_to(&sp); +let r2 = ReduceTo::>::reduce_to(r1.target_problem()); + +// Solve final target, extract back through chain +let solver = BruteForce::new(); +let vc_sol = solver.find_best(r2.target_problem()); +let is_sol = r2.extract_solution(&vc_sol[0]); +let sp_sol = r1.extract_solution(&is_sol); +``` + +## Solvers + +Two 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 | Requires `ilp` feature flag | + +Enable ILP support: + +```toml +[dependencies] +problemreductions = { version = "0.1", features = ["ilp"] } +``` + +**Future:** Automated reduction path optimization will find the best route between any two connected problems. + +## JSON Resources + +The library exports machine-readable metadata useful for tooling and research: + +| File | Contents | Use Case | +|------|----------|----------| +| [`reduction_graph.json`](reductions/reduction_graph.json) | All problem variants and reduction edges | Visualization, path finding, research | +| [`problem_schemas.json`](reductions/problem_schemas.json) | Field definitions for each problem type | Code generation, validation | + +Generate locally: + +```bash +cargo run --example export_graph # reduction_graph.json +cargo run --example export_schemas # problem_schemas.json +``` + +## Next Steps + +- Explore the [interactive reduction graph](./introduction.html) to discover available reductions +- Read the [Architecture](./arch.md) guide for implementation details +- Browse the [API Reference](./api.html) for full documentation +``` + +**Step 2: Verify markdown renders** + +```bash +cd docs && mdbook build && echo "Build successful" +``` + +**Step 3: Commit** + +```bash +git add docs/src/getting-started.md +git commit -m "docs: rewrite Getting Started with workflow focus" +``` + +--- + +## Task 5: Update Architecture - Module Overview Section + +**Files:** +- Modify: `docs/src/arch.md` (partial update) + +**Step 1: Read current file to get line numbers** + +```bash +head -60 docs/src/arch.md +``` + +**Step 2: Replace the beginning of arch.md (lines 1-58) with new Module Overview** + +Replace the beginning of `docs/src/arch.md` up through the Problems section header with: + +```markdown +# Architecture + +This guide covers the library internals for contributors and developers extending the library. + +## Module Overview + +
+ +![Module Overview](static/module-overview.svg) + +
+
+ +![Module Overview](static/module-overview-dark.svg) + +
+ +| Module | Purpose | +|--------|---------| +| `src/models/` | Problem type implementations (SAT, Graph, Set, Optimization) | +| `src/rules/` | Reduction rules with `ReduceTo` implementations | +| `src/registry/` | Compile-time reduction graph metadata | +| `src/solvers/` | BruteForce and ILP solvers | +| `src/traits.rs` | Core `Problem` and `OptimizationProblem` traits | +| `src/types.rs` | Shared types (`SolutionSize`, `Direction`, `ProblemSize`) | + +## Trait Hierarchy + +
+ +![Trait Hierarchy](static/trait-hierarchy.svg) + +
+
+ +![Trait Hierarchy](static/trait-hierarchy-dark.svg) + +
+ +Every problem implements `Problem`. Optimization problems additionally implement `OptimizationProblem`. + +```rust +pub trait Problem: Clone { + const NAME: &'static str; // e.g., "MaximumIndependentSet" + type Metric: Clone; // SolutionSize or bool + fn dims(&self) -> Vec; // config space: [2, 2, 2] for 3 binary vars + fn evaluate(&self, config: &[usize]) -> Self::Metric; + fn variant() -> Vec<(&'static str, &'static str)>; +} + +pub trait OptimizationProblem: Problem> { + type Value: PartialOrd + Clone; // i32, f64, etc. + fn direction(&self) -> Direction; // Maximize or Minimize +} +``` + +**Key types:** +- `SolutionSize`: `Valid(T)` for feasible solutions, `Invalid` for constraint violations +- `Direction`: `Maximize` or `Minimize` + +## Problems +``` + +**Step 3: Commit partial update** + +```bash +git add docs/src/arch.md +git commit -m "docs: update Architecture with module overview and trait hierarchy" +``` + +--- + +## Task 6: Update Architecture - Problems and Rules Sections + +**Files:** +- Modify: `docs/src/arch.md` (continue update) + +**Step 1: Update the Problems section** + +Find and update the Problems section to fix outdated API references. Replace `solution_size(&config)` with `evaluate(&config)`: + +Old: +```rust +let config = vec![1, 0, 1, 0]; +let result = problem.solution_size(&config); +// result.is_valid: bool +// result.size: objective value +``` + +New: +```rust +let config = vec![1, 0, 1, 0]; +let result = problem.evaluate(&config); +// result.is_valid() -> bool +// result.size() -> Option<&T> +``` + +**Step 2: Update the Rules section** + +Replace the outdated reduction example: + +Old: +```rust +let reduction = problem.reduce_to::>(); +``` + +New: +```rust +let reduction = ReduceTo::>::reduce_to(&problem); +``` + +**Step 3: Remove reference to ConstraintSatisfactionProblem** + +Delete the line: "For problems with explicit constraints, also implement `ConstraintSatisfactionProblem`." + +**Step 4: Commit** + +```bash +git add docs/src/arch.md +git commit -m "docs: fix outdated API references in Architecture" +``` + +--- + +## Task 7: Add Contributing Section to Architecture + +**Files:** +- Modify: `docs/src/arch.md` (append) + +**Step 1: Add Contributing section at the end of arch.md** + +Append to `docs/src/arch.md`: + +```markdown + +## Contributing + +### Recommended: Issue-Based Workflow + +The easiest way to contribute is through GitHub issues: + +1. **Open an issue** using the [Problem](https://github.com/CodingThrust/problem-reductions/issues/new?template=problem.md) or [Rule](https://github.com/CodingThrust/problem-reductions/issues/new?template=rule.md) template +2. **Fill in all sections** — definition, algorithm, size overhead, example instance +3. **AI handles implementation** — automated tools generate the code from your specification + +### Optional: Plan + Automated PR + +For more control over the implementation: + +1. Use `superpowers:brainstorming` to create a detailed plan +2. Create a PR with `[action]` prefix in the description +3. Automated implementation is triggered from your plan + +### Manual Implementation + +When automation isn't suitable: + +- **Adding a problem:** See [adding-models.md](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/rules/adding-models.md) +- **Adding a reduction:** See [adding-reductions.md](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/rules/adding-reductions.md) +- **Testing requirements:** See [testing.md](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/rules/testing.md) + +Run `make test clippy` before submitting PRs. +``` + +**Step 2: Commit** + +```bash +git add docs/src/arch.md +git commit -m "docs: add Contributing section to Architecture" +``` + +--- + +## Task 8: Build and Verify Documentation + +**Files:** +- None (verification only) + +**Step 1: Build the mdBook** + +```bash +make doc +``` + +**Step 2: Verify diagrams are included** + +```bash +ls -la docs/book/static/*.svg | grep -E "(reduction-workflow|module-overview|trait-hierarchy)" +``` + +Expected: All 6 SVG files present (light + dark for each) + +**Step 3: Open locally and visual check** + +```bash +open docs/book/getting-started.html +open docs/book/arch.html +``` + +Verify: +- Diagrams render correctly +- Light/dark theme switching works +- Links work + +**Step 4: Final commit if any fixes needed** + +```bash +git status +# If clean, done. If changes needed, fix and commit. +``` + +--- + +## Summary + +| Task | Description | +|------|-------------| +| 1 | Create reduction workflow diagram (Typst → SVG) | +| 2 | Create module overview diagram | +| 3 | Create trait hierarchy diagram | +| 4 | Rewrite Getting Started | +| 5 | Update Architecture - Module Overview | +| 6 | Update Architecture - Fix outdated API | +| 7 | Add Contributing section | +| 8 | Build and verify | diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index d332d953..5e018c80 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -10,4 +10,3 @@ # Developer Guide - [API Reference](./api.md) -- [CLAUDE.md](./claude.md) diff --git a/docs/src/arch.md b/docs/src/arch.md index 4d0593ea..b7bfcc6f 100644 --- a/docs/src/arch.md +++ b/docs/src/arch.md @@ -1,105 +1,102 @@ # Architecture -## Problems +This guide covers the library internals for contributors and developers. See [Getting Started](./getting-started.md) for usage examples. -Every computational problem implements the `Problem` trait. A problem defines: +## Module Overview -- **Variables** — the unknowns to be solved (e.g., vertex assignments, boolean values) -- **Flavors** — possible values each variable can take (usually 2 for binary problems) -- **Solution size** — the objective value for a given configuration +
-Each problem type has its own parameters. For example: +![Module Overview](static/module-overview.svg) -- `MaximumIndependentSet` — parameterized by graph type `G` and weight type `W` -- `Satisfiability` — CNF formula with optional clause weights -- `QUBO` — parameterized by weight type only +
+
-Graph-based problems support multiple topologies: +![Module Overview](static/module-overview-dark.svg) -| Graph Type | Description | -|------------|-------------| -| `SimpleGraph` | Standard adjacency-based graph | -| `GridGraph` | Vertices on a regular grid | -| `UnitDiskGraph` | Edges connect vertices within a distance threshold | -| `HyperGraph` | Edges connecting any number of vertices | +
-Problem variants appear as separate nodes in the reduction graph when they have distinct reductions: +| Module | Purpose | +|--------|---------| +| [`src/models/`](#models) | Problem type implementations (SAT, Graph, Set, Optimization) | +| [`src/rules/`](#rules) | Reduction rules with `ReduceTo` implementations | +| [`src/registry/`](#registry) | Compile-time reduction graph metadata | +| [`src/solvers/`](#solvers) | BruteForce and ILP solvers | +| `src/traits.rs` | Core `Problem` and `OptimizationProblem` traits (see [Models](#models)) | +| `src/types.rs` | Shared types: `SolutionSize`, `Direction`, `ProblemSize` (see [Models](#models)) | -``` -MaximumIndependentSet # base variant -MaximumIndependentSet/GridGraph # different graph topology -MaximumIndependentSet/Weighted # weighted objective -``` +## Models -Evaluating a configuration returns both validity and objective value: +Every problem implements `Problem`. Optimization problems additionally implement `OptimizationProblem`. -```rust -let config = vec![1, 0, 1, 0]; // Variable assignments -let result = problem.solution_size(&config); -// result.is_valid: bool -// result.size: objective value -``` +
-### Implementation +![Trait Hierarchy](static/trait-hierarchy.svg) -Implement the `Problem` trait. Key methods: +
+
-| Method | Purpose | -|--------|---------| -| `NAME` | Problem identifier (e.g., `"MaximumIndependentSet"`) | -| `variant()` | Key-value pairs identifying this variant | -| `num_variables()` | Number of unknowns | -| `num_flavors()` | Values per variable (usually 2) | -| `solution_size()` | Evaluate a configuration | +![Trait Hierarchy](static/trait-hierarchy-dark.svg) -For problems with explicit constraints, also implement `ConstraintSatisfactionProblem`. +
-See [Adding Models](claude.md) for the full guide. +```rust +pub trait Problem: Clone { + const NAME: &'static str; // e.g., "MaximumIndependentSet" + type Metric: Clone; // SolutionSize or bool + fn dims(&self) -> Vec; // config space: [2, 2, 2] for 3 binary vars + fn evaluate(&self, config: &[usize]) -> Self::Metric; + fn variant() -> Vec<(&'static str, &'static str)>; +} -## Rules +pub trait OptimizationProblem: Problem> { + type Value: PartialOrd + Clone; // i32, f64, etc. + fn direction(&self) -> Direction; // Maximize or Minimize +} +``` -A **reduction** transforms one problem into another while preserving solutions. Given a source problem A and target problem B: +**Key types:** +- `SolutionSize`: `Valid(T)` for feasible solutions, `Invalid` for constraint violations +- `Direction`: `Maximize` or `Minimize` -1. **Reduce** — convert A to B -2. **Solve** — find solution to B -3. **Extract** — map B's solution back to A +Problems are parameterized by graph type and weight type: -```rust -// Reduce: MaximumIndependentSet → QUBO -let reduction = problem.reduce_to::>(); -let qubo = reduction.target_problem(); +- `MaximumIndependentSet` — graph type `G`, weight type `W` +- `Satisfiability` — CNF formula (concrete type, no parameters) +- `QUBO` — parameterized by weight type only -// Solve the target -let qubo_solution = solver.find_best(qubo); +**Graph types:** -// Extract back to source -let original_solution = reduction.extract_solution(&qubo_solution[0]); -``` +| Type | Description | +|------|-------------| +| `SimpleGraph` | Standard adjacency-based graph | +| `GridGraph` | Vertices on a regular grid | +| `UnitDiskGraph` | Edges connect vertices within a distance threshold | +| `HyperGraph` | Edges connecting any number of vertices | -Reductions track size overhead for complexity analysis: +**Variant IDs** in the reduction graph follow `ProblemName[/GraphType][/Weighted]`: -```rust -let source_size = reduction.source_size(); // ProblemSize -let target_size = reduction.target_size(); // ProblemSize +``` +MaximumIndependentSet # base variant (SimpleGraph, unweighted) +MaximumIndependentSet/GridGraph # different graph topology +MaximumIndependentSet/Weighted # weighted objective ``` -The reduction graph shows all available transformations: +All problem types support JSON serialization via serde: -``` -Satisfiability ──→ MaximumIndependentSet ──→ QUBO - │ - ▼ - MinimumVertexCover +```rust +use problemreductions::io::{to_json, from_json}; + +let json = to_json(&problem)?; +let restored: MaximumIndependentSet = from_json(&json)?; ``` -Not all reductions preserve optimality — some only preserve satisfiability. The graph encodes this metadata. +See [adding-models.md](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/rules/adding-models.md) for the full implementation guide. -### Implementation +## Rules A reduction requires two pieces: -1. **Result struct** — holds the target problem and extraction logic -2. **`ReduceTo` impl** — performs the reduction +**1. Result struct** — holds the target problem and extraction logic: ```rust #[derive(Clone)] @@ -114,113 +111,26 @@ impl ReductionResult for ReductionAToB { fn target_problem(&self) -> &B { &self.target } fn extract_solution(&self, target_sol: &[usize]) -> Vec { /* ... */ } - fn source_size(&self) -> ProblemSize { /* ... */ } - fn target_size(&self) -> ProblemSize { /* ... */ } } ``` -Use the `#[reduction]` macro to register in the global inventory: +**2. `ReduceTo` impl** with the `#[reduction]` macro: ```rust #[reduction(A -> B)] impl ReduceTo for A { type Result = ReductionAToB; - fn reduce_to(&self) -> Self::Result { /* ... */ } } ``` -The macro generates `inventory::submit!` calls, making the reduction discoverable at compile time for the reduction graph. +The macro generates `inventory::submit!` calls for compile-time reduction graph registration. -See [Adding Reductions](claude.md) for the full guide. +See [adding-reductions.md](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/rules/adding-reductions.md) for the full implementation guide. ## Registry -The **reduction graph** is a directed graph where: - -- **Nodes** — problem variants (e.g., `MaximumIndependentSet/GridGraph`) -- **Edges** — available reductions between variants - -Variant IDs follow the pattern `ProblemName[/GraphType][/Weighted]`: - -| Variant ID | Meaning | -|------------|---------| -| `MaximumIndependentSet` | Base variant (SimpleGraph, unweighted) | -| `MaximumIndependentSet/GridGraph` | GridGraph topology | -| `MaximumIndependentSet/Weighted` | Weighted objective | -| `MaximumIndependentSet/GridGraph/Weighted` | Both | - -The graph data is stored in [`reduction_graph.json`](reductions/reduction_graph.json): - -
-Schema - -```json -{ - "nodes": [ - { - "name": "Satisfiability", - "variant": {}, - "category": "satisfiability", - "doc_path": "..." - }, - { - "name": "MaximumIndependentSet", - "variant": {"graph": "GridGraph"}, - "category": "graph", - "doc_path": "..." - } - ], - "edges": [ - { - "source": {"name": "Satisfiability", "variant": {}}, - "target": {"name": "MaximumIndependentSet", "variant": {}} - } - ] -} -``` - -
- -Problem schemas (`problem_schemas.json`) describe each problem's structure: - -
-problem_schemas.json schema - -```json -[ - { - "name": "Satisfiability", - "category": "satisfiability", - "description": "Find satisfying assignment for CNF formula", - "fields": [ - { - "name": "num_vars", - "type_name": "usize", - "description": "Number of Boolean variables" - }, - { - "name": "clauses", - "type_name": "Vec", - "description": "Clauses in conjunctive normal form" - }, - { - "name": "weights", - "type_name": "Vec", - "description": "Clause weights for MAX-SAT" - } - ] - } -] -``` - -
- -Use the interactive diagram in the [mdBook documentation](https://codingthrust.github.io/problem-reductions/) to explore available reductions. - -### Implementation - -Reductions are collected at compile time using the `inventory` crate. The `#[reduction]` macro registers metadata: +The reduction graph is built at compile time using the `inventory` crate: ```rust #[reduction(A -> B)] @@ -230,83 +140,38 @@ impl ReduceTo for A { /* ... */ } // inventory::submit! { ReductionMeta { source: "A", target: "B", ... } } ``` -To regenerate the exports after adding rules or problems: +**JSON exports:** +- [reduction_graph.json](reductions/reduction_graph.json) — all problem variants and reduction edges +- [problem_schemas.json](reductions/problem_schemas.json) — field definitions for each problem type + +Regenerate exports: ```bash -cargo run --example export_graph # writes docs/paper/reduction_graph.json -cargo run --example export_schemas # writes docs/paper/problem_schemas.json +cargo run --example export_graph # docs/src/reductions/reduction_graph.json +cargo run --example export_schemas # docs/src/reductions/problem_schemas.json ``` ## Solvers -Solvers find optimal solutions to problems. The library provides: - -| Solver | Description | Use case | -|--------|-------------|----------| -| `BruteForce` | Enumerates all configurations | Small instances (< 20 variables) | -| `ILPSolver` | Integer Linear Programming (HiGHS) | Larger instances, requires `ilp` feature | - -All solvers implement the `Solver` trait: - -```rust -let solver = BruteForce::new(); -let solutions = solver.find_best(&problem); // Best solution(s) -let with_size = solver.find_best_with_size(&problem); // With objective values -``` - -Solvers work with reductions — solve the target problem, then extract: - -```rust -let reduction = problem.reduce_to::>(); -let qubo_solutions = solver.find_best(reduction.target_problem()); -let original = reduction.extract_solution(&qubo_solutions[0]); -``` - -### Implementation - -The `Solver` trait: +Solvers implement the `Solver` trait: ```rust pub trait Solver { - fn find_best(&self, problem: &P) -> Vec>; - fn find_best_with_size(&self, problem: &P) - -> Vec<(Vec, SolutionSize)>; + fn find_best(&self, problem: &P) -> Option>; + fn find_satisfying>(&self, problem: &P) -> Option>; } ``` `ILPSolver` additionally provides `solve_reduced()` for problems implementing `ReduceTo`. -Enable with: +## Contributing -```toml -[dependencies] -problemreductions = { version = "0.1", features = ["ilp"] } -``` - -## File I/O - -All problem types support JSON serialization for persistence and interoperability. - -```rust -use problemreductions::io::{write_problem, read_problem, FileFormat}; +See [Call for Contributions](./introduction.md#call-for-contributions) for the recommended issue-based workflow (no coding required). -// Write -write_problem(&problem, "problem.json", FileFormat::Json)?; +For manual implementation: -// Read -let problem: MaximumIndependentSet = read_problem("problem.json", FileFormat::Json)?; -``` - -String serialization: - -```rust -use problemreductions::io::{to_json, from_json}; - -let json = to_json(&problem)?; -let restored: MaximumIndependentSet = from_json(&json)?; -``` +- **Adding a problem:** See [adding-models.md](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/rules/adding-models.md) +- **Adding a reduction:** See [adding-reductions.md](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/rules/adding-reductions.md) +- **Testing requirements:** See [testing.md](https://github.com/CodingThrust/problem-reductions/blob/main/.claude/rules/testing.md) -| Format | Description | -|--------|-------------| -| `Json` | Pretty-printed | -| `JsonCompact` | No whitespace | +Run `make test clippy` before submitting PRs. diff --git a/docs/src/claude.md b/docs/src/claude.md deleted file mode 100644 index 5ec03224..00000000 --- a/docs/src/claude.md +++ /dev/null @@ -1,99 +0,0 @@ -# CLAUDE.md - -## Project Overview -Rust library for NP-hard problem reductions. Implements computational problems with reduction rules for transforming between equivalent formulations. - -## Commands -```bash -make help # Show all available targets -make build # Build the project -make test # Run all tests -make fmt # Format code with rustfmt -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 (runs examples + exports first) -make coverage # Generate coverage report (>95% required) -make check # Quick pre-commit check (fmt + clippy + test) -make export-graph # Regenerate reduction graph JSON -make export-schemas # Regenerate problem schemas JSON -make qubo-testdata # Regenerate QUBO ground truth JSON -make clean # Clean build artifacts -``` - -## Verify Changes -```bash -make test clippy export-graph # Must pass before PR -``` - -## Architecture - -### Core Modules -- `src/models/` - Problem implementations (SAT, Graph, Set, Optimization) -- `src/rules/` - Reduction rules + inventory registration -- `src/solvers/` - BruteForce solver, ILP solver (feature-gated) -- `src/traits.rs` - `Problem`, `ConstraintSatisfactionProblem` traits -- `src/rules/traits.rs` - `ReduceTo`, `ReductionResult` traits -- `src/registry/` - Compile-time reduction metadata collection -- `src/unit_tests/` - Unit test files (mirroring `src/` structure, referenced via `#[path]`) -- `tests/main.rs` - Integration tests (modules in `tests/suites/`) -- `tests/data/` - Ground truth JSON for integration tests -- `scripts/` - Python test data generation scripts (managed with `uv`) -- `docs/plans/` - Implementation plans - -### Trait Hierarchy - -``` -Problem (core trait - all problems must implement) -│ -├── const NAME: &'static str // Problem name, e.g., "MaximumIndependentSet" -├── type GraphType: GraphMarker // Graph topology marker -├── type Weight: NumericWeight // Weight type (i32, f64, Unweighted) -├── type Size // Objective value type -│ -├── fn num_variables(&self) -> usize -├── fn num_flavors(&self) -> usize // Usually 2 for binary problems -├── fn problem_size(&self) -> ProblemSize -├── fn energy_mode(&self) -> EnergyMode -├── fn solution_size(&self, config) -> SolutionSize -└── ... (default methods: variables, flavors, is_valid_config) - -ConstraintSatisfactionProblem : Problem (extension for CSPs) -│ -├── fn constraints(&self) -> Vec -├── fn objectives(&self) -> Vec -├── fn weights(&self) -> Vec -├── fn set_weights(&mut self, weights) -├── fn is_weighted(&self) -> bool -└── ... (default methods: is_satisfied, compute_objective) -``` - -### Key Patterns -- Problems parameterized by weight type `W` and graph type `G` -- `ReductionResult` provides `target_problem()` and `extract_solution()` -- Graph types: SimpleGraph, GridGraph, UnitDiskGraph, Hypergraph -- Weight types: `Unweighted` (marker), `i32`, `f64` - -### Problem Variant IDs -Reduction graph nodes use variant IDs: `ProblemName[/GraphType][/Weighted]` -- Base: `MaximumIndependentSet` (SimpleGraph, unweighted) -- Graph variant: `MaximumIndependentSet/GridGraph` -- Weighted variant: `MaximumIndependentSet/Weighted` -- Both: `MaximumIndependentSet/GridGraph/Weighted` - -## Conventions - -### File Naming -- Reduction files: `src/rules/_.rs` -- Model files: `src/models//.rs` -- Test naming: `test__to__closed_loop` - -## Contributing -See `.claude/rules/` for detailed guides: -- `adding-reductions.md` - How to add reduction rules -- `adding-models.md` - How to add problem types -- `testing.md` - Testing requirements and patterns -- `documentation.md` - Paper documentation patterns - -Also see GitHub Issue #3 for coding rules. diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index f8731b3b..db15eed9 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -1,5 +1,9 @@ # Getting Started +## What This Library Does + +**problemreductions** transforms hard computational problems into forms that efficient solvers can handle. You define a problem, reduce it to another problem type (like QUBO or ILP), solve the reduced problem, and extract the solution back. The [interactive reduction graph](./introduction.html) shows all available problem types and transformations. + ## Installation Add to your `Cargo.toml`: @@ -9,97 +13,95 @@ Add to your `Cargo.toml`: problemreductions = "0.1" ``` -## Basic Usage +## The Reduction Workflow -### Creating a Problem +The core workflow is: **create** a problem, **reduce** it to a target, **solve** the target, and **extract** the solution back. -```rust -use problemreductions::prelude::*; +
-// Independent Set on a path graph -let is = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); +![Reduction Workflow](static/reduction-workflow.svg) -// Vertex Cover on the same graph -let vc = MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3)]); +
+
-// QUBO problem -let qubo = QUBO::from_matrix(vec![ - vec![1.0, -2.0], - vec![0.0, 1.0], -]); -``` +![Reduction Workflow](static/reduction-workflow-dark.svg) + +
-### Solving a Problem +### Complete Example ```rust use problemreductions::prelude::*; +use problemreductions::topology::SimpleGraph; -let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); +// 1. Create: Independent Set on a path graph (4 vertices) +let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + +// 2. Reduce: Transform to Minimum Vertex Cover +let reduction = ReduceTo::>::reduce_to(&problem); +let target = reduction.target_problem(); + +// 3. Solve: Find optimal solution to the target problem let solver = BruteForce::new(); -let solutions = solver.find_best(&problem); +let target_solution = solver.find_best(target).unwrap(); + +// 4. Extract: Map solution back to original problem +let solution = reduction.extract_solution(&target_solution); -println!("Found {} optimal solutions", solutions.len()); -for sol in &solutions { - println!(" Solution: {:?}", sol); -} +// Verify: solution is valid for the original problem +let metric = problem.evaluate(&solution); +assert!(metric.is_valid()); ``` -### Applying Reductions +### Chaining Reductions + +Reductions can be chained. Each step preserves the solution mapping: ```rust use problemreductions::prelude::*; +use problemreductions::topology::SimpleGraph; -// Create an Independent Set problem -let is = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2)]); +// SetPacking -> IndependentSet -> VertexCover +let sp = MaximumSetPacking::::new(vec![vec![0, 1], vec![1, 2], vec![2, 3]]); -// Reduce to Vertex Cover -let result = ReduceTo::>::reduce_to(&is); -let vc = result.target_problem(); +let r1 = ReduceTo::>::reduce_to(&sp); +let r2 = ReduceTo::>::reduce_to(r1.target_problem()); -// Solve the reduced problem +// Solve final target, extract back through chain let solver = BruteForce::new(); -let vc_solutions = solver.find_best(vc); - -// Extract solution back to original problem -let is_solution = result.extract_solution(&vc_solutions[0]); +let vc_sol = solver.find_best(r2.target_problem()).unwrap(); +let is_sol = r2.extract_solution(&vc_sol); +let sp_sol = r1.extract_solution(&is_sol); ``` -### Chaining Reductions - -```rust -use problemreductions::prelude::*; +## Solvers -let sp = MaximumSetPacking::::new(vec![ - vec![0, 1], - vec![1, 2], - vec![2, 3], -]); +Two solvers for testing purposes are available: -// MaximumSetPacking -> MaximumIndependentSet -> MinimumVertexCover -let sp_to_is = ReduceTo::>::reduce_to(&sp); -let is = sp_to_is.target_problem(); +| 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 | Requires `ilp` feature flag | -let is_to_vc = ReduceTo::>::reduce_to(is); -let vc = is_to_vc.target_problem(); +Enable ILP support: -// Solve and extract back through the chain -let solver = BruteForce::new(); -let vc_solutions = solver.find_best(vc); -let is_solution = is_to_vc.extract_solution(&vc_solutions[0]); -let sp_solution = sp_to_is.extract_solution(&is_solution); +```toml +[dependencies] +problemreductions = { version = "0.1", features = ["ilp"] } ``` -### Type Safety +**Future:** Automated reduction path optimization will find the best route between any two connected problems. -The reduction system is compile-time verified. Invalid reductions won't compile: +## JSON Resources + +The library exports machine-readable metadata useful for tooling and research: + +- [reduction_graph.json](reductions/reduction_graph.json) lists all problem variants and reduction edges +- [problem_schemas.json](reductions/problem_schemas.json) lists field definitions for each problem type -```rust,compile_fail -// This won't compile - no reduction from QUBO to MaximumSetPacking -let result = ReduceTo::>::reduce_to(&qubo); -``` ## Next Steps - Explore the [interactive reduction graph](./introduction.html) to discover available reductions -- Browse the [API Reference](./api.md) for full documentation -- Check out the [Solvers](./solvers.md) for different solving strategies +- Read the [Architecture](./arch.md) guide for implementation details +- Browse the [API Reference](./api.html) for full documentation diff --git a/docs/src/introduction.md b/docs/src/introduction.md index a6c17592..b27ee383 100644 --- a/docs/src/introduction.md +++ b/docs/src/introduction.md @@ -353,15 +353,16 @@ For theoretical background and correctness proofs, see the [PDF manual](https:// })(); -## Our vision +## Our Vision -Historically, computational complexity theorists have focused on the theory, leaving the tedious implementation work to developers. This `algorithm → paper` pipeline causes much duplicated effort in algorithm implementation. Without the right infrastructure, fundamental questions remain difficult to answer: -- What is the fastest known algorithm for solving a given problem? -- Given an efficient solver, which high-impact problems can it reach via reductions? +Computational complexity theory has produced a rich body of polynomial-time reductions between NP-hard problems, yet these results largely remain confined to papers. The gap between theoretical algorithms and working software leads to two persistent inefficiencies: -Imagine every computational problem in the world connected in a directed reduction graph, so that for any pair of problems we can find the most efficient reduction path between them — a high-speed railway linking any two problems. We would no longer duplicate effort solving problems that are essentially the "same." Building this infrastructure, however, is not easy. +- **Solver underutilization.** State-of-the-art solvers (SAT solvers, ILP solvers, QUBO annealers) each target a single problem formulation. In principle, any problem reducible to that formulation can leverage the same solver — but without a systematic reduction library, practitioners must re-derive and re-implement each transformation. +- **Redundant effort.** Problems that are polynomial-time equivalent are, from a computational standpoint, interchangeable. Without infrastructure connecting them, the same algorithmic insights are independently reimplemented across domains. -What if AI could take over the implementation step, completing an `algorithm → paper → software` pipeline? Theorists would still focus on theory while AI does the heavy lifting. Can we trust AI-generated implementations? We believe so — not only because large language models are rapidly improving, but also because nearly all reductions can be verified with round-trip tests: `source → target → solution to target → solution to source` must equal `source → solution to source`. Correctness is checked automatically by running these round-trip examples. +Our goal is to build a comprehensive, machine-readable reduction graph: a directed graph in which every node is a computational problem and every edge is a verified polynomial-time reduction. Given such a graph, one can automatically compose reduction paths to route any source problem to any reachable target solver. + +A key enabler is AI-assisted implementation. We propose a pipeline of `algorithm → paper → software`, in which AI agents translate published reduction proofs into tested code. The critical question — can AI-generated reductions be trusted? — has a concrete answer: nearly all reductions admit **closed-loop verification**. A round-trip test reduces a source instance to a target, solves the target, extracts the solution back, and checks it against a direct solve of the source. This property makes correctness mechanically verifiable, independent of how the code was produced.
@@ -374,7 +375,20 @@ What if AI could take over the implementation step, completing an `algorithm →
-Our vision is to automate this test-driven development pipeline and enable the general public to contribute. This software will be open source, forever, available at any physical location in the universe to every human being and AI agent. +This library is the foundation of that effort: an open-source, extensible reduction graph with verified implementations, designed for contributions from both human researchers and AI agents. + +## Call for Contributions + +> **Everyone can contribute — no programming experience required.** If you know a computational problem or a reduction rule, just describe it in a GitHub issue. AI will generate a tested pull request for you to review. +> +> **Contribute 10 non-trivial reduction rules and you will be automatically added to the author list of the [paper](https://codingthrust.github.io/problem-reductions/reductions.pdf).** + +1. **Open an issue** using the [Problem](https://github.com/CodingThrust/problem-reductions/issues/new?template=problem.md) or [Rule](https://github.com/CodingThrust/problem-reductions/issues/new?template=rule.md) template +2. **Fill in all sections** — definition, algorithm, size overhead, example instance +3. **Review AI-generated code** — AI generates code and you can comment on the pull request +4. **Merge** — ask maintainers' assistance to merge once you are satisfied + +For manual implementation, see the [Architecture](./arch.md#contributing) guide. ## License diff --git a/docs/paper/problem_schemas.json b/docs/src/reductions/problem_schemas.json similarity index 100% rename from docs/paper/problem_schemas.json rename to docs/src/reductions/problem_schemas.json diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index c1dd4885..eece8803 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -103,15 +103,6 @@ "category": "satisfiability", "doc_path": "models/satisfiability/struct.KSatisfiability.html" }, - { - "name": "KSatisfiability", - "variant": { - "graph": "SimpleGraph", - "weight": "i32" - }, - "category": "satisfiability", - "doc_path": "models/satisfiability/struct.KSatisfiability.html" - }, { "name": "MaxCut", "variant": {}, @@ -200,7 +191,7 @@ "name": "MinimumDominatingSet", "variant": { "graph": "SimpleGraph", - "weight": "Unweighted" + "weight": "i32" }, "category": "graph", "doc_path": "models/graph/struct.MinimumDominatingSet.html" @@ -460,46 +451,46 @@ } }, "target": { - "name": "Satisfiability", + "name": "QUBO", "variant": { "graph": "SimpleGraph", - "weight": "Unweighted" + "weight": "f64" } }, "overhead": [ - { - "field": "num_clauses", - "formula": "num_clauses" - }, { "field": "num_vars", - "formula": "num_vars" + "formula": "num_vars + num_clauses" } ], - "doc_path": "rules/sat_ksat/index.html" + "doc_path": "rules/ksatisfiability_qubo/index.html" }, { "source": { "name": "KSatisfiability", "variant": { "graph": "SimpleGraph", - "weight": "i32" + "weight": "Unweighted" } }, "target": { - "name": "QUBO", + "name": "Satisfiability", "variant": { "graph": "SimpleGraph", - "weight": "f64" + "weight": "Unweighted" } }, "overhead": [ + { + "field": "num_clauses", + "formula": "num_clauses" + }, { "field": "num_vars", - "formula": "num_vars + num_clauses" + "formula": "num_vars" } ], - "doc_path": "rules/ksatisfiability_qubo/index.html" + "doc_path": "rules/sat_ksat/index.html" }, { "source": { @@ -848,7 +839,7 @@ "name": "MaximumIndependentSet", "variant": { "graph": "SimpleGraph", - "weight": "Unweighted" + "weight": "i32" } }, "overhead": [ @@ -875,7 +866,7 @@ "name": "MinimumDominatingSet", "variant": { "graph": "SimpleGraph", - "weight": "Unweighted" + "weight": "i32" } }, "overhead": [ diff --git a/docs/src/static/module-overview-dark.svg b/docs/src/static/module-overview-dark.svg new file mode 100644 index 00000000..6d520f88 --- /dev/null +++ b/docs/src/static/module-overview-dark.svg @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/static/module-overview.svg b/docs/src/static/module-overview.svg new file mode 100644 index 00000000..ba76e869 --- /dev/null +++ b/docs/src/static/module-overview.svg @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/static/module-overview.typ b/docs/src/static/module-overview.typ new file mode 100644 index 00000000..afd84a84 --- /dev/null +++ b/docs/src/static/module-overview.typ @@ -0,0 +1,40 @@ +#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge +#set page(width: auto, height: auto, margin: (top: 5pt, bottom: 5pt, left: 5pt, right: 5pt), fill: none) +#set text(font: "Helvetica Neue") + +#let module-overview(dark: false) = { + let (fg, box-color) = if dark { + (rgb("#e2e8f0"), rgb("#94a3b8")) + } else { + (rgb("#1e293b"), rgb("#64748b")) + } + + // Module colors - darker versions for dark mode + let (model-color, rule-color, registry-color, solver-color) = if dark { + (rgb("#166534"), rgb("#1e3a5f"), rgb("#854d0e"), rgb("#7f1d1d")) + } else { + (rgb("#dcfce7"), rgb("#dbeafe"), rgb("#fef3c7"), rgb("#fee2e2")) + } + + set text(fill: fg, size: 10pt) + + diagram( + node-stroke: 1.5pt + box-color, + edge-stroke: 1.5pt + box-color, + spacing: (25mm, 15mm), + + // Module nodes + node((0, 0), box(width: 30mm, align(center)[#strong[models/]\ #text(size: 10pt)[Problem types]]), fill: model-color, corner-radius: 6pt, inset: 10pt, name: ), + node((1, 0), box(width: 30mm, align(center)[#strong[rules/]\ #text(size: 10pt)[Reductions]]), fill: rule-color, corner-radius: 6pt, inset: 10pt, name: ), + node((2, 0), box(width: 30mm, align(center)[#strong[registry/]\ #text(size: 10pt)[Graph metadata]]), fill: registry-color, corner-radius: 6pt, inset: 10pt, name: ), + node((1, 1), box(width: 30mm, align(center)[#strong[solvers/]\ #text(size: 8pt)[BruteForce, ILP]]), fill: solver-color, corner-radius: 6pt, inset: 10pt, name: ), + + // Relationships + edge(, , "->", label: text(size: 10pt)[imports], label-sep: 2pt, label-pos: 0.5, label-side: left), + edge(, , "->", label: text(size: 10pt)[registers], label-sep: 2pt, label-pos: 0.5, label-side: left), + edge(, , "<-", label: text(size: 10pt)[solves], label-sep: 2pt, label-pos: 0.5, label-side: left), + ) +} + +#let standalone-dark = sys.inputs.at("dark", default: "false") == "true" +#module-overview(dark: standalone-dark) diff --git a/docs/src/static/reduction-workflow-dark.svg b/docs/src/static/reduction-workflow-dark.svg new file mode 100644 index 00000000..b04e1c2c --- /dev/null +++ b/docs/src/static/reduction-workflow-dark.svg @@ -0,0 +1,427 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/static/reduction-workflow.svg b/docs/src/static/reduction-workflow.svg new file mode 100644 index 00000000..1254938a --- /dev/null +++ b/docs/src/static/reduction-workflow.svg @@ -0,0 +1,427 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/static/reduction-workflow.typ b/docs/src/static/reduction-workflow.typ new file mode 100644 index 00000000..cbb5358f --- /dev/null +++ b/docs/src/static/reduction-workflow.typ @@ -0,0 +1,36 @@ +#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge +#set page(width: auto, height: auto, margin: (top: 5pt, bottom: 5pt, left: 5pt, right: 5pt), fill: none) +#set text(font: "Helvetica Neue") + +#let reduction-workflow(dark: false) = { + let (fg, box-color, box-fill, success-fill) = if dark { + (rgb("#e2e8f0"), rgb("#94a3b8"), rgb("#1e293b"), rgb("#14532d")) + } else { + (rgb("#1e293b"), rgb("#64748b"), rgb("#f8fafc"), rgb("#dcfce7")) + } + + set text(fill: fg, size: 10pt) + + diagram( + node-stroke: 1.5pt + box-color, + edge-stroke: 1.5pt, + spacing: (20mm, 10mm), + + let accent = rgb("#3b82f6"), + let success = rgb("#22c55e"), + + // Nodes + node((0, 0), box(width: 28mm, align(center)[*Problem A*\ #text(size: 8pt)[source problem]]), fill: box-fill, corner-radius: 6pt, inset: 10pt, name:
), + node((1, 0), box(width: 28mm, align(center)[*Problem B*\ #text(size: 8pt)[target problem]]), fill: box-fill, corner-radius: 6pt, inset: 10pt, name: ), + node((2, 0), box(width: 28mm, align(center)[*Solution B*\ #text(size: 8pt)[solver output]]), fill: box-fill, corner-radius: 6pt, inset: 10pt, name: ), + node((1, 1), box(width: 28mm, align(center)[*Solution A*\ #text(size: 8pt)[extracted result]]), fill: success-fill, stroke: 1.5pt + success, corner-radius: 6pt, inset: 10pt, name: ), + + // Edges with labels + edge(, , "->", stroke: 1.5pt + accent, label: text(size: 9pt)[`reduce_to`], label-sep: 5pt, label-pos: 0.5, label-side: left), + edge(, , "->", stroke: 1.5pt + accent, label: text(size: 9pt)[`find_best`], label-sep: 5pt, label-pos: 0.5, label-side: left), + edge(, , "->", stroke: 1.5pt + success, label: text(size: 9pt)[`extract_solution`], label-sep: 2pt, label-pos: 0.5, label-side: left), + ) +} + +#let standalone-dark = sys.inputs.at("dark", default: "false") == "true" +#reduction-workflow(dark: standalone-dark) diff --git a/docs/src/static/trait-hierarchy-dark.svg b/docs/src/static/trait-hierarchy-dark.svg new file mode 100644 index 00000000..29e6dde4 --- /dev/null +++ b/docs/src/static/trait-hierarchy-dark.svg @@ -0,0 +1,707 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/static/trait-hierarchy.svg b/docs/src/static/trait-hierarchy.svg new file mode 100644 index 00000000..1004628e --- /dev/null +++ b/docs/src/static/trait-hierarchy.svg @@ -0,0 +1,707 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/static/trait-hierarchy.typ b/docs/src/static/trait-hierarchy.typ new file mode 100644 index 00000000..45a760cb --- /dev/null +++ b/docs/src/static/trait-hierarchy.typ @@ -0,0 +1,69 @@ +#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge +#set page(width: auto, height: auto, margin: (top: 5pt, bottom: 5pt, left: 5pt, right: 5pt), fill: none) +#set text(font: "Helvetica Neue") + +#let trait-hierarchy(dark: false) = { + let (fg, box-color, secondary) = if dark { + (rgb("#e2e8f0"), rgb("#94a3b8"), rgb("#94a3b8")) + } else { + (rgb("#1e293b"), rgb("#64748b"), rgb("#6b7280")) + } + + // Trait and type fills - darker for dark mode + let (trait-fill, type-fill) = if dark { + (rgb("#1e3a5f"), rgb("#854d0e")) + } else { + (rgb("#dbeafe"), rgb("#fef3c7")) + } + + set text(fill: fg, size: 9pt) + + diagram( + node-stroke: 1.5pt + box-color, + edge-stroke: 1.5pt + box-color, + spacing: (8mm, 12mm), + + // Problem trait (main) + node((0, 0), box(width: 55mm, align(left)[ + #strong[trait Problem]\ + #text(size: 8pt, fill: secondary)[ + `const NAME: &str`\ + `type Metric: Clone`\ + `fn dims() -> Vec`\ + `fn evaluate(&config) -> Metric`\ + `fn variant() -> Vec<(&str, &str)>` + ] + ]), fill: trait-fill, corner-radius: 6pt, inset: 10pt, name: ), + + // OptimizationProblem trait + node((0, 1), box(width: 55mm, align(left)[ + #strong[trait OptimizationProblem]\ + #text(size: 8pt, fill: secondary)[ + `type Value: PartialOrd + Clone`\ + `fn direction() -> Direction`\ + #text(style: "italic")[requires `Metric = SolutionSize`] + ] + ]), fill: trait-fill, corner-radius: 6pt, inset: 10pt, name: ), + + // Type boxes on the right + node((1.3, 0), box(width: 38mm, align(left)[ + #strong[SolutionSize\]\ + #text(size: 8pt, fill: secondary)[`Valid(T) | Invalid`] + ]), fill: type-fill, corner-radius: 6pt, inset: 8pt, name: ), + + node((1.3, 1), box(width: 38mm, align(left)[ + #strong[Direction]\ + #text(size: 8pt, fill: secondary)[`Maximize | Minimize`] + ]), fill: type-fill, corner-radius: 6pt, inset: 8pt, name: ), + + // Inheritance arrow + edge(, , "->", label: text(size: 8pt)[extends], label-side: left, label-fill: none), + + // Type associations + edge(, , "->"), + edge(, , "->"), + ) +} + +#let standalone-dark = sys.inputs.at("dark", default: "false") == "true" +#trait-hierarchy(dark: standalone-dark) diff --git a/docs/src/static/workflow-loop-dark.svg b/docs/src/static/workflow-loop-dark.svg index 47f904a8..ad7f03d1 100644 --- a/docs/src/static/workflow-loop-dark.svg +++ b/docs/src/static/workflow-loop-dark.svg @@ -1,33 +1,33 @@ - + - - - - - - - - - - - - - + + + + + + + + + + + + + - + - - - + + + @@ -37,22 +37,22 @@ - + - + - - - - - - - - - + + + + + + + + + @@ -60,20 +60,20 @@ - + - + - - - - - - - + + + + + + + @@ -81,20 +81,20 @@ - + - + - - - - - - - + + + + + + + @@ -102,49 +102,49 @@ - - + + - + - - + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - + + - - - - - - - - - - - + + + + + + + + + + + @@ -154,54 +154,54 @@ - - + + - + - - + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -211,48 +211,48 @@ - - + + - + - - + + - - - - - - - - - - + + + + + + + + + + - - - - - - + + + + + + - - + + - - - - - - - - + + + + + + + + @@ -262,24 +262,24 @@ - - + + - + - - + + - - - - - - - + + + + + + + @@ -293,155 +293,155 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/docs/src/static/workflow-loop.svg b/docs/src/static/workflow-loop.svg index 53ef9bb6..3291cb7f 100644 --- a/docs/src/static/workflow-loop.svg +++ b/docs/src/static/workflow-loop.svg @@ -1,33 +1,33 @@ - + - - - - - - - - - - - - - + + + + + + + + + + + + + - + - - - + + + @@ -37,22 +37,22 @@ - + - + - - - - - - - - - + + + + + + + + + @@ -60,20 +60,20 @@ - + - + - - - - - - - + + + + + + + @@ -81,20 +81,20 @@ - + - + - - - - - - - + + + + + + + @@ -102,49 +102,49 @@ - - + + - + - - + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - + + - - - - - - - - - - - + + + + + + + + + + + @@ -154,54 +154,54 @@ - - + + - + - - + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -211,48 +211,48 @@ - - + + - + - - + + - - - - - - - - - - + + + + + + + + + + - - - - - - + + + + + + - - + + - - - - - - - - + + + + + + + + @@ -262,24 +262,24 @@ - - + + - + - - + + - - - - - - - + + + + + + + @@ -293,155 +293,155 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/docs/src/static/workflow-loop.typ b/docs/src/static/workflow-loop.typ index e710d564..7477e72f 100644 --- a/docs/src/static/workflow-loop.typ +++ b/docs/src/static/workflow-loop.typ @@ -1,6 +1,6 @@ #import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge #set page(width: auto, height: auto, margin: (top: 5pt, bottom: 5pt, left: 5pt, right: 5pt), fill: none) -#set text(font: "Noto Sans CJK SC") +#set text(font: "Helvetica Neue") // Parameterized workflow-loop diagram // Usage: #workflow-loop(lang: "zh", label-size: 12pt, scale: 1.2) diff --git a/examples/export_graph.rs b/examples/export_graph.rs index cea778b5..dc2e4c32 100644 --- a/examples/export_graph.rs +++ b/examples/export_graph.rs @@ -13,8 +13,8 @@ fn main() { println!(" Problem types: {}", graph.num_types()); println!(" Reductions: {}", graph.num_reductions()); - // Export to JSON - let output_path = Path::new("docs/paper/reduction_graph.json"); + // Export to JSON (single source for both mdBook and paper) + let output_path = Path::new("docs/src/reductions/reduction_graph.json"); // Create parent directories if needed if let Some(parent) = output_path.parent() { diff --git a/examples/export_schemas.rs b/examples/export_schemas.rs index c17b5b48..f91b7ee6 100644 --- a/examples/export_schemas.rs +++ b/examples/export_schemas.rs @@ -9,7 +9,8 @@ fn main() { let schemas = collect_schemas(); println!("Collected {} problem schemas", schemas.len()); - let output_path = Path::new("docs/paper/problem_schemas.json"); + // Single source for both mdBook and paper + let output_path = Path::new("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"); } diff --git a/examples/reduction_circuitsat_to_spinglass.rs b/examples/reduction_circuitsat_to_spinglass.rs index 3c1b17e1..c35c22ed 100644 --- a/examples/reduction_circuitsat_to_spinglass.rs +++ b/examples/reduction_circuitsat_to_spinglass.rs @@ -82,7 +82,7 @@ pub fn run() { // 3. Solve the target SpinGlass problem let solver = BruteForce::new(); - let sg_solutions = solver.find_best(sg); + let sg_solutions = solver.find_all_best(sg); println!("\n=== Solution ==="); println!( "Target SpinGlass ground states found: {}", diff --git a/examples/reduction_factoring_to_circuitsat.rs b/examples/reduction_factoring_to_circuitsat.rs index cae75789..114b1023 100644 --- a/examples/reduction_factoring_to_circuitsat.rs +++ b/examples/reduction_factoring_to_circuitsat.rs @@ -60,7 +60,7 @@ pub fn run() { // 2. Solve the source Factoring problem directly (only 6 binary variables) let solver = BruteForce::new(); - let factoring_solutions = solver.find_best(&factoring); + let factoring_solutions = solver.find_all_best(&factoring); println!("\nFactoring solutions found: {}", factoring_solutions.len()); for sol in &factoring_solutions { let (a, b) = factoring.read_factors(sol); diff --git a/examples/reduction_ilp_to_qubo.rs b/examples/reduction_ilp_to_qubo.rs index 0a59f96d..cf4c38dd 100644 --- a/examples/reduction_ilp_to_qubo.rs +++ b/examples/reduction_ilp_to_qubo.rs @@ -89,7 +89,7 @@ pub fn run() { // Solve QUBO with brute force let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Extract and verify solutions println!("\nOptimal solutions:"); diff --git a/examples/reduction_kcoloring_to_qubo.rs b/examples/reduction_kcoloring_to_qubo.rs index 6805e7a0..f960fb97 100644 --- a/examples/reduction_kcoloring_to_qubo.rs +++ b/examples/reduction_kcoloring_to_qubo.rs @@ -58,7 +58,7 @@ pub fn run() { // Solve QUBO with brute force let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Extract and verify solutions println!("\nValid 3-colorings: {}", qubo_solutions.len()); diff --git a/examples/reduction_ksatisfiability_to_qubo.rs b/examples/reduction_ksatisfiability_to_qubo.rs index f6fe2ef1..ed7d1608 100644 --- a/examples/reduction_ksatisfiability_to_qubo.rs +++ b/examples/reduction_ksatisfiability_to_qubo.rs @@ -80,7 +80,7 @@ pub fn run() { // Solve QUBO with brute force let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Extract and verify solutions println!("\nOptimal solutions:"); diff --git a/examples/reduction_maxcut_to_spinglass.rs b/examples/reduction_maxcut_to_spinglass.rs index 549eeff5..da27eb0b 100644 --- a/examples/reduction_maxcut_to_spinglass.rs +++ b/examples/reduction_maxcut_to_spinglass.rs @@ -32,7 +32,7 @@ pub fn run() { println!("Target: SpinGlass with {} variables", sg.num_variables()); let solver = BruteForce::new(); - let sg_solutions = solver.find_best(sg); + let sg_solutions = solver.find_all_best(sg); println!("\n=== Solution ==="); println!("Target solutions found: {}", sg_solutions.len()); diff --git a/examples/reduction_maximumclique_to_ilp.rs b/examples/reduction_maximumclique_to_ilp.rs index 222bdd73..4c43971a 100644 --- a/examples/reduction_maximumclique_to_ilp.rs +++ b/examples/reduction_maximumclique_to_ilp.rs @@ -42,7 +42,7 @@ pub fn run() { // 4. Solve target ILP let solver = BruteForce::new(); - let ilp_solutions = solver.find_best(ilp); + let ilp_solutions = solver.find_all_best(ilp); println!("\n=== Solution ==="); println!("ILP solutions found: {}", ilp_solutions.len()); diff --git a/examples/reduction_maximumindependentset_to_ilp.rs b/examples/reduction_maximumindependentset_to_ilp.rs index d4922529..5bf753fe 100644 --- a/examples/reduction_maximumindependentset_to_ilp.rs +++ b/examples/reduction_maximumindependentset_to_ilp.rs @@ -41,7 +41,7 @@ pub fn run() { // 4. Solve target ILP let solver = BruteForce::new(); - let ilp_solutions = solver.find_best(ilp); + let ilp_solutions = solver.find_all_best(ilp); println!("\n=== Solution ==="); println!("ILP solutions found: {}", ilp_solutions.len()); diff --git a/examples/reduction_maximumindependentset_to_maximumsetpacking.rs b/examples/reduction_maximumindependentset_to_maximumsetpacking.rs index 7582efd8..486d0f9e 100644 --- a/examples/reduction_maximumindependentset_to_maximumsetpacking.rs +++ b/examples/reduction_maximumindependentset_to_maximumsetpacking.rs @@ -43,7 +43,7 @@ pub fn run() { // Solve the target problem let solver = BruteForce::new(); - let target_solutions = solver.find_best(target); + let target_solutions = solver.find_all_best(target); println!("\nBest target solutions: {}", target_solutions.len()); diff --git a/examples/reduction_maximumindependentset_to_minimumvertexcover.rs b/examples/reduction_maximumindependentset_to_minimumvertexcover.rs index 6682306a..eeb6f1b3 100644 --- a/examples/reduction_maximumindependentset_to_minimumvertexcover.rs +++ b/examples/reduction_maximumindependentset_to_minimumvertexcover.rs @@ -41,7 +41,7 @@ pub fn run() { // 4. Solve target let solver = BruteForce::new(); - let vc_solutions = solver.find_best(vc); + let vc_solutions = solver.find_all_best(vc); println!("\n=== Solution ==="); println!("Target solutions found: {}", vc_solutions.len()); diff --git a/examples/reduction_maximumindependentset_to_qubo.rs b/examples/reduction_maximumindependentset_to_qubo.rs index a10cb727..e645836a 100644 --- a/examples/reduction_maximumindependentset_to_qubo.rs +++ b/examples/reduction_maximumindependentset_to_qubo.rs @@ -49,7 +49,7 @@ pub fn run() { // Solve QUBO with brute force let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Extract and verify solutions println!("\nOptimal solutions:"); diff --git a/examples/reduction_maximummatching_to_ilp.rs b/examples/reduction_maximummatching_to_ilp.rs index 61eefc9c..0fdff47b 100644 --- a/examples/reduction_maximummatching_to_ilp.rs +++ b/examples/reduction_maximummatching_to_ilp.rs @@ -41,7 +41,7 @@ pub fn run() { // 4. Solve target ILP let solver = BruteForce::new(); - let ilp_solutions = solver.find_best(ilp); + let ilp_solutions = solver.find_all_best(ilp); println!("\n=== Solution ==="); println!("ILP solutions found: {}", ilp_solutions.len()); diff --git a/examples/reduction_maximummatching_to_maximumsetpacking.rs b/examples/reduction_maximummatching_to_maximumsetpacking.rs index 5172ddfd..e36217c7 100644 --- a/examples/reduction_maximummatching_to_maximumsetpacking.rs +++ b/examples/reduction_maximummatching_to_maximumsetpacking.rs @@ -43,7 +43,7 @@ pub fn run() { // Solve the target problem let solver = BruteForce::new(); - let target_solutions = solver.find_best(target); + let target_solutions = solver.find_all_best(target); println!("\nBest target solutions: {}", target_solutions.len()); diff --git a/examples/reduction_maximumsetpacking_to_ilp.rs b/examples/reduction_maximumsetpacking_to_ilp.rs index dd1786b5..9c2147dc 100644 --- a/examples/reduction_maximumsetpacking_to_ilp.rs +++ b/examples/reduction_maximumsetpacking_to_ilp.rs @@ -50,7 +50,7 @@ pub fn run() { // 4. Solve target ILP let solver = BruteForce::new(); - let ilp_solutions = solver.find_best(ilp); + let ilp_solutions = solver.find_all_best(ilp); println!("\n=== Solution ==="); println!("ILP solutions found: {}", ilp_solutions.len()); diff --git a/examples/reduction_maximumsetpacking_to_qubo.rs b/examples/reduction_maximumsetpacking_to_qubo.rs index 76deeba1..ea6c80f9 100644 --- a/examples/reduction_maximumsetpacking_to_qubo.rs +++ b/examples/reduction_maximumsetpacking_to_qubo.rs @@ -62,7 +62,7 @@ pub fn run() { // Solve QUBO with brute force let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Extract and verify solutions println!("\nOptimal solutions:"); diff --git a/examples/reduction_minimumdominatingset_to_ilp.rs b/examples/reduction_minimumdominatingset_to_ilp.rs index 87c9b91c..270d3af8 100644 --- a/examples/reduction_minimumdominatingset_to_ilp.rs +++ b/examples/reduction_minimumdominatingset_to_ilp.rs @@ -41,7 +41,7 @@ pub fn run() { // 4. Solve target ILP let solver = BruteForce::new(); - let ilp_solutions = solver.find_best(ilp); + let ilp_solutions = solver.find_all_best(ilp); println!("\n=== Solution ==="); println!("ILP solutions found: {}", ilp_solutions.len()); diff --git a/examples/reduction_minimumsetcovering_to_ilp.rs b/examples/reduction_minimumsetcovering_to_ilp.rs index d4861e69..b9c8c3f5 100644 --- a/examples/reduction_minimumsetcovering_to_ilp.rs +++ b/examples/reduction_minimumsetcovering_to_ilp.rs @@ -50,7 +50,7 @@ pub fn run() { // 4. Solve target ILP let solver = BruteForce::new(); - let ilp_solutions = solver.find_best(ilp); + let ilp_solutions = solver.find_all_best(ilp); println!("\n=== Solution ==="); println!("ILP solutions found: {}", ilp_solutions.len()); diff --git a/examples/reduction_minimumvertexcover_to_ilp.rs b/examples/reduction_minimumvertexcover_to_ilp.rs index f90e306d..f2807bfc 100644 --- a/examples/reduction_minimumvertexcover_to_ilp.rs +++ b/examples/reduction_minimumvertexcover_to_ilp.rs @@ -41,7 +41,7 @@ pub fn run() { // 4. Solve target ILP let solver = BruteForce::new(); - let ilp_solutions = solver.find_best(ilp); + let ilp_solutions = solver.find_all_best(ilp); println!("\n=== Solution ==="); println!("ILP solutions found: {}", ilp_solutions.len()); diff --git a/examples/reduction_minimumvertexcover_to_maximumindependentset.rs b/examples/reduction_minimumvertexcover_to_maximumindependentset.rs index b0abf6ae..b0869be6 100644 --- a/examples/reduction_minimumvertexcover_to_maximumindependentset.rs +++ b/examples/reduction_minimumvertexcover_to_maximumindependentset.rs @@ -39,7 +39,7 @@ pub fn run() { ); let solver = BruteForce::new(); - let is_solutions = solver.find_best(is); + let is_solutions = solver.find_all_best(is); println!("\n=== Solution ==="); println!("Target solutions found: {}", is_solutions.len()); diff --git a/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs b/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs index 1c647b7f..5b0c6013 100644 --- a/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs +++ b/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs @@ -44,7 +44,7 @@ pub fn run() { // Solve the target problem let solver = BruteForce::new(); - let target_solutions = solver.find_best(target); + let target_solutions = solver.find_all_best(target); println!("\nBest target solutions: {}", target_solutions.len()); diff --git a/examples/reduction_minimumvertexcover_to_qubo.rs b/examples/reduction_minimumvertexcover_to_qubo.rs index 1fb1acbc..855193ca 100644 --- a/examples/reduction_minimumvertexcover_to_qubo.rs +++ b/examples/reduction_minimumvertexcover_to_qubo.rs @@ -49,7 +49,7 @@ pub fn run() { // Solve QUBO with brute force let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Extract and verify solutions println!("\nOptimal solutions:"); diff --git a/examples/reduction_qubo_to_spinglass.rs b/examples/reduction_qubo_to_spinglass.rs index b6e18905..e122e3d0 100644 --- a/examples/reduction_qubo_to_spinglass.rs +++ b/examples/reduction_qubo_to_spinglass.rs @@ -43,7 +43,7 @@ pub fn run() { println!("Target: SpinGlass with {} variables", sg.num_variables()); let solver = BruteForce::new(); - let sg_solutions = solver.find_best(sg); + let sg_solutions = solver.find_all_best(sg); println!("\n=== Solution ==="); println!("Target solutions found: {}", sg_solutions.len()); diff --git a/examples/reduction_satisfiability_to_maximumindependentset.rs b/examples/reduction_satisfiability_to_maximumindependentset.rs index 9d3c5703..0cb3f130 100644 --- a/examples/reduction_satisfiability_to_maximumindependentset.rs +++ b/examples/reduction_satisfiability_to_maximumindependentset.rs @@ -62,7 +62,7 @@ pub fn run() { // 3. Solve the target IS problem let solver = BruteForce::new(); - let is_solutions = solver.find_best(is); + let is_solutions = solver.find_all_best(is); println!("\n=== Solution ==="); println!("Target IS solutions found: {}", is_solutions.len()); diff --git a/examples/reduction_satisfiability_to_minimumdominatingset.rs b/examples/reduction_satisfiability_to_minimumdominatingset.rs index 4e5a0f63..f02f8c34 100644 --- a/examples/reduction_satisfiability_to_minimumdominatingset.rs +++ b/examples/reduction_satisfiability_to_minimumdominatingset.rs @@ -62,7 +62,7 @@ pub fn run() { // 3. Solve the target DS problem let solver = BruteForce::new(); - let ds_solutions = solver.find_best(ds); + let ds_solutions = solver.find_all_best(ds); println!("\n=== Solution ==="); println!("Target DS solutions found: {}", ds_solutions.len()); diff --git a/examples/reduction_spinglass_to_maxcut.rs b/examples/reduction_spinglass_to_maxcut.rs index 89fdd019..1b6364eb 100644 --- a/examples/reduction_spinglass_to_maxcut.rs +++ b/examples/reduction_spinglass_to_maxcut.rs @@ -37,7 +37,7 @@ pub fn run() { println!("Target: MaxCut with {} variables", maxcut.num_variables()); let solver = BruteForce::new(); - let maxcut_solutions = solver.find_best(maxcut); + let maxcut_solutions = solver.find_all_best(maxcut); println!("\n=== Solution ==="); println!("Target solutions found: {}", maxcut_solutions.len()); diff --git a/examples/reduction_spinglass_to_qubo.rs b/examples/reduction_spinglass_to_qubo.rs index f8c10232..86af4852 100644 --- a/examples/reduction_spinglass_to_qubo.rs +++ b/examples/reduction_spinglass_to_qubo.rs @@ -39,7 +39,7 @@ pub fn run() { println!("Target: QUBO with {} variables", qubo.num_variables()); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); println!("\n=== Solution ==="); println!("Target solutions found: {}", qubo_solutions.len()); diff --git a/src/lib.rs b/src/lib.rs index e8f9fdb7..2f6e9112 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,10 +25,10 @@ //! //! // Solve with brute force //! let solver = BruteForce::new(); -//! let solutions = solver.find_best(&problem); +//! let solution = solver.find_best(&problem).unwrap(); //! //! // Maximum independent set in a triangle has size 1 -//! assert!(solutions.iter().all(|s| s.iter().sum::() == 1)); +//! assert_eq!(solution.iter().sum::(), 1); //! ``` //! //! ## Problem Categories diff --git a/src/models/graph/max_cut.rs b/src/models/graph/max_cut.rs index 0c08025d..f886d473 100644 --- a/src/models/graph/max_cut.rs +++ b/src/models/graph/max_cut.rs @@ -52,7 +52,7 @@ inventory::submit! { /// /// // Solve with brute force /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Maximum cut in triangle is 2 (any partition cuts 2 edges) /// for sol in solutions { diff --git a/src/models/graph/maximal_is.rs b/src/models/graph/maximal_is.rs index a04ae49f..8160020f 100644 --- a/src/models/graph/maximal_is.rs +++ b/src/models/graph/maximal_is.rs @@ -39,7 +39,7 @@ inventory::submit! { /// let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Maximal independent sets: {0, 2} or {1} /// for sol in &solutions { diff --git a/src/models/graph/maximum_clique.rs b/src/models/graph/maximum_clique.rs index 4560f73e..213ef7c8 100644 --- a/src/models/graph/maximum_clique.rs +++ b/src/models/graph/maximum_clique.rs @@ -44,7 +44,7 @@ inventory::submit! { /// /// // Solve with brute force /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Maximum clique in a triangle (K3) is size 3 /// assert!(solutions.iter().all(|s| s.iter().sum::() == 3)); diff --git a/src/models/graph/maximum_independent_set.rs b/src/models/graph/maximum_independent_set.rs index 2534db2d..efd19550 100644 --- a/src/models/graph/maximum_independent_set.rs +++ b/src/models/graph/maximum_independent_set.rs @@ -44,7 +44,7 @@ inventory::submit! { /// /// // Solve with brute force /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Maximum independent set in a triangle has size 1 /// assert!(solutions.iter().all(|s| s.iter().sum::() == 1)); diff --git a/src/models/graph/maximum_matching.rs b/src/models/graph/maximum_matching.rs index 80bf6387..d3d72edb 100644 --- a/src/models/graph/maximum_matching.rs +++ b/src/models/graph/maximum_matching.rs @@ -42,7 +42,7 @@ inventory::submit! { /// let problem = MaximumMatching::::new(3, vec![(0, 1, 1), (1, 2, 1)]); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Maximum matching has 1 edge /// for sol in &solutions { diff --git a/src/models/graph/minimum_dominating_set.rs b/src/models/graph/minimum_dominating_set.rs index 16160283..eb4a2e9a 100644 --- a/src/models/graph/minimum_dominating_set.rs +++ b/src/models/graph/minimum_dominating_set.rs @@ -39,7 +39,7 @@ inventory::submit! { /// let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (0, 2), (0, 3)]); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Minimum dominating set is just the center vertex /// assert!(solutions.contains(&vec![1, 0, 0, 0])); diff --git a/src/models/graph/minimum_vertex_cover.rs b/src/models/graph/minimum_vertex_cover.rs index 140cc8f9..f126da6d 100644 --- a/src/models/graph/minimum_vertex_cover.rs +++ b/src/models/graph/minimum_vertex_cover.rs @@ -39,7 +39,7 @@ inventory::submit! { /// /// // Solve with brute force /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Minimum vertex cover is just vertex 1 /// assert!(solutions.contains(&vec![0, 1, 0])); diff --git a/src/models/optimization/qubo.rs b/src/models/optimization/qubo.rs index 1eb853d6..5b20bcbe 100644 --- a/src/models/optimization/qubo.rs +++ b/src/models/optimization/qubo.rs @@ -43,7 +43,7 @@ inventory::submit! { /// ]); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Optimal is x = [0, 1] with value -2 /// assert!(solutions.contains(&vec![0, 1])); diff --git a/src/models/optimization/spin_glass.rs b/src/models/optimization/spin_glass.rs index 9120eb73..2c771bc7 100644 --- a/src/models/optimization/spin_glass.rs +++ b/src/models/optimization/spin_glass.rs @@ -49,7 +49,7 @@ inventory::submit! { /// let problem = SpinGlass::::new(2, vec![((0, 1), 1.0)], vec![0.0, 0.0]); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Ground state has opposite spins /// for sol in &solutions { diff --git a/src/models/set/maximum_set_packing.rs b/src/models/set/maximum_set_packing.rs index f2918e3f..0ccfa0de 100644 --- a/src/models/set/maximum_set_packing.rs +++ b/src/models/set/maximum_set_packing.rs @@ -41,7 +41,7 @@ inventory::submit! { /// ]); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Verify solutions are pairwise disjoint /// for sol in solutions { diff --git a/src/models/set/minimum_set_covering.rs b/src/models/set/minimum_set_covering.rs index 8b3bdf8d..067faeed 100644 --- a/src/models/set/minimum_set_covering.rs +++ b/src/models/set/minimum_set_covering.rs @@ -46,7 +46,7 @@ inventory::submit! { /// ); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Verify solutions cover all elements /// for sol in solutions { diff --git a/src/models/specialized/biclique_cover.rs b/src/models/specialized/biclique_cover.rs index 976197b2..b0b53cc4 100644 --- a/src/models/specialized/biclique_cover.rs +++ b/src/models/specialized/biclique_cover.rs @@ -38,7 +38,7 @@ inventory::submit! { /// let problem = BicliqueCover::new(2, 2, vec![(0, 2), (0, 3), (1, 2)], 2); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Check coverage /// for sol in &solutions { diff --git a/src/models/specialized/bmf.rs b/src/models/specialized/bmf.rs index 06f47b90..fc50c8ed 100644 --- a/src/models/specialized/bmf.rs +++ b/src/models/specialized/bmf.rs @@ -44,7 +44,7 @@ inventory::submit! { /// let problem = BMF::new(a, 1); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Check the error /// for sol in &solutions { diff --git a/src/models/specialized/factoring.rs b/src/models/specialized/factoring.rs index 278bcfb3..d1639afa 100644 --- a/src/models/specialized/factoring.rs +++ b/src/models/specialized/factoring.rs @@ -35,7 +35,7 @@ inventory::submit! { /// let problem = Factoring::new(2, 2, 6); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // Should find: 2*3=6 or 3*2=6 /// for sol in &solutions { diff --git a/src/models/specialized/paintshop.rs b/src/models/specialized/paintshop.rs index ec95449b..9e009702 100644 --- a/src/models/specialized/paintshop.rs +++ b/src/models/specialized/paintshop.rs @@ -39,7 +39,7 @@ inventory::submit! { /// let problem = PaintShop::new(vec!["a", "b", "a", "c", "c", "b"]); /// /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(&problem); +/// let solutions = solver.find_all_best(&problem); /// /// // The minimum number of color switches /// for sol in &solutions { diff --git a/src/rules/traits.rs b/src/rules/traits.rs index c010bcd0..2a49a947 100644 --- a/src/rules/traits.rs +++ b/src/rules/traits.rs @@ -47,7 +47,7 @@ pub trait ReductionResult: Clone { /// /// // Solve and extract solutions /// let solver = BruteForce::new(); -/// let solutions = solver.find_best(is_problem); +/// let solutions = solver.find_all_best(is_problem); /// let sat_solutions: Vec<_> = solutions.iter() /// .map(|s| reduction.extract_solution(s)) /// .collect(); diff --git a/src/rules/unitdiskmapping/pathdecomposition.rs b/src/rules/unitdiskmapping/pathdecomposition.rs index 085f7a74..0aad1605 100644 --- a/src/rules/unitdiskmapping/pathdecomposition.rs +++ b/src/rules/unitdiskmapping/pathdecomposition.rs @@ -17,6 +17,19 @@ use rand::seq::IndexedRandom; use std::collections::{HashMap, HashSet}; +/// Adjacency list representation built once from an edge list. +type AdjList = Vec>; + +/// Build an adjacency list from an edge list. +fn build_adj(num_vertices: usize, edges: &[(usize, usize)]) -> AdjList { + let mut adj: Vec> = vec![HashSet::new(); num_vertices]; + for &(u, v) in edges { + adj[u].insert(v); + adj[v].insert(u); + } + adj +} + /// A layout representing a partial path decomposition. /// /// The layout tracks: @@ -44,7 +57,8 @@ impl Layout { /// * `edges` - List of edges as (u, v) pairs /// * `vertices` - Initial ordered list of vertices pub fn new(num_vertices: usize, edges: &[(usize, usize)], vertices: Vec) -> Self { - let (vsep, neighbors) = vsep_and_neighbors(num_vertices, edges, &vertices); + let adj = build_adj(num_vertices, edges); + let (vsep, neighbors) = vsep_and_neighbors(num_vertices, &adj, &vertices); let vertices_set: HashSet = vertices.iter().copied().collect(); let neighbors_set: HashSet = neighbors.iter().copied().collect(); let disconnected: Vec = (0..num_vertices) @@ -87,7 +101,7 @@ impl Layout { /// /// # Arguments /// * `num_vertices` - Total number of vertices -/// * `edges` - List of edges +/// * `adj` - Pre-built adjacency list /// * `vertices` - Ordered list of vertices /// /// # Returns @@ -95,16 +109,9 @@ impl Layout { /// neighbors is the final neighbor set after all vertices are added. fn vsep_and_neighbors( num_vertices: usize, - edges: &[(usize, usize)], + adj: &AdjList, vertices: &[usize], ) -> (usize, Vec) { - // Build adjacency list - let mut adj: Vec> = vec![HashSet::new(); num_vertices]; - for &(u, v) in edges { - adj[u].insert(v); - adj[v].insert(u); - } - let mut vsep = 0; let mut neighbors: HashSet = HashSet::new(); @@ -128,14 +135,7 @@ fn vsep_and_neighbors( /// Compute the updated vsep if vertex v is added to the layout. /// /// This is an efficient incremental computation that doesn't create a new layout. -fn vsep_updated(num_vertices: usize, edges: &[(usize, usize)], layout: &Layout, v: usize) -> usize { - // Build adjacency list - let mut adj: Vec> = vec![HashSet::new(); num_vertices]; - for &(u, w) in edges { - adj[u].insert(w); - adj[w].insert(u); - } - +fn vsep_updated(adj: &AdjList, layout: &Layout, v: usize) -> usize { let mut vs = layout.vsep_last(); // If v is in neighbors, removing it decreases frontier by 1 @@ -160,18 +160,10 @@ fn vsep_updated(num_vertices: usize, edges: &[(usize, usize)], layout: &Layout, /// /// Returns (new_vsep, new_neighbors, new_disconnected). fn vsep_updated_neighbors( - num_vertices: usize, - edges: &[(usize, usize)], + adj: &AdjList, layout: &Layout, v: usize, ) -> (usize, Vec, Vec) { - // Build adjacency list - let mut adj: Vec> = vec![HashSet::new(); num_vertices]; - for &(u, w) in edges { - adj[u].insert(w); - adj[w].insert(u); - } - let mut vs = layout.vsep_last(); let mut nbs: Vec = layout.neighbors.clone(); let mut disc: Vec = layout.disconnected.clone(); @@ -203,12 +195,11 @@ fn vsep_updated_neighbors( /// Extend a layout by adding a vertex. /// /// This is the ⊙ operator from the Julia implementation. -fn extend(num_vertices: usize, edges: &[(usize, usize)], layout: &Layout, v: usize) -> Layout { +fn extend(adj: &AdjList, layout: &Layout, v: usize) -> Layout { let mut vertices = layout.vertices.clone(); vertices.push(v); - let (vs_new, neighbors_new, disconnected) = - vsep_updated_neighbors(num_vertices, edges, layout, v); + let (vs_new, neighbors_new, disconnected) = vsep_updated_neighbors(adj, layout, v); Layout { vertices, @@ -223,14 +214,7 @@ fn extend(num_vertices: usize, edges: &[(usize, usize)], layout: &Layout, v: usi /// This adds vertices that can be added without increasing the vertex separation: /// 1. Vertices whose all neighbors are already in vertices or neighbors (safe to add) /// 2. Neighbor vertices that would add exactly one new neighbor (maintains separation) -fn greedy_exact(num_vertices: usize, edges: &[(usize, usize)], mut layout: Layout) -> Layout { - // Build adjacency list - let mut adj: Vec> = vec![HashSet::new(); num_vertices]; - for &(u, v) in edges { - adj[u].insert(v); - adj[v].insert(u); - } - +fn greedy_exact(adj: &AdjList, mut layout: Layout) -> Layout { let mut keep_going = true; while keep_going { keep_going = false; @@ -246,7 +230,7 @@ fn greedy_exact(num_vertices: usize, edges: &[(usize, usize)], mut layout: Layou .all(|&nb| vertices_set.contains(&nb) || neighbors_set.contains(&nb)); if all_neighbors_covered { - layout = extend(num_vertices, edges, &layout, v); + layout = extend(adj, &layout, v); keep_going = true; } } @@ -263,7 +247,7 @@ fn greedy_exact(num_vertices: usize, edges: &[(usize, usize)], mut layout: Layou .count(); if new_neighbors_count == 1 { - layout = extend(num_vertices, edges, &layout, v); + layout = extend(adj, &layout, v); keep_going = true; } } @@ -275,15 +259,10 @@ fn greedy_exact(num_vertices: usize, edges: &[(usize, usize)], mut layout: Layou /// Perform one greedy step by choosing the best vertex from a list. /// /// Selects randomly among vertices that minimize the new vsep. -fn greedy_step( - num_vertices: usize, - edges: &[(usize, usize)], - layout: &Layout, - list: &[usize], -) -> Layout { +fn greedy_step(adj: &AdjList, layout: &Layout, list: &[usize]) -> Layout { let layouts: Vec = list .iter() - .map(|&v| extend(num_vertices, edges, layout, v)) + .map(|&v| extend(adj, layout, v)) .collect(); let costs: Vec = layouts.iter().map(|l| l.vsep()).collect(); @@ -307,15 +286,16 @@ fn greedy_step( /// This combines exact rules (that don't increase pathwidth) with /// greedy choices when exact rules don't apply. pub fn greedy_decompose(num_vertices: usize, edges: &[(usize, usize)]) -> Layout { + let adj = build_adj(num_vertices, edges); let mut layout = Layout::empty(num_vertices); loop { - layout = greedy_exact(num_vertices, edges, layout); + layout = greedy_exact(&adj, layout); if !layout.neighbors.is_empty() { - layout = greedy_step(num_vertices, edges, &layout, &layout.neighbors.clone()); + layout = greedy_step(&adj, &layout, &layout.neighbors.clone()); } else if !layout.disconnected.is_empty() { - layout = greedy_step(num_vertices, edges, &layout, &layout.disconnected.clone()); + layout = greedy_step(&adj, &layout, &layout.disconnected.clone()); } else { break; } @@ -328,23 +308,24 @@ pub fn greedy_decompose(num_vertices: usize, edges: &[(usize, usize)]) -> Layout /// /// This finds the optimal (minimum) pathwidth decomposition. pub fn branch_and_bound(num_vertices: usize, edges: &[(usize, usize)]) -> Layout { + let adj = build_adj(num_vertices, edges); let initial = Layout::empty(num_vertices); let full_layout = Layout::new(num_vertices, edges, (0..num_vertices).collect()); let mut visited: HashMap, bool> = HashMap::new(); - branch_and_bound_internal(num_vertices, edges, initial, full_layout, &mut visited) + branch_and_bound_internal(&adj, num_vertices, initial, full_layout, &mut visited) } /// Internal branch and bound implementation. fn branch_and_bound_internal( + adj: &AdjList, num_vertices: usize, - edges: &[(usize, usize)], p: Layout, mut best: Layout, visited: &mut HashMap, bool>, ) -> Layout { if p.vsep() < best.vsep() && !visited.contains_key(&p.vertices) { - let p2 = greedy_exact(num_vertices, edges, p.clone()); + let p2 = greedy_exact(adj, p.clone()); let vsep_p2 = p2.vsep(); // Check if P2 is complete @@ -362,16 +343,16 @@ fn branch_and_bound_internal( // Sort by increasing vsep_updated let mut vsep_order: Vec<(usize, usize)> = remaining .iter() - .map(|&v| (vsep_updated(num_vertices, edges, &p2, v), v)) + .map(|&v| (vsep_updated(adj, &p2, v), v)) .collect(); vsep_order.sort_by_key(|&(cost, _)| cost); - for (_, v) in vsep_order { - if vsep_updated(num_vertices, edges, &p2, v) < best.vsep() { - let extended = extend(num_vertices, edges, &p2, v); + for (cost, v) in vsep_order { + if cost < best.vsep() { + let extended = extend(adj, &p2, v); let l3 = branch_and_bound_internal( + adj, num_vertices, - edges, extended, best.clone(), visited, diff --git a/src/solvers/brute_force.rs b/src/solvers/brute_force.rs index aa52cf07..05fcf192 100644 --- a/src/solvers/brute_force.rs +++ b/src/solvers/brute_force.rs @@ -17,8 +17,11 @@ impl BruteForce { Self } - /// Internal: find all optimal solutions. - fn find_all_best(&self, problem: &P) -> Vec> { + /// Find all optimal solutions for an optimization problem. + /// + /// Returns all configurations that achieve the optimal metric value. + /// Returns empty vec if all configurations are invalid. + pub fn find_all_best(&self, problem: &P) -> Vec> { let iter = DimsIterator::new(problem.dims()); let direction = problem.direction(); let mut best_solutions: Vec> = vec![]; @@ -73,8 +76,8 @@ impl BruteForce { } impl Solver for BruteForce { - fn find_best(&self, problem: &P) -> Vec> { - self.find_all_best(problem) + fn find_best(&self, problem: &P) -> Option> { + self.find_all_best(problem).into_iter().next() } fn find_satisfying>(&self, problem: &P) -> Option> { diff --git a/src/solvers/mod.rs b/src/solvers/mod.rs index ad03808f..5d5bf0a7 100644 --- a/src/solvers/mod.rs +++ b/src/solvers/mod.rs @@ -14,11 +14,11 @@ use crate::traits::{OptimizationProblem, Problem}; /// Trait for problem solvers. pub trait Solver { - /// Find best solution(s) for an optimization problem. + /// Find one optimal solution for an optimization problem. /// - /// Returns all configurations that achieve the optimal metric value. - /// Returns empty vec if all configurations are invalid. - fn find_best(&self, problem: &P) -> Vec>; + /// Returns a single configuration that achieves the optimal metric value, + /// or `None` if no feasible configuration exists. + fn find_best(&self, problem: &P) -> Option>; /// Find any satisfying solution for a satisfaction problem (Metric = bool). fn find_satisfying>(&self, problem: &P) -> Option>; diff --git a/src/testing/macros.rs b/src/testing/macros.rs index 07fc9a6b..ef35ca70 100644 --- a/src/testing/macros.rs +++ b/src/testing/macros.rs @@ -78,7 +78,7 @@ macro_rules! graph_problem_tests { if $n <= 15 { let problem = create_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // All solutions should have the same (optimal) value if solutions.len() > 1 { @@ -150,8 +150,8 @@ macro_rules! complement_test { let problem_b = <$prob_b>::new(n, edges); let solver = BruteForce::new(); - let solutions_a = solver.find_best(&problem_a); - let solutions_b = solver.find_best(&problem_b); + let solutions_a = solver.find_all_best(&problem_a); + let solutions_b = solver.find_all_best(&problem_b); // Get optimal sizes (count of selected vertices) let size_a: usize = solutions_a[0].iter().sum(); diff --git a/src/types.rs b/src/types.rs index 699f079f..64f5346f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -52,7 +52,7 @@ impl NumericSize for T where /// Trait for weight storage. Separates weight storage from objective value type. pub trait Weights: Clone + 'static { - /// Name for variant metadata (e.g., "Unweighted", "Weighted"). + /// Name for variant metadata (e.g., "Unweighted", "`Weighted`"). const NAME: &'static str; /// The objective/metric type derived from these weights. type Size: NumericSize; diff --git a/src/unit_tests/graph_models.rs b/src/unit_tests/graph_models.rs index ddb54f91..e04ce888 100644 --- a/src/unit_tests/graph_models.rs +++ b/src/unit_tests/graph_models.rs @@ -97,7 +97,7 @@ mod maximum_independent_set { MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // All solutions should have exactly 1 vertex selected assert_eq!(solutions.len(), 3); // Three equivalent solutions for sol in &solutions { @@ -112,7 +112,7 @@ mod maximum_independent_set { MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Maximum size is 2 for sol in &solutions { let size: usize = sol.iter().sum(); @@ -128,7 +128,7 @@ mod maximum_independent_set { let problem = MaximumIndependentSet::with_weights(3, vec![(0, 1), (1, 2)], vec![1, 100, 1]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); // Should select vertex 1 (weight 100) over vertices 0+2 (weight 2) assert_eq!(solutions[0], vec![0, 1, 0]); @@ -178,7 +178,7 @@ mod maximum_independent_set { let problem = MaximumIndependentSet::::new(3, vec![]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); // All vertices can be selected assert_eq!(solutions[0], vec![1, 1, 1]); @@ -247,7 +247,7 @@ mod minimum_vertex_cover { let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![0, 1, 0]); } @@ -258,7 +258,7 @@ mod minimum_vertex_cover { let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // There are 3 minimum covers of size 2 assert_eq!(solutions.len(), 3); for sol in &solutions { @@ -274,7 +274,7 @@ mod minimum_vertex_cover { let problem = MinimumVertexCover::with_weights(3, vec![(0, 1), (1, 2)], vec![100, 1, 100]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); // Should select vertex 1 (weight 1) instead of 0 and 2 (total 200) assert_eq!(solutions[0], vec![0, 1, 0]); @@ -307,7 +307,7 @@ mod minimum_vertex_cover { let problem = MinimumVertexCover::::new(3, vec![]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // No edges means empty cover is valid and optimal assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![0, 0, 0]); @@ -318,7 +318,7 @@ mod minimum_vertex_cover { let problem = MinimumVertexCover::::new(2, vec![(0, 1)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Either vertex covers the single edge assert_eq!(solutions.len(), 2); } @@ -344,7 +344,7 @@ mod minimum_vertex_cover { let solver = BruteForce::new(); - let is_solutions = solver.find_best(&is_problem); + let is_solutions = solver.find_all_best(&is_problem); for is_sol in &is_solutions { // Complement should be a valid vertex cover let vc_config: Vec = is_sol.iter().map(|&x| 1 - x).collect(); diff --git a/src/unit_tests/models/graph/max_cut.rs b/src/unit_tests/models/graph/max_cut.rs index 669b2364..4de09c43 100644 --- a/src/unit_tests/models/graph/max_cut.rs +++ b/src/unit_tests/models/graph/max_cut.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::types::SolutionSize; #[test] @@ -42,7 +42,7 @@ fn test_brute_force_triangle() { let problem = MaxCut::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); for sol in &solutions { let size = problem.evaluate(sol); assert_eq!(size, SolutionSize::Valid(2)); @@ -57,7 +57,7 @@ fn test_brute_force_path() { let problem = MaxCut::::unweighted(3, vec![(0, 1), (1, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); for sol in &solutions { let size = problem.evaluate(sol); assert_eq!(size, SolutionSize::Valid(2)); @@ -72,7 +72,7 @@ fn test_brute_force_weighted() { let problem = MaxCut::::new(3, vec![(0, 1, 10), (1, 2, 1)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Max is 11 (cut both edges) with partition like [0,1,0] or [1,0,1] for sol in &solutions { let size = problem.evaluate(sol); @@ -127,7 +127,7 @@ fn test_empty_graph() { let problem = MaxCut::::unweighted(3, vec![]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Any partition gives cut size 0 assert!(!solutions.is_empty()); for sol in &solutions { @@ -142,7 +142,7 @@ fn test_single_edge() { let problem = MaxCut::::new(2, vec![(0, 1, 5)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Putting vertices in different sets maximizes cut assert_eq!(solutions.len(), 2); // [0,1] and [1,0] for sol in &solutions { @@ -161,7 +161,7 @@ fn test_complete_graph_k4() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Max cut in K4 is 4 (2-2 partition) for sol in &solutions { assert_eq!(problem.evaluate(sol), SolutionSize::Valid(4)); @@ -176,7 +176,7 @@ fn test_bipartite_graph() { let problem = MaxCut::::unweighted(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Bipartite graph can achieve max cut = all edges for sol in &solutions { assert_eq!(problem.evaluate(sol), SolutionSize::Valid(4)); diff --git a/src/unit_tests/models/graph/maximal_is.rs b/src/unit_tests/models/graph/maximal_is.rs index 07f800e4..417b21cd 100644 --- a/src/unit_tests/models/graph/maximal_is.rs +++ b/src/unit_tests/models/graph/maximal_is.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::types::SolutionSize; #[test] @@ -79,7 +79,7 @@ fn test_brute_force_path() { let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Largest maximal IS is {0, 2} with size 2 assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![1, 0, 1]); @@ -92,7 +92,7 @@ fn test_brute_force_triangle() { let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // All maximal IS have size 1 (any single vertex) assert_eq!(solutions.len(), 3); for sol in &solutions { @@ -130,7 +130,7 @@ fn test_empty_graph() { let problem = MaximalIS::::new(3, vec![]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Only maximal IS is all vertices assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![1, 1, 1]); @@ -203,7 +203,7 @@ fn test_weighted_solution() { MaximalIS::::with_weights(3, vec![(0, 1), (1, 2)], vec![10, 100, 10]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Should prefer {1} with weight 100 over {0, 2} with weight 20 // With LargerSizeIsBetter, {1} with 100 > {0, 2} with 20 assert_eq!(solutions.len(), 1); diff --git a/src/unit_tests/models/graph/maximum_clique.rs b/src/unit_tests/models/graph/maximum_clique.rs index 493000d0..d863c99d 100644 --- a/src/unit_tests/models/graph/maximum_clique.rs +++ b/src/unit_tests/models/graph/maximum_clique.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::types::SolutionSize; #[test] @@ -94,7 +94,7 @@ fn test_brute_force_triangle() { let problem = MaximumClique::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![1, 1, 1]); } @@ -107,7 +107,7 @@ fn test_brute_force_path() { let problem = MaximumClique::::new(3, vec![(0, 1), (1, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Maximum size is 2 for sol in &solutions { let size: usize = sol.iter().sum(); @@ -126,7 +126,7 @@ fn test_brute_force_weighted() { MaximumClique::::with_weights(3, vec![(0, 1), (1, 2)], vec![1, 100, 1]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Should select {0, 1} (weight 101) or {1, 2} (weight 101) assert!(solutions.len() == 2); for sol in &solutions { @@ -178,7 +178,7 @@ fn test_empty_graph() { let problem = MaximumClique::::new(3, vec![]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 3); // Each solution should have exactly one vertex selected for sol in &solutions { @@ -244,7 +244,7 @@ fn test_complete_graph() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![1, 1, 1, 1]); // All vertices form a clique } diff --git a/src/unit_tests/models/graph/maximum_independent_set.rs b/src/unit_tests/models/graph/maximum_independent_set.rs index 03a330db..a841d7e5 100644 --- a/src/unit_tests/models/graph/maximum_independent_set.rs +++ b/src/unit_tests/models/graph/maximum_independent_set.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -80,7 +80,7 @@ fn test_brute_force_triangle() { let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // All solutions should have exactly 1 vertex selected assert_eq!(solutions.len(), 3); // Three equivalent solutions for sol in &solutions { @@ -94,7 +94,7 @@ fn test_brute_force_path() { let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Maximum size is 2 for sol in &solutions { let size: usize = sol.iter().sum(); @@ -115,7 +115,7 @@ fn test_brute_force_weighted() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); // Should select vertex 1 (weight 100) over vertices 0+2 (weight 2) assert_eq!(solutions[0], vec![0, 1, 0]); @@ -130,7 +130,7 @@ fn test_brute_force_weighted_f64() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions, vec![vec![0, 1, 0]]); assert_eq!(problem.evaluate(&solutions[0]), SolutionSize::Valid(2.0)); } @@ -179,7 +179,7 @@ fn test_empty_graph() { let problem = MaximumIndependentSet::::new(3, vec![]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); // All vertices can be selected assert_eq!(solutions[0], vec![1, 1, 1]); diff --git a/src/unit_tests/models/graph/maximum_matching.rs b/src/unit_tests/models/graph/maximum_matching.rs index 10638b16..309ef4da 100644 --- a/src/unit_tests/models/graph/maximum_matching.rs +++ b/src/unit_tests/models/graph/maximum_matching.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -59,7 +59,7 @@ fn test_brute_force_path() { let problem = MaximumMatching::::unweighted(4, vec![(0, 1), (1, 2), (2, 3)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Maximum matching has 2 edges: {0-1, 2-3} assert!(solutions.contains(&vec![1, 0, 1])); for sol in &solutions { @@ -72,7 +72,7 @@ fn test_brute_force_triangle() { let problem = MaximumMatching::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Maximum matching has 1 edge (any of the 3) for sol in &solutions { assert_eq!(sol.iter().sum::(), 1); @@ -88,7 +88,7 @@ fn test_brute_force_weighted() { MaximumMatching::::new(4, vec![(0, 1, 100), (0, 2, 1), (1, 3, 1)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Edge 0-1 (weight 100) alone beats edges 0-2 + 1-3 (weight 2) assert!(solutions.contains(&vec![1, 0, 0])); } @@ -132,7 +132,7 @@ fn test_perfect_matching() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Perfect matching has 2 edges for sol in &solutions { assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(2)); diff --git a/src/unit_tests/models/graph/minimum_dominating_set.rs b/src/unit_tests/models/graph/minimum_dominating_set.rs index c7916e7f..9863b806 100644 --- a/src/unit_tests/models/graph/minimum_dominating_set.rs +++ b/src/unit_tests/models/graph/minimum_dominating_set.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -65,7 +65,7 @@ fn test_brute_force_star() { let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (0, 2), (0, 3)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(solutions.contains(&vec![1, 0, 0, 0])); for sol in &solutions { assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(1)); @@ -79,7 +79,7 @@ fn test_brute_force_path() { MinimumDominatingSet::::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Minimum is 2 (e.g., vertices 1 and 3) for sol in &solutions { assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(2)); @@ -98,7 +98,7 @@ fn test_brute_force_weighted() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Prefer selecting all leaves (3) over center (100) assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![0, 1, 1, 1]); @@ -130,7 +130,7 @@ fn test_isolated_vertex() { let problem = MinimumDominatingSet::::new(3, vec![(0, 1)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Vertex 2 is isolated, must be selected for sol in &solutions { assert_eq!(sol[2], 1); diff --git a/src/unit_tests/models/graph/minimum_vertex_cover.rs b/src/unit_tests/models/graph/minimum_vertex_cover.rs index c892877f..a4662b86 100644 --- a/src/unit_tests/models/graph/minimum_vertex_cover.rs +++ b/src/unit_tests/models/graph/minimum_vertex_cover.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -46,7 +46,7 @@ fn test_brute_force_path() { let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![0, 1, 0]); } @@ -57,7 +57,7 @@ fn test_brute_force_triangle() { let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // There are 3 minimum covers of size 2 assert_eq!(solutions.len(), 3); for sol in &solutions { @@ -77,7 +77,7 @@ fn test_brute_force_weighted() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); // Should select vertex 1 (weight 1) instead of 0 and 2 (total 200) assert_eq!(solutions[0], vec![0, 1, 0]); @@ -110,7 +110,7 @@ fn test_empty_graph() { let problem = MinimumVertexCover::::new(3, vec![]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // No edges means empty cover is valid and optimal assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![0, 0, 0]); @@ -121,7 +121,7 @@ fn test_single_edge() { let problem = MinimumVertexCover::::new(2, vec![(0, 1)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Either vertex covers the single edge assert_eq!(solutions.len(), 2); } @@ -137,7 +137,7 @@ fn test_complement_relationship() { let solver = BruteForce::new(); - let is_solutions = solver.find_best(&is_problem); + let is_solutions = solver.find_all_best(&is_problem); for is_sol in &is_solutions { // Complement should be a valid vertex cover let vc_config: Vec = is_sol.iter().map(|&x| 1 - x).collect(); diff --git a/src/unit_tests/models/optimization/ilp.rs b/src/unit_tests/models/optimization/ilp.rs index 18b60674..7e731570 100644 --- a/src/unit_tests/models/optimization/ilp.rs +++ b/src/unit_tests/models/optimization/ilp.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -367,7 +367,7 @@ fn test_ilp_brute_force_maximization() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&ilp); + let solutions = solver.find_all_best(&ilp); // Optimal: x1=1, x0=0 => objective = 2 assert_eq!(solutions.len(), 1); @@ -385,7 +385,7 @@ fn test_ilp_brute_force_minimization() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&ilp); + let solutions = solver.find_all_best(&ilp); // Optimal: x0=1,x1=0 or x0=0,x1=1 => objective = 1 assert_eq!(solutions.len(), 2); @@ -408,7 +408,7 @@ fn test_ilp_brute_force_no_feasible() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&ilp); + let solutions = solver.find_all_best(&ilp); // All solutions are infeasible - BruteForce should return empty list assert!(solutions.is_empty(), "Expected no solutions for infeasible ILP"); @@ -432,7 +432,7 @@ fn test_ilp_unconstrained() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&ilp); + let solutions = solver.find_all_best(&ilp); // Optimal: both = 1 assert_eq!(solutions.len(), 1); @@ -450,7 +450,7 @@ fn test_ilp_equality_constraint() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&ilp); + let solutions = solver.find_all_best(&ilp); // Optimal: x0=0, x1=1 => objective = 0 assert_eq!(solutions.len(), 1); @@ -474,7 +474,7 @@ fn test_ilp_multiple_constraints() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&ilp); + let solutions = solver.find_all_best(&ilp); // Optimal: x0=1, x1=0, x2=1 => objective = 2 assert_eq!(solutions.len(), 1); diff --git a/src/unit_tests/models/optimization/qubo.rs b/src/unit_tests/models/optimization/qubo.rs index 520ad2f2..9b136c76 100644 --- a/src/unit_tests/models/optimization/qubo.rs +++ b/src/unit_tests/models/optimization/qubo.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -40,7 +40,7 @@ fn test_brute_force_minimize() { let problem = QUBO::from_matrix(vec![vec![1.0, 0.0], vec![0.0, -2.0]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![0, 1]); assert_eq!(Problem::evaluate(&problem, &solutions[0]), SolutionSize::Valid(-2.0)); @@ -54,7 +54,7 @@ fn test_brute_force_with_interaction() { let problem = QUBO::from_matrix(vec![vec![-1.0, 2.0], vec![0.0, -1.0]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Minimum is -1 at [1,0] or [0,1] assert_eq!(solutions.len(), 2); for sol in &solutions { @@ -98,7 +98,7 @@ fn test_single_variable() { let problem = QUBO::from_matrix(vec![vec![-5.0]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![1]); // x=1 gives -5, x=0 gives 0 } diff --git a/src/unit_tests/models/optimization/spin_glass.rs b/src/unit_tests/models/optimization/spin_glass.rs index 2005ac8c..c075722e 100644 --- a/src/unit_tests/models/optimization/spin_glass.rs +++ b/src/unit_tests/models/optimization/spin_glass.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -85,7 +85,7 @@ fn test_brute_force_ferromagnetic() { let problem = SpinGlass::::new(2, vec![((0, 1), 1.0)], vec![0.0, 0.0]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Minimum energy is -1 (anti-aligned) for sol in &solutions { assert_ne!(sol[0], sol[1]); @@ -100,7 +100,7 @@ fn test_brute_force_antiferromagnetic() { let problem = SpinGlass::::new(2, vec![((0, 1), -1.0)], vec![0.0, 0.0]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Minimum energy is -1 (aligned) for sol in &solutions { assert_eq!(sol[0], sol[1]); @@ -130,7 +130,7 @@ fn test_triangle_frustration() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Best we can do is satisfy 2 out of 3 interactions // Energy = -1 -1 + 1 = -1 (one frustrated) for sol in &solutions { diff --git a/src/unit_tests/models/set/maximum_set_packing.rs b/src/unit_tests/models/set/maximum_set_packing.rs index 2e8f2834..2bbda678 100644 --- a/src/unit_tests/models/set/maximum_set_packing.rs +++ b/src/unit_tests/models/set/maximum_set_packing.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -60,7 +60,7 @@ fn test_brute_force_chain() { let problem = MaximumSetPacking::::new(vec![vec![0, 1], vec![1, 2], vec![2, 3]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Max is 2: select {0,1} and {2,3} for sol in &solutions { assert_eq!(sol.iter().sum::(), 2); @@ -78,7 +78,7 @@ fn test_brute_force_weighted() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Should select sets 1 and 2 (total 6) over set 0 (total 5) assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![0, 1, 1]); @@ -105,7 +105,7 @@ fn test_disjoint_sets() { let problem = MaximumSetPacking::::new(vec![vec![0], vec![1], vec![2], vec![3]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // All sets are disjoint, so select all assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![1, 1, 1, 1]); @@ -117,7 +117,7 @@ fn test_all_overlapping() { let problem = MaximumSetPacking::::new(vec![vec![0, 1], vec![0, 2], vec![0, 3]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Can only select one set for sol in &solutions { assert_eq!(sol.iter().sum::(), 1); @@ -154,8 +154,8 @@ fn test_relationship_to_independent_set() { let solver = BruteForce::new(); - let sp_solutions = solver.find_best(&sp_problem); - let is_solutions = solver.find_best(&is_problem); + let sp_solutions = solver.find_all_best(&sp_problem); + let is_solutions = solver.find_all_best(&is_problem); // Should have same optimal value let sp_size: usize = sp_solutions[0].iter().sum(); diff --git a/src/unit_tests/models/set/minimum_set_covering.rs b/src/unit_tests/models/set/minimum_set_covering.rs index 51834261..bbcbc701 100644 --- a/src/unit_tests/models/set/minimum_set_covering.rs +++ b/src/unit_tests/models/set/minimum_set_covering.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -62,7 +62,7 @@ fn test_brute_force_simple() { let problem = MinimumSetCovering::::new(3, vec![vec![0, 1], vec![1, 2], vec![0, 2]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); for sol in &solutions { assert_eq!(sol.iter().sum::(), 2); // Verify it's a valid cover @@ -80,7 +80,7 @@ fn test_brute_force_weighted() { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Should select sets 1 and 2 (total 6) instead of set 0 (total 10) assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![0, 1, 1]); @@ -115,7 +115,7 @@ fn test_single_set_covers_all() { let problem = MinimumSetCovering::::new(3, vec![vec![0, 1, 2], vec![0], vec![1], vec![2]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // First set alone covers everything assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![1, 0, 0, 0]); @@ -127,7 +127,7 @@ fn test_overlapping_sets() { let problem = MinimumSetCovering::::new(3, vec![vec![0, 1], vec![1, 2], vec![1]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Minimum is selecting first two sets for sol in &solutions { assert_eq!(sol.iter().sum::(), 2); diff --git a/src/unit_tests/models/specialized/biclique_cover.rs b/src/unit_tests/models/specialized/biclique_cover.rs index b8e0fe70..5f58c15e 100644 --- a/src/unit_tests/models/specialized/biclique_cover.rs +++ b/src/unit_tests/models/specialized/biclique_cover.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -78,7 +78,7 @@ fn test_brute_force_simple() { let problem = BicliqueCover::new(2, 2, vec![(0, 2)], 1); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); for sol in &solutions { assert!(problem.is_valid_cover(sol)); // Minimum size is 2 (one left, one right vertex) @@ -93,7 +93,7 @@ fn test_brute_force_two_bicliques() { let problem = BicliqueCover::new(2, 2, vec![(0, 2), (1, 3)], 2); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); for sol in &solutions { assert!(problem.is_valid_cover(sol)); } diff --git a/src/unit_tests/models/specialized/bmf.rs b/src/unit_tests/models/specialized/bmf.rs index 80808a86..d68b12ed 100644 --- a/src/unit_tests/models/specialized/bmf.rs +++ b/src/unit_tests/models/specialized/bmf.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -92,7 +92,7 @@ fn test_brute_force_ones() { let problem = BMF::new(matrix, 1); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); for sol in &solutions { // Exact factorization has distance 0 assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(0)); @@ -106,7 +106,7 @@ fn test_brute_force_identity() { let problem = BMF::new(matrix, 2); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Should find exact factorization for sol in &solutions { assert!(problem.is_exact(sol)); @@ -120,7 +120,7 @@ fn test_brute_force_insufficient_rank() { let problem = BMF::new(matrix, 1); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Best approximation has distance > 0 let best_distance = problem.hamming_distance(&solutions[0]); // With rank 1, best we can do is distance 1 (all ones or all zeros except one) diff --git a/src/unit_tests/models/specialized/factoring.rs b/src/unit_tests/models/specialized/factoring.rs index cc577e89..a328894e 100644 --- a/src/unit_tests/models/specialized/factoring.rs +++ b/src/unit_tests/models/specialized/factoring.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -66,7 +66,7 @@ fn test_brute_force_factor_6() { let problem = Factoring::new(2, 2, 6); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Should find 2*3 and 3*2 assert!(!solutions.is_empty()); for sol in &solutions { @@ -80,7 +80,7 @@ fn test_brute_force_factor_15() { let problem = Factoring::new(3, 3, 15); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Should find 3*5, 5*3, 1*15, 15*1 for sol in &solutions { let (a, b) = problem.read_factors(sol); @@ -94,7 +94,7 @@ fn test_brute_force_prime() { let problem = Factoring::new(3, 3, 7); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); let factor_pairs: Vec<_> = solutions.iter().map(|s| problem.read_factors(s)).collect(); // Should find at least one of (1,7) or (7,1) @@ -128,7 +128,7 @@ fn test_factor_one() { let problem = Factoring::new(2, 2, 1); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); for sol in &solutions { let (a, b) = problem.read_factors(sol); assert_eq!(a * b, 1); diff --git a/src/unit_tests/models/specialized/paintshop.rs b/src/unit_tests/models/specialized/paintshop.rs index 0cd23e56..bab8b635 100644 --- a/src/unit_tests/models/specialized/paintshop.rs +++ b/src/unit_tests/models/specialized/paintshop.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; @@ -62,7 +62,7 @@ fn test_brute_force_simple() { let problem = PaintShop::new(vec!["a", "b", "a", "b"]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Optimal has 1 switch: [0,0] or [1,1] for sol in &solutions { assert_eq!(problem.count_switches(sol), 1); @@ -75,7 +75,7 @@ fn test_brute_force_longer() { let problem = PaintShop::new(vec!["a", "b", "a", "c", "c", "b"]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Find the minimum number of switches let min_switches = problem.count_switches(&solutions[0]); for sol in &solutions { @@ -102,7 +102,7 @@ fn test_single_car() { let problem = PaintShop::new(vec!["a", "a"]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Both configs give 1 switch: a(0)->a(1) or a(1)->a(0) assert_eq!(solutions.len(), 2); for sol in &solutions { @@ -116,7 +116,7 @@ fn test_adjacent_same_car() { let problem = PaintShop::new(vec!["a", "a", "b", "b"]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Best case: [0,0] -> [0,1,0,1] = 3 switches, or [0,1] -> [0,1,1,0] = 2 switches // Actually: [0,0] -> a=0,a=1,b=0,b=1 = [0,1,0,1] = 3 switches // [0,1] -> a=0,a=1,b=1,b=0 = [0,1,1,0] = 2 switches diff --git a/src/unit_tests/property.rs b/src/unit_tests/property.rs index 21a6d5a7..ad475b37 100644 --- a/src/unit_tests/property.rs +++ b/src/unit_tests/property.rs @@ -44,8 +44,8 @@ proptest! { let vc_problem = MinimumVertexCover::::new(n, edges); let solver = BruteForce::new(); - let is_solutions = solver.find_best(&is_problem); - let vc_solutions = solver.find_best(&vc_problem); + let is_solutions = solver.find_all_best(&is_problem); + let vc_solutions = solver.find_all_best(&vc_problem); let is_size: usize = is_solutions[0].iter().sum(); let vc_size: usize = vc_solutions[0].iter().sum(); @@ -60,7 +60,7 @@ proptest! { let problem = MaximumIndependentSet::::new(n, edges); let solver = BruteForce::new(); - for sol in solver.find_best(&problem) { + for sol in solver.find_all_best(&problem) { // Any subset of an IS is also an IS for i in 0..n { let mut subset = sol.clone(); @@ -77,7 +77,7 @@ proptest! { let problem = MinimumVertexCover::::new(n, edges); let solver = BruteForce::new(); - for sol in solver.find_best(&problem) { + for sol in solver.find_all_best(&problem) { // Adding any vertex to a VC still gives a valid VC for i in 0..n { let mut superset = sol.clone(); @@ -96,7 +96,7 @@ proptest! { let solver = BruteForce::new(); // Get all valid independent sets (not just optimal) - for sol in solver.find_best(&is_problem) { + for sol in solver.find_all_best(&is_problem) { // The complement should be a valid vertex cover let complement: Vec = sol.iter().map(|&x| 1 - x).collect(); prop_assert!(vc_problem.evaluate(&complement).is_valid(), @@ -129,7 +129,7 @@ proptest! { let problem = MaximumIndependentSet::::new(n, edges); let solver = BruteForce::new(); - for sol in solver.find_best(&problem) { + for sol in solver.find_all_best(&problem) { let metric = problem.evaluate(&sol); // Valid solutions have non-negative size prop_assert!(metric.is_valid()); diff --git a/src/unit_tests/rules/circuit_spinglass.rs b/src/unit_tests/rules/circuit_spinglass.rs index 31cd367f..da30a78e 100644 --- a/src/unit_tests/rules/circuit_spinglass.rs +++ b/src/unit_tests/rules/circuit_spinglass.rs @@ -1,6 +1,6 @@ use super::*; use crate::models::specialized::Circuit; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::types::NumericSize; /// Verify a gadget has the correct ground states. @@ -18,7 +18,7 @@ where + NumericSize, { let solver = BruteForce::new(); - let solutions = solver.find_best(&gadget.problem); + let solutions = solver.find_all_best(&gadget.problem); // For each expected input/output pair, verify there's a matching ground state for (inputs, outputs) in expected { @@ -117,7 +117,7 @@ fn test_set0_gadget() { assert_eq!(gadget.outputs, vec![0]); let solver = BruteForce::new(); - let solutions = solver.find_best(&gadget.problem); + let solutions = solver.find_all_best(&gadget.problem); // Ground state should be spin down (0) assert!(solutions.contains(&vec![0])); assert!(!solutions.contains(&vec![1])); @@ -131,7 +131,7 @@ fn test_set1_gadget() { assert_eq!(gadget.outputs, vec![0]); let solver = BruteForce::new(); - let solutions = solver.find_best(&gadget.problem); + let solutions = solver.find_all_best(&gadget.problem); // Ground state should be spin up (1) assert!(solutions.contains(&vec![1])); assert!(!solutions.contains(&vec![0])); @@ -149,7 +149,7 @@ fn test_simple_and_circuit() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(sg); + let solutions = solver.find_all_best(sg); // Extract and verify solutions let extracted: Vec> = solutions @@ -188,7 +188,7 @@ fn test_simple_or_circuit() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(sg); + let solutions = solver.find_all_best(sg); let extracted: Vec> = solutions .iter() @@ -225,7 +225,7 @@ fn test_not_circuit() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(sg); + let solutions = solver.find_all_best(sg); let extracted: Vec> = solutions .iter() @@ -260,7 +260,7 @@ fn test_xor_circuit() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(sg); + let solutions = solver.find_all_best(sg); let extracted: Vec> = solutions .iter() @@ -297,7 +297,7 @@ fn test_constant_true() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(sg); + let solutions = solver.find_all_best(sg); let extracted: Vec> = solutions .iter() @@ -324,7 +324,7 @@ fn test_constant_false() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(sg); + let solutions = solver.find_all_best(sg); let extracted: Vec> = solutions .iter() @@ -355,7 +355,7 @@ fn test_multi_input_and() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(sg); + let solutions = solver.find_all_best(sg); let extracted: Vec> = solutions .iter() @@ -396,7 +396,7 @@ fn test_chained_circuit() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(sg); + let solutions = solver.find_all_best(sg); let extracted: Vec> = solutions .iter() @@ -444,7 +444,7 @@ fn test_nested_expression() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(sg); + let solutions = solver.find_all_best(sg); let extracted: Vec> = solutions .iter() diff --git a/src/unit_tests/rules/coloring_qubo.rs b/src/unit_tests/rules/coloring_qubo.rs index bf4cddb4..e353e398 100644 --- a/src/unit_tests/rules/coloring_qubo.rs +++ b/src/unit_tests/rules/coloring_qubo.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::Problem; #[test] @@ -10,7 +10,7 @@ fn test_kcoloring_to_qubo_closed_loop() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // All solutions should extract to valid colorings for sol in &qubo_solutions { @@ -30,7 +30,7 @@ fn test_kcoloring_to_qubo_path() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -50,7 +50,7 @@ fn test_kcoloring_to_qubo_reversed_edges() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); diff --git a/src/unit_tests/rules/factoring_ilp.rs b/src/unit_tests/rules/factoring_ilp.rs index f78a298e..e8db96f5 100644 --- a/src/unit_tests/rules/factoring_ilp.rs +++ b/src/unit_tests/rules/factoring_ilp.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, ILPSolver, Solver}; +use crate::solvers::{BruteForce, ILPSolver}; #[test] fn test_reduction_creates_valid_ilp() { @@ -177,7 +177,7 @@ fn test_ilp_matches_brute_force() { // Get brute force solutions let bf = BruteForce::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); // ILP solution should be among brute force solutions let (a, b) = problem.read_factors(&ilp_factors); diff --git a/src/unit_tests/rules/ilp_qubo.rs b/src/unit_tests/rules/ilp_qubo.rs index b3781535..c3568f01 100644 --- a/src/unit_tests/rules/ilp_qubo.rs +++ b/src/unit_tests/rules/ilp_qubo.rs @@ -1,6 +1,6 @@ use super::*; use crate::models::optimization::{LinearConstraint, ObjectiveSense}; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::Problem; #[test] @@ -21,7 +21,7 @@ fn test_ilp_to_qubo_closed_loop() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -49,7 +49,7 @@ fn test_ilp_to_qubo_minimize() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -79,7 +79,7 @@ fn test_ilp_to_qubo_equality() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Should have exactly 3 optimal solutions (C(3,2)) assert_eq!(qubo_solutions.len(), 3); @@ -113,7 +113,7 @@ fn test_ilp_to_qubo_ge_with_slack() { assert_eq!(qubo.num_variables(), 5); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -147,7 +147,7 @@ fn test_ilp_to_qubo_le_with_slack() { assert_eq!(qubo.num_variables(), 5); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); diff --git a/src/unit_tests/rules/ksatisfiability_qubo.rs b/src/unit_tests/rules/ksatisfiability_qubo.rs index ba9156a4..d8df1cb1 100644 --- a/src/unit_tests/rules/ksatisfiability_qubo.rs +++ b/src/unit_tests/rules/ksatisfiability_qubo.rs @@ -1,6 +1,6 @@ use super::*; use crate::models::satisfiability::CNFClause; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::Problem; #[test] @@ -20,7 +20,7 @@ fn test_ksatisfiability_to_qubo_closed_loop() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Verify all solutions satisfy all clauses for sol in &qubo_solutions { @@ -37,7 +37,7 @@ fn test_ksatisfiability_to_qubo_simple() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -61,7 +61,7 @@ fn test_ksatisfiability_to_qubo_contradiction() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Both x=0 and x=1 satisfy exactly 1 clause assert_eq!(qubo_solutions.len(), 2); @@ -82,7 +82,7 @@ fn test_ksatisfiability_to_qubo_reversed_vars() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -125,7 +125,7 @@ fn test_k3satisfiability_to_qubo_closed_loop() { assert_eq!(qubo.num_variables(), 12); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Verify all extracted solutions maximize satisfied clauses for sol in &qubo_solutions { @@ -148,7 +148,7 @@ fn test_k3satisfiability_to_qubo_single_clause() { assert_eq!(qubo.num_variables(), 4); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // All solutions should satisfy the single clause for sol in &qubo_solutions { @@ -168,7 +168,7 @@ fn test_k3satisfiability_to_qubo_all_negated() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); diff --git a/src/unit_tests/rules/maximumindependentset_ilp.rs b/src/unit_tests/rules/maximumindependentset_ilp.rs index 51dac2af..7d12ba13 100644 --- a/src/unit_tests/rules/maximumindependentset_ilp.rs +++ b/src/unit_tests/rules/maximumindependentset_ilp.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, ILPSolver, Solver}; +use crate::solvers::{BruteForce, ILPSolver}; use crate::traits::Problem; use crate::types::SolutionSize; @@ -58,7 +58,7 @@ fn test_ilp_solution_equals_brute_force_triangle() { let ilp_solver = ILPSolver::new(); // Solve with brute force on original problem - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); // Solve via ILP reduction let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); @@ -85,7 +85,7 @@ fn test_ilp_solution_equals_brute_force_path() { let ilp_solver = ILPSolver::new(); // Solve with brute force - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_size: usize = bf_solutions[0].iter().sum(); // Solve via ILP @@ -113,7 +113,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_obj = problem.evaluate(&bf_solutions[0]); let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); diff --git a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs index b8254d55..9c47e01e 100644 --- a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs +++ b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; #[test] fn test_is_to_setpacking() { @@ -10,7 +10,7 @@ fn test_is_to_setpacking() { let sp_problem = reduction.target_problem(); let solver = BruteForce::new(); - let sp_solutions = solver.find_best(sp_problem); + let sp_solutions = solver.find_all_best(sp_problem); // Extract back let is_solutions: Vec<_> = sp_solutions @@ -39,7 +39,7 @@ fn test_setpacking_to_is() { let is_problem = reduction.target_problem(); let solver = BruteForce::new(); - let is_solutions = solver.find_best(is_problem); + let is_solutions = solver.find_all_best(is_problem); // Max packing = 2 (sets 0 and 1) for sol in &is_solutions { @@ -52,7 +52,7 @@ fn test_setpacking_to_is() { fn test_roundtrip_is_sp_is() { let original = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let solver = BruteForce::new(); - let original_solutions = solver.find_best(&original); + let original_solutions = solver.find_all_best(&original); // IS -> SP -> IS let reduction1 = ReduceTo::>::reduce_to(&original); @@ -61,7 +61,7 @@ fn test_roundtrip_is_sp_is() { ReduceTo::>::reduce_to(&sp); let roundtrip = reduction2.target_problem(); - let roundtrip_solutions = solver.find_best(roundtrip); + let roundtrip_solutions = solver.find_all_best(roundtrip); // Solutions should have same objective value let orig_size: usize = original_solutions[0].iter().sum(); @@ -90,7 +90,7 @@ fn test_empty_graph() { assert_eq!(sp_problem.num_sets(), 3); let solver = BruteForce::new(); - let solutions = solver.find_best(sp_problem); + let solutions = solver.find_all_best(sp_problem); // With no overlaps, we can select all sets assert_eq!(solutions[0].iter().sum::(), 3); diff --git a/src/unit_tests/rules/maximumindependentset_qubo.rs b/src/unit_tests/rules/maximumindependentset_qubo.rs index f26625c5..2065d9b9 100644 --- a/src/unit_tests/rules/maximumindependentset_qubo.rs +++ b/src/unit_tests/rules/maximumindependentset_qubo.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::Problem; #[test] @@ -11,7 +11,7 @@ fn test_independentset_to_qubo_closed_loop() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -29,7 +29,7 @@ fn test_independentset_to_qubo_triangle() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -46,7 +46,7 @@ fn test_independentset_to_qubo_empty_graph() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); diff --git a/src/unit_tests/rules/maximummatching_ilp.rs b/src/unit_tests/rules/maximummatching_ilp.rs index 856f6123..f5464c80 100644 --- a/src/unit_tests/rules/maximummatching_ilp.rs +++ b/src/unit_tests/rules/maximummatching_ilp.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, ILPSolver, Solver}; +use crate::solvers::{BruteForce, ILPSolver}; use crate::traits::Problem; use crate::types::SolutionSize; @@ -57,7 +57,7 @@ fn test_ilp_solution_equals_brute_force_triangle() { let ilp_solver = ILPSolver::new(); // Solve with brute force on original problem - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); // Solve via ILP reduction let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); @@ -84,7 +84,7 @@ fn test_ilp_solution_equals_brute_force_path() { let ilp_solver = ILPSolver::new(); // Solve with brute force - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_size = problem.evaluate(&bf_solutions[0]); // Solve via ILP @@ -112,7 +112,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_obj = problem.evaluate(&bf_solutions[0]); let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); diff --git a/src/unit_tests/rules/maximummatching_maximumsetpacking.rs b/src/unit_tests/rules/maximummatching_maximumsetpacking.rs index fc273710..8e80f51a 100644 --- a/src/unit_tests/rules/maximummatching_maximumsetpacking.rs +++ b/src/unit_tests/rules/maximummatching_maximumsetpacking.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::topology::SimpleGraph; use crate::traits::Problem; use crate::types::SolutionSize; @@ -28,7 +28,7 @@ fn test_matching_to_setpacking_path() { let sp = reduction.target_problem(); let solver = BruteForce::new(); - let sp_solutions = solver.find_best(sp); + let sp_solutions = solver.find_all_best(sp); // Extract back to MaximumMatching solutions let _matching_solutions: Vec<_> = sp_solutions @@ -37,7 +37,7 @@ fn test_matching_to_setpacking_path() { .collect(); // Verify against direct MaximumMatching solution - let direct_solutions = solver.find_best(&matching); + let direct_solutions = solver.find_all_best(&matching); // Solutions should have same objective value let sp_size: usize = sp_solutions[0].iter().sum(); @@ -54,7 +54,7 @@ fn test_matching_to_setpacking_triangle() { let sp = reduction.target_problem(); let solver = BruteForce::new(); - let sp_solutions = solver.find_best(sp); + let sp_solutions = solver.find_all_best(sp); // Max matching in triangle = 1 (any single edge) for sol in &sp_solutions { @@ -77,13 +77,13 @@ fn test_matching_to_setpacking_weighted() { assert_eq!(sp.weights_ref(), &vec![100, 1, 1]); let solver = BruteForce::new(); - let sp_solutions = solver.find_best(sp); + let sp_solutions = solver.find_all_best(sp); // Edge 0-1 (weight 100) alone beats edges 0-2 + 1-3 (weight 2) assert!(sp_solutions.contains(&vec![1, 0, 0])); // Verify through direct MaximumMatching solution - let direct_solutions = solver.find_best(&matching); + let direct_solutions = solver.find_all_best(&matching); assert_eq!(matching.evaluate(&sp_solutions[0]), SolutionSize::Valid(100)); assert_eq!(matching.evaluate(&direct_solutions[0]), SolutionSize::Valid(100)); } @@ -113,8 +113,8 @@ fn test_matching_to_setpacking_k4() { let sp = reduction.target_problem(); let solver = BruteForce::new(); - let sp_solutions = solver.find_best(sp); - let direct_solutions = solver.find_best(&matching); + let sp_solutions = solver.find_all_best(sp); + let direct_solutions = solver.find_all_best(&matching); // Both should find matchings of size 2 let sp_size: usize = sp_solutions[0].iter().sum(); @@ -143,7 +143,7 @@ fn test_matching_to_setpacking_single_edge() { assert_eq!(sp.sets()[0], vec![0, 1]); let solver = BruteForce::new(); - let sp_solutions = solver.find_best(sp); + let sp_solutions = solver.find_all_best(sp); // Should select the only set assert_eq!(sp_solutions, vec![vec![1]]); @@ -157,7 +157,7 @@ fn test_matching_to_setpacking_disjoint_edges() { let sp = reduction.target_problem(); let solver = BruteForce::new(); - let sp_solutions = solver.find_best(sp); + let sp_solutions = solver.find_all_best(sp); // Both edges can be selected (they don't share vertices) assert_eq!(sp_solutions, vec![vec![1, 1]]); @@ -181,7 +181,7 @@ fn test_matching_to_setpacking_star() { let sp = reduction.target_problem(); let solver = BruteForce::new(); - let sp_solutions = solver.find_best(sp); + let sp_solutions = solver.find_all_best(sp); // All edges share vertex 0, so max matching = 1 for sol in &sp_solutions { diff --git a/src/unit_tests/rules/maximumsetpacking_ilp.rs b/src/unit_tests/rules/maximumsetpacking_ilp.rs index 79bb6a5c..55991fde 100644 --- a/src/unit_tests/rules/maximumsetpacking_ilp.rs +++ b/src/unit_tests/rules/maximumsetpacking_ilp.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, ILPSolver, Solver}; +use crate::solvers::{BruteForce, ILPSolver}; use crate::traits::Problem; use crate::types::SolutionSize; @@ -57,7 +57,7 @@ fn test_ilp_solution_equals_brute_force_chain() { let ilp_solver = ILPSolver::new(); // Solve with brute force on original problem - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); // Solve via ILP reduction let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); @@ -83,7 +83,7 @@ fn test_ilp_solution_equals_brute_force_all_overlap() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_size: usize = bf_solutions[0].iter().sum(); let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); @@ -111,7 +111,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_obj = problem.evaluate(&bf_solutions[0]); let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); diff --git a/src/unit_tests/rules/maximumsetpacking_qubo.rs b/src/unit_tests/rules/maximumsetpacking_qubo.rs index a236e754..d8f9846c 100644 --- a/src/unit_tests/rules/maximumsetpacking_qubo.rs +++ b/src/unit_tests/rules/maximumsetpacking_qubo.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::Problem; #[test] @@ -12,7 +12,7 @@ fn test_setpacking_to_qubo_closed_loop() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -29,7 +29,7 @@ fn test_setpacking_to_qubo_disjoint() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -47,7 +47,7 @@ fn test_setpacking_to_qubo_all_overlap() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); diff --git a/src/unit_tests/rules/minimumdominatingset_ilp.rs b/src/unit_tests/rules/minimumdominatingset_ilp.rs index 27605a72..094e82f5 100644 --- a/src/unit_tests/rules/minimumdominatingset_ilp.rs +++ b/src/unit_tests/rules/minimumdominatingset_ilp.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, ILPSolver, Solver}; +use crate::solvers::{BruteForce, ILPSolver}; use crate::traits::Problem; use crate::types::SolutionSize; @@ -59,7 +59,7 @@ fn test_ilp_solution_equals_brute_force_star() { let ilp_solver = ILPSolver::new(); // Solve with brute force on original problem - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_size = problem.evaluate(&bf_solutions[0]); // Solve via ILP reduction @@ -87,7 +87,7 @@ fn test_ilp_solution_equals_brute_force_path() { let ilp_solver = ILPSolver::new(); // Solve with brute force - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_size = problem.evaluate(&bf_solutions[0]); // Solve via ILP @@ -114,7 +114,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_obj = problem.evaluate(&bf_solutions[0]); let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); @@ -219,7 +219,7 @@ fn test_cycle_graph() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_size = problem.evaluate(&bf_solutions[0]); let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); diff --git a/src/unit_tests/rules/minimumsetcovering_ilp.rs b/src/unit_tests/rules/minimumsetcovering_ilp.rs index d51205a9..67faac88 100644 --- a/src/unit_tests/rules/minimumsetcovering_ilp.rs +++ b/src/unit_tests/rules/minimumsetcovering_ilp.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, ILPSolver, Solver}; +use crate::solvers::{BruteForce, ILPSolver}; use crate::traits::Problem; use crate::types::SolutionSize; @@ -57,7 +57,7 @@ fn test_ilp_solution_equals_brute_force_simple() { let ilp_solver = ILPSolver::new(); // Solve with brute force on original problem - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); // Solve via ILP reduction let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); @@ -90,7 +90,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_obj = problem.evaluate(&bf_solutions[0]); let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); diff --git a/src/unit_tests/rules/minimumvertexcover_ilp.rs b/src/unit_tests/rules/minimumvertexcover_ilp.rs index dd4db217..4fa5d7ce 100644 --- a/src/unit_tests/rules/minimumvertexcover_ilp.rs +++ b/src/unit_tests/rules/minimumvertexcover_ilp.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, ILPSolver, Solver}; +use crate::solvers::{BruteForce, ILPSolver}; use crate::traits::Problem; use crate::types::SolutionSize; @@ -58,7 +58,7 @@ fn test_ilp_solution_equals_brute_force_triangle() { let ilp_solver = ILPSolver::new(); // Solve with brute force on original problem - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); // Solve via ILP reduction let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); @@ -85,7 +85,7 @@ fn test_ilp_solution_equals_brute_force_path() { let ilp_solver = ILPSolver::new(); // Solve with brute force - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_size: usize = bf_solutions[0].iter().sum(); // Solve via ILP @@ -113,7 +113,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_obj = problem.evaluate(&bf_solutions[0]); let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); @@ -237,7 +237,7 @@ fn test_single_edge() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_size: usize = bf_solutions[0].iter().sum(); let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); @@ -260,7 +260,7 @@ fn test_star_graph() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&problem); + let bf_solutions = bf.find_all_best(&problem); let bf_size: usize = bf_solutions[0].iter().sum(); let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); diff --git a/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs b/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs index 5bbe14b1..39658724 100644 --- a/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs +++ b/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; #[test] fn test_is_to_vc_reduction() { @@ -11,7 +11,7 @@ fn test_is_to_vc_reduction() { // Solve the VC problem let solver = BruteForce::new(); - let vc_solutions = solver.find_best(vc_problem); + let vc_solutions = solver.find_all_best(vc_problem); // Extract back to IS solutions let is_solutions: Vec<_> = vc_solutions @@ -34,7 +34,7 @@ fn test_vc_to_is_reduction() { let is_problem = reduction.target_problem(); let solver = BruteForce::new(); - let is_solutions = solver.find_best(is_problem); + let is_solutions = solver.find_all_best(is_problem); let vc_solutions: Vec<_> = is_solutions .iter() @@ -52,7 +52,7 @@ fn test_vc_to_is_reduction() { fn test_roundtrip_is_vc_is() { let original = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let solver = BruteForce::new(); - let original_solutions = solver.find_best(&original); + let original_solutions = solver.find_all_best(&original); // IS -> VC -> IS let reduction1 = ReduceTo::>::reduce_to(&original); @@ -60,7 +60,7 @@ fn test_roundtrip_is_vc_is() { let reduction2 = ReduceTo::>::reduce_to(&vc); let roundtrip = reduction2.target_problem(); - let roundtrip_solutions = solver.find_best(roundtrip); + let roundtrip_solutions = solver.find_all_best(roundtrip); // Solutions should have same objective value let orig_size: usize = original_solutions[0].iter().sum(); diff --git a/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs b/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs index bccf30fe..def0f139 100644 --- a/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs +++ b/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; #[test] fn test_vc_to_sc_basic() { @@ -51,7 +51,7 @@ fn test_vc_to_sc_solution_extraction() { // Solve the MinimumSetCovering problem let solver = BruteForce::new(); - let sc_solutions = solver.find_best(sc_problem); + let sc_solutions = solver.find_all_best(sc_problem); // Extract solutions back to MinimumVertexCover let vc_solutions: Vec<_> = sc_solutions @@ -78,12 +78,12 @@ fn test_vc_to_sc_optimality_preservation() { let solver = BruteForce::new(); // Solve VC directly - let direct_solutions = solver.find_best(&vc_problem); + let direct_solutions = solver.find_all_best(&vc_problem); let direct_size = direct_solutions[0].iter().sum::(); // Solve via reduction let reduction = ReduceTo::>::reduce_to(&vc_problem); - let sc_solutions = solver.find_best(reduction.target_problem()); + let sc_solutions = solver.find_all_best(reduction.target_problem()); let reduced_solutions: Vec<_> = sc_solutions .iter() .map(|s| reduction.extract_solution(s)) @@ -106,8 +106,8 @@ fn test_vc_to_sc_weighted() { // Solve both ways let solver = BruteForce::new(); - let vc_solutions = solver.find_best(&vc_problem); - let sc_solutions = solver.find_best(sc_problem); + let vc_solutions = solver.find_all_best(&vc_problem); + let sc_solutions = solver.find_all_best(sc_problem); // Both should select vertex 1 (weight 1) assert_eq!(vc_solutions[0], vec![0, 1, 0]); @@ -147,7 +147,7 @@ fn test_vc_to_sc_star_graph() { // Minimum cover should be just vertex 0 let solver = BruteForce::new(); - let solutions = solver.find_best(&vc_problem); + let solutions = solver.find_all_best(&vc_problem); assert_eq!(solutions[0], vec![1, 0, 0, 0]); } @@ -162,7 +162,7 @@ fn test_vc_to_sc_all_solutions_valid() { let sc_problem = reduction.target_problem(); let solver = BruteForce::new(); - let sc_solutions = solver.find_best(sc_problem); + let sc_solutions = solver.find_all_best(sc_problem); for sc_sol in &sc_solutions { let vc_sol = reduction.extract_solution(sc_sol); diff --git a/src/unit_tests/rules/minimumvertexcover_qubo.rs b/src/unit_tests/rules/minimumvertexcover_qubo.rs index 0b617e17..a742d4f7 100644 --- a/src/unit_tests/rules/minimumvertexcover_qubo.rs +++ b/src/unit_tests/rules/minimumvertexcover_qubo.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::Problem; #[test] @@ -11,7 +11,7 @@ fn test_vertexcovering_to_qubo_closed_loop() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -28,7 +28,7 @@ fn test_vertexcovering_to_qubo_triangle() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); @@ -46,7 +46,7 @@ fn test_vertexcovering_to_qubo_star() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); for sol in &qubo_solutions { let extracted = reduction.extract_solution(sol); diff --git a/src/unit_tests/rules/sat_maximumindependentset.rs b/src/unit_tests/rules/sat_maximumindependentset.rs index 9b57dfcd..5a208e4a 100644 --- a/src/unit_tests/rules/sat_maximumindependentset.rs +++ b/src/unit_tests/rules/sat_maximumindependentset.rs @@ -1,6 +1,6 @@ use super::*; use crate::models::satisfiability::CNFClause; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; #[test] fn test_boolvar_creation() { @@ -67,7 +67,7 @@ fn test_two_clause_sat_to_is() { // Maximum IS should have size 1 (can't select both) let solver = BruteForce::new(); - let solutions = solver.find_best(is_problem); + let solutions = solver.find_all_best(is_problem); for sol in &solutions { assert_eq!(sol.iter().sum::(), 1); } @@ -102,7 +102,7 @@ fn test_satisfiable_formula() { // Solve the IS problem let solver = BruteForce::new(); - let is_solutions = solver.find_best(is_problem); + let is_solutions = solver.find_all_best(is_problem); // Max IS should be 3 (one literal per clause) for sol in &is_solutions { @@ -130,7 +130,7 @@ fn test_unsatisfiable_formula() { let is_problem = reduction.target_problem(); let solver = BruteForce::new(); - let is_solutions = solver.find_best(is_problem); + let is_solutions = solver.find_all_best(is_problem); // Max IS can only be 1 (not 2 = num_clauses) // This indicates the formula is unsatisfiable @@ -161,7 +161,7 @@ fn test_three_sat_example() { assert_eq!(is_problem.num_vertices(), 9); let solver = BruteForce::new(); - let is_solutions = solver.find_best(is_problem); + let is_solutions = solver.find_all_best(is_problem); // Check that max IS has size 3 (satisfiable) let max_size = is_solutions[0].iter().sum::(); @@ -275,7 +275,7 @@ fn test_sat_is_solution_correspondence() { // Solve via reduction (IS is an optimization problem, so use find_best) let reduction = ReduceTo::>::reduce_to(&sat); let is_problem = reduction.target_problem(); - let is_solutions = sat_solver.find_best(is_problem); + let is_solutions = sat_solver.find_all_best(is_problem); // Extract SAT solutions from IS let extracted_sat_solutions: Vec<_> = is_solutions diff --git a/src/unit_tests/rules/sat_minimumdominatingset.rs b/src/unit_tests/rules/sat_minimumdominatingset.rs index b3282b2f..79fa7b5d 100644 --- a/src/unit_tests/rules/sat_minimumdominatingset.rs +++ b/src/unit_tests/rules/sat_minimumdominatingset.rs @@ -1,6 +1,6 @@ use super::*; use crate::models::satisfiability::CNFClause; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; #[test] fn test_simple_sat_to_ds() { @@ -51,7 +51,7 @@ fn test_satisfiable_formula() { // Solve the dominating set problem let solver = BruteForce::new(); - let solutions = solver.find_best(ds_problem); + let solutions = solver.find_all_best(ds_problem); // Minimum dominating set should be of size 2 (one per variable) let min_size = solutions[0].iter().sum::(); @@ -82,7 +82,7 @@ fn test_unsatisfiable_formula() { assert_eq!(ds_problem.num_vertices(), 5); let solver = BruteForce::new(); - let solutions = solver.find_best(ds_problem); + let solutions = solver.find_all_best(ds_problem); // For unsatisfiable formula, the minimum dominating set will need // more than num_variables vertices OR won't produce a valid assignment @@ -127,7 +127,7 @@ fn test_three_sat_example() { assert_eq!(ds_problem.num_vertices(), 12); let solver = BruteForce::new(); - let solutions = solver.find_best(ds_problem); + let solutions = solver.find_all_best(ds_problem); // Minimum should be 3 (one per variable) let min_size = solutions[0].iter().sum::(); @@ -246,7 +246,7 @@ fn test_sat_ds_solution_correspondence() { // Solve via reduction (DS is an optimization problem, so use find_best) let reduction = ReduceTo::>::reduce_to(&sat); let ds_problem = reduction.target_problem(); - let ds_solutions = sat_solver.find_best(ds_problem); + let ds_solutions = sat_solver.find_all_best(ds_problem); // Direct SAT solutions should all be valid (they're from find_all_satisfying, so they all satisfy) assert!(!direct_sat_solutions.is_empty()); diff --git a/src/unit_tests/rules/spinglass_maxcut.rs b/src/unit_tests/rules/spinglass_maxcut.rs index b7676567..656ba6a0 100644 --- a/src/unit_tests/rules/spinglass_maxcut.rs +++ b/src/unit_tests/rules/spinglass_maxcut.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; #[test] fn test_maxcut_to_spinglass() { @@ -9,7 +9,7 @@ fn test_maxcut_to_spinglass() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(sg); + let solutions = solver.find_all_best(sg); assert!(!solutions.is_empty()); } diff --git a/src/unit_tests/rules/spinglass_qubo.rs b/src/unit_tests/rules/spinglass_qubo.rs index db4284f5..0f73e042 100644 --- a/src/unit_tests/rules/spinglass_qubo.rs +++ b/src/unit_tests/rules/spinglass_qubo.rs @@ -1,5 +1,5 @@ use super::*; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::Problem; #[test] @@ -11,7 +11,7 @@ fn test_qubo_to_spinglass() { let sg = reduction.target_problem(); let solver = BruteForce::new(); - let sg_solutions = solver.find_best(sg); + let sg_solutions = solver.find_all_best(sg); let qubo_solutions: Vec<_> = sg_solutions .iter() .map(|s| reduction.extract_solution(s)) @@ -43,7 +43,7 @@ fn test_spinglass_to_qubo() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Ferromagnetic: aligned spins are optimal for sol in &qubo_solutions { @@ -55,7 +55,7 @@ fn test_spinglass_to_qubo() { fn test_roundtrip_qubo_sg_qubo() { let original = QUBO::from_matrix(vec![vec![-1.0, 2.0], vec![0.0, -1.0]]); let solver = BruteForce::new(); - let original_solutions = solver.find_best(&original); + let original_solutions = solver.find_all_best(&original); let _original_val = original.evaluate(&original_solutions[0]); // QUBO -> SG -> QUBO @@ -64,7 +64,7 @@ fn test_roundtrip_qubo_sg_qubo() { let reduction2 = ReduceTo::>::reduce_to(&sg); let roundtrip = reduction2.target_problem(); - let roundtrip_solutions = solver.find_best(roundtrip); + let roundtrip_solutions = solver.find_all_best(roundtrip); let _roundtrip_val = roundtrip.evaluate(&roundtrip_solutions[0]); // The solutions should have the same configuration @@ -85,7 +85,7 @@ fn test_antiferromagnetic() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(qubo); + let solutions = solver.find_all_best(qubo); // Anti-ferromagnetic: opposite spins are optimal for sol in &solutions { @@ -106,7 +106,7 @@ fn test_with_onsite_fields() { let qubo = reduction.target_problem(); let solver = BruteForce::new(); - let solutions = solver.find_best(qubo); + let solutions = solver.find_all_best(qubo); assert_eq!(solutions.len(), 1); assert_eq!(solutions[0], vec![0], "Should prefer x=0 (s=-1)"); diff --git a/src/unit_tests/rules/unitdiskmapping/pathdecomposition.rs b/src/unit_tests/rules/unitdiskmapping/pathdecomposition.rs index 9f0f78fc..c4196b93 100644 --- a/src/unit_tests/rules/unitdiskmapping/pathdecomposition.rs +++ b/src/unit_tests/rules/unitdiskmapping/pathdecomposition.rs @@ -1,4 +1,5 @@ use super::*; +use std::collections::HashSet; #[test] fn test_layout_empty() { @@ -22,7 +23,8 @@ fn test_layout_new() { fn test_vsep_and_neighbors_path() { // Path: 0-1-2 let edges = vec![(0, 1), (1, 2)]; - let (vsep, _) = vsep_and_neighbors(3, &edges, &[0, 1, 2]); + let adj = build_adj(3, &edges); + let (vsep, _) = vsep_and_neighbors(3, &adj, &[0, 1, 2]); assert_eq!(vsep, 1); } @@ -30,8 +32,9 @@ fn test_vsep_and_neighbors_path() { fn test_vsep_and_neighbors_star() { // Star: 0 connected to 1, 2, 3 let edges = vec![(0, 1), (0, 2), (0, 3)]; + let adj = build_adj(4, &edges); // Order: 0, 1, 2, 3 - after adding 0, all others become neighbors - let (vsep, _) = vsep_and_neighbors(4, &edges, &[0, 1, 2, 3]); + let (vsep, _) = vsep_and_neighbors(4, &adj, &[0, 1, 2, 3]); assert_eq!(vsep, 3); // After adding 0, neighbors = {1, 2, 3} } @@ -39,8 +42,9 @@ fn test_vsep_and_neighbors_star() { fn test_extend() { // Path: 0-1-2 let edges = vec![(0, 1), (1, 2)]; + let adj = build_adj(3, &edges); let layout = Layout::empty(3); - let layout = extend(3, &edges, &layout, 0); + let layout = extend(&adj, &layout, 0); assert_eq!(layout.vertices, vec![0]); assert!(layout.neighbors.contains(&1)); assert!(layout.disconnected.contains(&2)); @@ -194,3 +198,155 @@ fn test_pathwidth_auto_large() { assert_eq!(layout.vertices.len(), n); assert_eq!(layout.vsep(), 1); // Path graph has pathwidth 1 } + +// === Ground truth tests from JSON dataset === + +/// Compute vsep from scratch for a given vertex ordering on a graph. +/// This is an independent reimplementation for verification — it does NOT call +/// any function from the pathdecomposition module. +fn verify_vsep(num_vertices: usize, edges: &[(usize, usize)], order: &[usize]) -> usize { + let mut adj: Vec> = vec![HashSet::new(); num_vertices]; + for &(u, v) in edges { + adj[u].insert(v); + adj[v].insert(u); + } + + let mut vsep = 0; + let mut added: HashSet = HashSet::new(); + + for &v in order { + added.insert(v); + // Count vertices not yet added but adjacent to some added vertex + let frontier = (0..num_vertices) + .filter(|w| !added.contains(w) && adj[*w].iter().any(|u| added.contains(u))) + .count(); + vsep = vsep.max(frontier); + } + vsep +} + +#[derive(serde::Deserialize)] +struct PathwidthEntry { + graph: String, + num_vertices: usize, + num_edges: usize, + pathwidth: usize, + vertex_order: Vec, +} + +fn load_pathwidth_ground_truth() -> Vec { + let path = format!( + "{}/tests/data/pathwidth_ground_truth.json", + env!("CARGO_MANIFEST_DIR") + ); + let content = std::fs::read_to_string(&path).expect("Failed to read pathwidth ground truth"); + serde_json::from_str(&content).expect("Failed to parse pathwidth ground truth JSON") +} + +#[test] +fn test_ground_truth_vertex_order_is_valid_permutation() { + let entries = load_pathwidth_ground_truth(); + for entry in &entries { + let (n, edges) = crate::topology::smallgraph(&entry.graph).unwrap(); + assert_eq!(n, entry.num_vertices, "{}: vertex count mismatch", entry.graph); + assert_eq!(edges.len(), entry.num_edges, "{}: edge count mismatch", entry.graph); + + // vertex_order must be a permutation of 0..n + let mut sorted = entry.vertex_order.clone(); + sorted.sort(); + assert_eq!( + sorted, + (0..n).collect::>(), + "{}: vertex_order is not a valid permutation", + entry.graph + ); + } +} + +#[test] +fn test_ground_truth_vsep_matches_claimed_pathwidth() { + let entries = load_pathwidth_ground_truth(); + for entry in &entries { + let (n, edges) = crate::topology::smallgraph(&entry.graph).unwrap(); + + // Independently compute vsep for the given vertex order + let computed_vsep = verify_vsep(n, &edges, &entry.vertex_order); + assert_eq!( + computed_vsep, entry.pathwidth, + "{}: vsep of vertex_order ({}) != claimed pathwidth ({})", + entry.graph, computed_vsep, entry.pathwidth + ); + } +} + +#[test] +fn test_branch_and_bound_matches_ground_truth() { + let entries = load_pathwidth_ground_truth(); + for entry in &entries { + // tutte (46 vertices) is too slow for routine B&B; tested separately with #[ignore] + if entry.graph == "tutte" { + continue; + } + let (n, edges) = crate::topology::smallgraph(&entry.graph).unwrap(); + let layout = pathwidth(n, &edges, PathDecompositionMethod::MinhThiTrick); + + // Must produce a complete layout + assert_eq!( + layout.vertices.len(), n, + "{}: layout missing vertices", entry.graph + ); + + // Pathwidth must match ground truth (branch-and-bound is exact) + assert_eq!( + layout.vsep(), entry.pathwidth, + "{}: branch_and_bound vsep ({}) != ground truth ({})", + entry.graph, layout.vsep(), entry.pathwidth + ); + + // Independently verify the produced layout's vsep + let verified = verify_vsep(n, &edges, &layout.vertices); + assert_eq!( + verified, layout.vsep(), + "{}: Layout.vsep ({}) != independently verified vsep ({})", + entry.graph, layout.vsep(), verified + ); + } +} + +#[test] +#[ignore] // tutte (46 vertices) takes ~10s in branch-and-bound +fn test_branch_and_bound_tutte() { + let (n, edges) = crate::topology::smallgraph("tutte").unwrap(); + let layout = pathwidth(n, &edges, PathDecompositionMethod::MinhThiTrick); + assert_eq!(layout.vertices.len(), n); + assert_eq!(layout.vsep(), 6); // known pathwidth from ground truth + let verified = verify_vsep(n, &edges, &layout.vertices); + assert_eq!(verified, layout.vsep()); +} + +#[test] +fn test_greedy_respects_ground_truth_upper_bound() { + let entries = load_pathwidth_ground_truth(); + for entry in &entries { + let (n, edges) = crate::topology::smallgraph(&entry.graph).unwrap(); + let layout = pathwidth(n, &edges, PathDecompositionMethod::greedy_with_restarts(20)); + + // Greedy may not be optimal but must not be worse than n-1 + assert!( + layout.vsep() >= entry.pathwidth, + "{}: greedy vsep ({}) < optimal ({}), which is impossible", + entry.graph, layout.vsep(), entry.pathwidth + ); + + // Must be a complete layout + assert_eq!(layout.vertices.len(), n, "{}: greedy layout incomplete", entry.graph); + + // Independently verify + let verified = verify_vsep(n, &edges, &layout.vertices); + assert_eq!( + verified, layout.vsep(), + "{}: greedy Layout.vsep ({}) != independently verified ({})", + entry.graph, layout.vsep(), verified + ); + } +} diff --git a/src/unit_tests/solvers/brute_force.rs b/src/unit_tests/solvers/brute_force.rs index c946fd93..fa78cd1d 100644 --- a/src/unit_tests/solvers/brute_force.rs +++ b/src/unit_tests/solvers/brute_force.rs @@ -97,7 +97,7 @@ fn test_solver_maximization() { }; let solver = BruteForce::new(); - let best = solver.find_best(&problem); + let best = solver.find_all_best(&problem); assert_eq!(best.len(), 1); assert_eq!(best[0], vec![1, 1, 1]); // Select all for max sum = 6 } @@ -109,7 +109,7 @@ fn test_solver_minimization() { }; let solver = BruteForce::new(); - let best = solver.find_best(&problem); + let best = solver.find_all_best(&problem); assert_eq!(best.len(), 1); assert_eq!(best[0], vec![0, 0, 0]); // Select none for min sum = 0 } @@ -122,7 +122,7 @@ fn test_solver_multiple_optimal() { }; let solver = BruteForce::new(); - let best = solver.find_best(&problem); + let best = solver.find_all_best(&problem); assert_eq!(best.len(), 1); assert_eq!(best[0], vec![1, 1]); // Only one optimal: select both = 10 } @@ -132,7 +132,7 @@ fn test_solver_empty() { let problem = MaxSumOpt { weights: vec![] }; let solver = BruteForce::new(); - let best = solver.find_best(&problem); + let best = solver.find_all_best(&problem); assert_eq!(best, vec![Vec::::new()]); } @@ -203,6 +203,39 @@ fn test_solver_find_satisfying_empty_dims_unsat() { assert_eq!(solver.find_all_satisfying(&problem), Vec::>::new()); } +#[test] +fn test_find_best_returns_one_solution() { + let problem = MaxSumOpt { + weights: vec![1, 2, 3], + }; + let solver = BruteForce::new(); + + let best = solver.find_best(&problem); + assert!(best.is_some()); + assert_eq!(best.unwrap(), vec![1, 1, 1]); +} + +#[test] +fn test_find_best_empty_problem() { + let problem = MaxSumOpt { weights: vec![] }; + let solver = BruteForce::new(); + + let best = solver.find_best(&problem); + assert_eq!(best, Some(vec![])); +} + +#[test] +fn test_find_best_minimization() { + let problem = MinSumOpt { + weights: vec![1, 2, 3], + }; + let solver = BruteForce::new(); + + let best = solver.find_best(&problem); + assert!(best.is_some()); + assert_eq!(best.unwrap(), vec![0, 0, 0]); +} + #[test] fn test_solver_with_real_mis() { use crate::models::graph::MaximumIndependentSet; @@ -213,7 +246,7 @@ fn test_solver_with_real_mis() { let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); - let best = solver.find_best(&problem); + let best = solver.find_all_best(&problem); assert_eq!(best.len(), 3); // Three single-vertex solutions for sol in &best { assert_eq!(sol.iter().sum::(), 1); diff --git a/src/unit_tests/solvers/ilp/solver.rs b/src/unit_tests/solvers/ilp/solver.rs index 6e446dc1..abf5f831 100644 --- a/src/unit_tests/solvers/ilp/solver.rs +++ b/src/unit_tests/solvers/ilp/solver.rs @@ -1,6 +1,6 @@ use super::*; use crate::models::optimization::{LinearConstraint, VarBounds}; -use crate::solvers::{BruteForce, Solver}; +use crate::solvers::BruteForce; use crate::traits::Problem; #[test] @@ -69,7 +69,7 @@ fn test_ilp_solver_matches_brute_force() { let bf = BruteForce::new(); let ilp_solver = ILPSolver::new(); - let bf_solutions = bf.find_best(&ilp); + let bf_solutions = bf.find_all_best(&ilp); let ilp_solution = ilp_solver.solve(&ilp).unwrap(); // Both should find optimal value (2) @@ -198,7 +198,7 @@ fn test_ilp_multiple_constraints() { // Check against brute force let bf = BruteForce::new(); - let bf_solutions = bf.find_best(&ilp); + let bf_solutions = bf.find_all_best(&ilp); let bf_size = ilp.evaluate(&bf_solutions[0]).unwrap(); assert!( diff --git a/src/unit_tests/unitdiskmapping_algorithms/map_graph.rs b/src/unit_tests/unitdiskmapping_algorithms/map_graph.rs index 22feba90..0db575f1 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/map_graph.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/map_graph.rs @@ -306,6 +306,7 @@ fn test_mis_overhead_cubical() { } #[test] +#[ignore] // tutte maps to 1232-vertex grid; ILP solving takes ~10s fn test_mis_overhead_tutte() { let (n, edges) = smallgraph("tutte").unwrap(); let result = map_graph(n, &edges); diff --git a/tests/data/pathwidth_ground_truth.json b/tests/data/pathwidth_ground_truth.json new file mode 100644 index 00000000..cbbdeefc --- /dev/null +++ b/tests/data/pathwidth_ground_truth.json @@ -0,0 +1,441 @@ +[ + { + "graph": "bull", + "num_vertices": 5, + "num_edges": 5, + "pathwidth": 2, + "vertex_order": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + { + "graph": "chvatal", + "num_vertices": 12, + "num_edges": 24, + "pathwidth": 6, + "vertex_order": [ + 0, + 6, + 5, + 1, + 9, + 10, + 4, + 11, + 2, + 7, + 3, + 8 + ] + }, + { + "graph": "cubical", + "num_vertices": 8, + "num_edges": 12, + "pathwidth": 4, + "vertex_order": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ] + }, + { + "graph": "desargues", + "num_vertices": 20, + "num_edges": 30, + "pathwidth": 6, + "vertex_order": [ + 0, + 5, + 19, + 4, + 10, + 9, + 2, + 7, + 1, + 6, + 18, + 3, + 11, + 8, + 16, + 15, + 17, + 14, + 12, + 13 + ] + }, + { + "graph": "diamond", + "num_vertices": 4, + "num_edges": 5, + "pathwidth": 2, + "vertex_order": [ + 0, + 1, + 2, + 3 + ] + }, + { + "graph": "dodecahedral", + "num_vertices": 20, + "num_edges": 30, + "pathwidth": 6, + "vertex_order": [ + 0, + 1, + 10, + 8, + 9, + 19, + 2, + 11, + 7, + 18, + 3, + 13, + 6, + 12, + 14, + 17, + 4, + 5, + 16, + 15 + ] + }, + { + "graph": "frucht", + "num_vertices": 12, + "num_edges": 18, + "pathwidth": 4, + "vertex_order": [ + 0, + 1, + 7, + 6, + 10, + 11, + 5, + 2, + 8, + 4, + 3, + 9 + ] + }, + { + "graph": "heawood", + "num_vertices": 14, + "num_edges": 21, + "pathwidth": 6, + "vertex_order": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ] + }, + { + "graph": "house", + "num_vertices": 5, + "num_edges": 6, + "pathwidth": 2, + "vertex_order": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + { + "graph": "housex", + "num_vertices": 5, + "num_edges": 8, + "pathwidth": 3, + "vertex_order": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + { + "graph": "icosahedral", + "num_vertices": 12, + "num_edges": 30, + "pathwidth": 6, + "vertex_order": [ + 0, + 5, + 11, + 1, + 7, + 6, + 8, + 4, + 10, + 2, + 9, + 3 + ] + }, + { + "graph": "krackhardtkite", + "num_vertices": 10, + "num_edges": 18, + "pathwidth": 4, + "vertex_order": [ + 9, + 8, + 7, + 5, + 2, + 0, + 3, + 6, + 1, + 4 + ] + }, + { + "graph": "moebiuskantor", + "num_vertices": 16, + "num_edges": 24, + "pathwidth": 6, + "vertex_order": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ] + }, + { + "graph": "octahedral", + "num_vertices": 6, + "num_edges": 12, + "pathwidth": 4, + "vertex_order": [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + }, + { + "graph": "pappus", + "num_vertices": 18, + "num_edges": 27, + "pathwidth": 7, + "vertex_order": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17 + ] + }, + { + "graph": "petersen", + "num_vertices": 10, + "num_edges": 15, + "pathwidth": 5, + "vertex_order": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + { + "graph": "sedgewickmaze", + "num_vertices": 8, + "num_edges": 10, + "pathwidth": 3, + "vertex_order": [ + 1, + 7, + 0, + 3, + 6, + 4, + 2, + 5 + ] + }, + { + "graph": "tetrahedral", + "num_vertices": 4, + "num_edges": 6, + "pathwidth": 3, + "vertex_order": [ + 0, + 1, + 2, + 3 + ] + }, + { + "graph": "truncatedcube", + "num_vertices": 24, + "num_edges": 36, + "pathwidth": 5, + "vertex_order": [ + 0, + 2, + 4, + 1, + 11, + 14, + 3, + 6, + 8, + 10, + 7, + 12, + 5, + 18, + 16, + 15, + 9, + 19, + 17, + 23, + 20, + 13, + 22, + 21 + ] + }, + { + "graph": "truncatedtetrahedron", + "num_vertices": 12, + "num_edges": 18, + "pathwidth": 4, + "vertex_order": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, + { + "graph": "tutte", + "num_vertices": 46, + "num_edges": 69, + "pathwidth": 6, + "vertex_order": [ + 0, + 3, + 18, + 19, + 45, + 17, + 42, + 43, + 16, + 20, + 41, + 22, + 44, + 40, + 15, + 21, + 2, + 13, + 11, + 10, + 12, + 39, + 35, + 14, + 36, + 34, + 8, + 9, + 37, + 38, + 7, + 1, + 23, + 6, + 4, + 27, + 26, + 24, + 5, + 33, + 31, + 28, + 25, + 32, + 29, + 30 + ] + } +] \ No newline at end of file diff --git a/tests/suites/integration.rs b/tests/suites/integration.rs index a6d39315..90bfd0b0 100644 --- a/tests/suites/integration.rs +++ b/tests/suites/integration.rs @@ -20,7 +20,7 @@ mod all_problems_solvable { let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { assert!(problem.evaluate(sol).is_valid()); @@ -31,7 +31,7 @@ mod all_problems_solvable { fn test_vertex_covering_solvable() { let problem = MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { assert!(problem.evaluate(sol).is_valid()); @@ -42,7 +42,7 @@ mod all_problems_solvable { fn test_max_cut_solvable() { let problem = MaxCut::::new(4, vec![(0, 1, 1), (1, 2, 2), (2, 3, 1)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); } @@ -63,7 +63,7 @@ mod all_problems_solvable { let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { assert!(problem.evaluate(sol).is_valid()); @@ -74,7 +74,7 @@ mod all_problems_solvable { fn test_maximal_is_solvable() { let problem = MaximalIS::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { assert!(problem.evaluate(sol).is_valid()); @@ -86,7 +86,7 @@ mod all_problems_solvable { let problem = MaximumMatching::::new(4, vec![(0, 1, 1), (1, 2, 2), (2, 3, 1)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { assert!(problem.evaluate(sol).is_valid()); @@ -116,7 +116,7 @@ mod all_problems_solvable { fn test_spin_glass_solvable() { let problem = SpinGlass::new(3, vec![((0, 1), -1.0), ((1, 2), 1.0)], vec![0.5, -0.5, 0.0]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); } @@ -128,7 +128,7 @@ mod all_problems_solvable { vec![0.0, 0.0, 1.0], ]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); } @@ -137,7 +137,7 @@ mod all_problems_solvable { let problem = MinimumSetCovering::::new(5, vec![vec![0, 1, 2], vec![2, 3, 4], vec![0, 4]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { assert!(problem.evaluate(sol).is_valid()); @@ -149,7 +149,7 @@ mod all_problems_solvable { let problem = MaximumSetPacking::::new(vec![vec![0, 1], vec![2, 3], vec![1, 2], vec![4]]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { assert!(problem.evaluate(sol).is_valid()); @@ -180,7 +180,7 @@ mod all_problems_solvable { fn test_factoring_solvable() { let problem = Factoring::new(15, 2, 2); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { assert!(problem.evaluate(sol).is_valid()); @@ -191,7 +191,7 @@ mod all_problems_solvable { fn test_paintshop_solvable() { let problem = PaintShop::new(vec!["a", "b", "a", "b"]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); } @@ -200,7 +200,7 @@ mod all_problems_solvable { // Left vertices: 0, 1; Right vertices: 2, 3 let problem = BicliqueCover::new(2, 2, vec![(0, 2), (0, 3), (1, 2), (1, 3)], 1); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { assert!(problem.evaluate(sol).is_valid()); @@ -211,7 +211,7 @@ mod all_problems_solvable { fn test_bmf_solvable() { let problem = BMF::new(vec![vec![true, true], vec![true, true]], 1); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { // BMF minimizes Hamming distance, all configs are valid (no invalid marker) @@ -235,8 +235,8 @@ mod problem_relationships { let vc_problem = MinimumVertexCover::::new(n, edges); let solver = BruteForce::new(); - let is_solutions = solver.find_best(&is_problem); - let vc_solutions = solver.find_best(&vc_problem); + let is_solutions = solver.find_all_best(&is_problem); + let vc_solutions = solver.find_all_best(&vc_problem); let max_is_size = is_solutions[0].iter().sum::(); let min_vc_size = vc_solutions[0].iter().sum::(); @@ -255,7 +255,7 @@ mod problem_relationships { let is_problem = MaximumIndependentSet::::new(n, edges); let solver = BruteForce::new(); - let maximal_solutions = solver.find_best(&maximal_is); + let maximal_solutions = solver.find_all_best(&maximal_is); // Every maximal IS is also a valid IS for sol in &maximal_solutions { @@ -291,7 +291,7 @@ mod problem_relationships { ); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Optimal should be all same spin (all 0 or all 1) for sol in &solutions { @@ -315,11 +315,11 @@ mod problem_relationships { let solver = BruteForce::new(); // All sets needed for cover - let cover_solutions = solver.find_best(&covering); + let cover_solutions = solver.find_all_best(&covering); assert_eq!(cover_solutions[0].iter().sum::(), 3); // All sets can be packed (no overlap) - let pack_solutions = solver.find_best(&packing); + let pack_solutions = solver.find_all_best(&packing); assert_eq!(pack_solutions[0].iter().sum::(), 3); } } @@ -332,7 +332,7 @@ mod edge_cases { fn test_empty_graph_independent_set() { let problem = MaximumIndependentSet::::new(3, vec![]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // All vertices can be in IS when no edges assert_eq!(solutions[0].iter().sum::(), 3); @@ -344,7 +344,7 @@ mod edge_cases { let edges = vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]; let problem = MaximumIndependentSet::::new(4, edges); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Maximum IS in complete graph is 1 assert_eq!(solutions[0].iter().sum::(), 1); @@ -373,7 +373,7 @@ mod edge_cases { // Factor 4 = 2 * 2 let problem = Factoring::new(4, 2, 2); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); for sol in &solutions { @@ -385,7 +385,7 @@ mod edge_cases { fn test_single_car_paintshop() { let problem = PaintShop::new(vec!["a", "a"]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Single car always has 1 switch (color must change) assert_eq!(problem.count_switches(&solutions[0]), 1); @@ -402,7 +402,7 @@ mod weighted_problems { problem.set_weights(vec![10, 1, 1]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Should prefer vertex 0 (weight 10) over vertex 1 (weight 1) // Optimal: {0, 2} with weight 11 @@ -420,7 +420,7 @@ mod weighted_problems { problem.set_weights(vec![1, 10, 1]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Prefer {0, 2} over {1} because {0,2} has weight 2 vs {1} has weight 10 let best_weight: i32 = solutions[0] @@ -435,7 +435,7 @@ mod weighted_problems { fn test_weighted_max_cut() { let problem = MaxCut::new(3, vec![(0, 1, 10), (1, 2, 1)]); let solver = BruteForce::new(); - let solutions = solver.find_best(&problem); + let solutions = solver.find_all_best(&problem); // Maximum cut should include the heavy edge (0,1) let cut_value = problem.evaluate(&solutions[0]); diff --git a/tests/suites/reductions.rs b/tests/suites/reductions.rs index 7b45969a..a028f294 100644 --- a/tests/suites/reductions.rs +++ b/tests/suites/reductions.rs @@ -26,7 +26,7 @@ mod is_vc_reductions { // Solve the target VC problem let solver = BruteForce::new(); - let vc_solutions = solver.find_best(vc_problem); + let vc_solutions = solver.find_all_best(vc_problem); // Extract back to IS solution let is_solution = result.extract_solution(&vc_solutions[0]); @@ -51,7 +51,7 @@ mod is_vc_reductions { // Solve the target IS problem let solver = BruteForce::new(); - let is_solutions = solver.find_best(is_problem); + let is_solutions = solver.find_all_best(is_problem); // Extract back to VC solution let vc_solution = result.extract_solution(&is_solutions[0]); @@ -79,7 +79,7 @@ mod is_vc_reductions { // Solve the final problem let solver = BruteForce::new(); - let solutions = solver.find_best(final_is); + let solutions = solver.find_all_best(final_is); // Extract through the chain let intermediate_sol = back_to_is.extract_solution(&solutions[0]); @@ -112,10 +112,10 @@ mod is_vc_reductions { let solver = BruteForce::new(); // Solve IS, reduce to VC solution - let is_solutions = solver.find_best(&is_problem); + let is_solutions = solver.find_all_best(&is_problem); let max_is = is_solutions[0].iter().sum::(); - let vc_solutions = solver.find_best(&vc_problem); + let vc_solutions = solver.find_all_best(&vc_problem); let min_vc = vc_solutions[0].iter().sum::(); assert_eq!(max_is + min_vc, n); @@ -140,7 +140,7 @@ mod is_sp_reductions { // Solve let solver = BruteForce::new(); - let sp_solutions = solver.find_best(sp_problem); + let sp_solutions = solver.find_all_best(sp_problem); // Extract to IS solution let is_solution = result.extract_solution(&sp_solutions[0]); @@ -162,7 +162,7 @@ mod is_sp_reductions { // Solve let solver = BruteForce::new(); - let is_solutions = solver.find_best(is_problem); + let is_solutions = solver.find_all_best(is_problem); // Extract to SP solution let sp_solution = result.extract_solution(&is_solutions[0]); @@ -183,7 +183,7 @@ mod is_sp_reductions { // Solve SP let solver = BruteForce::new(); - let sp_solutions = solver.find_best(sp_problem); + let sp_solutions = solver.find_all_best(sp_problem); // Extract to IS solution let is_solution = to_sp.extract_solution(&sp_solutions[0]); @@ -192,7 +192,7 @@ mod is_sp_reductions { assert!(original.evaluate(&is_solution) .is_valid()); // Should match directly solving IS - let direct_solutions = solver.find_best(&original); + let direct_solutions = solver.find_all_best(&original); let direct_max = direct_solutions[0].iter().sum::(); let reduced_max = is_solution.iter().sum::(); @@ -216,7 +216,7 @@ mod sg_qubo_reductions { // Solve QUBO let solver = BruteForce::new(); - let qubo_solutions = solver.find_best(qubo); + let qubo_solutions = solver.find_all_best(qubo); // Extract to SG solution let sg_solution = result.extract_solution(&qubo_solutions[0]); @@ -235,7 +235,7 @@ mod sg_qubo_reductions { // Solve SG let solver = BruteForce::new(); - let sg_solutions = solver.find_best(sg); + let sg_solutions = solver.find_all_best(sg); // Extract to QUBO solution let qubo_solution = result.extract_solution(&sg_solutions[0]); @@ -257,8 +257,8 @@ mod sg_qubo_reductions { // Check that ground states correspond let solver = BruteForce::new(); - let sg_solutions = solver.find_best(&sg); - let qubo_solutions = solver.find_best(qubo); + let sg_solutions = solver.find_all_best(&sg); + let qubo_solutions = solver.find_all_best(qubo); // Extract QUBO solution back to SG let extracted = result.extract_solution(&qubo_solutions[0]); @@ -298,7 +298,7 @@ mod sg_maxcut_reductions { // Solve MaxCut let solver = BruteForce::new(); - let maxcut_solutions = solver.find_best(maxcut); + let maxcut_solutions = solver.find_all_best(maxcut); // Extract to SG solution let sg_solution = result.extract_solution(&maxcut_solutions[0]); @@ -317,7 +317,7 @@ mod sg_maxcut_reductions { // Solve SG let solver = BruteForce::new(); - let sg_solutions = solver.find_best(sg); + let sg_solutions = solver.find_all_best(sg); // Extract to MaxCut solution let maxcut_solution = result.extract_solution(&sg_solutions[0]); @@ -339,8 +339,8 @@ mod sg_maxcut_reductions { let solver = BruteForce::new(); // Solve both - let sg_solutions = solver.find_best(&sg); - let maxcut_solutions = solver.find_best(maxcut); + let sg_solutions = solver.find_all_best(&sg); + let maxcut_solutions = solver.find_all_best(maxcut); // Extract MaxCut solution back to SG let extracted = result.extract_solution(&maxcut_solutions[0]); @@ -373,7 +373,7 @@ mod topology_tests { let sp = MaximumSetPacking::::new(sets); let solver = BruteForce::new(); - let solutions = solver.find_best(&sp); + let solutions = solver.find_all_best(&sp); assert!(sp.evaluate(&solutions[0]).is_valid()); } @@ -394,7 +394,7 @@ mod topology_tests { let is_problem = MaximumIndependentSet::::new(4, edges); let solver = BruteForce::new(); - let solutions = solver.find_best(&is_problem); + let solutions = solver.find_all_best(&is_problem); // Vertices 0-1 are connected, 2-3 are connected // Max IS: {0, 2} or {0, 3} or {1, 2} or {1, 3} = size 2 @@ -491,7 +491,7 @@ mod qubo_reductions { assert_eq!(qubo.num_variables(), data.qubo_num_vars); let solver = BruteForce::new(); - let solutions = solver.find_best(qubo); + let solutions = solver.find_all_best(qubo); // All QUBO optimal solutions should extract to valid IS solutions for sol in &solutions { @@ -534,7 +534,7 @@ mod qubo_reductions { assert_eq!(qubo.num_variables(), data.qubo_num_vars); let solver = BruteForce::new(); - let solutions = solver.find_best(qubo); + let solutions = solver.find_all_best(qubo); for sol in &solutions { let extracted = reduction.extract_solution(sol); @@ -575,7 +575,7 @@ mod qubo_reductions { assert_eq!(qubo.num_variables(), data.qubo_num_vars); let solver = BruteForce::new(); - let solutions = solver.find_best(qubo); + let solutions = solver.find_all_best(qubo); for sol in &solutions { let extracted = reduction.extract_solution(sol); @@ -612,7 +612,7 @@ mod qubo_reductions { assert_eq!(qubo.num_variables(), data.qubo_num_vars); let solver = BruteForce::new(); - let solutions = solver.find_best(qubo); + let solutions = solver.find_all_best(qubo); for sol in &solutions { let extracted = reduction.extract_solution(sol); @@ -677,7 +677,7 @@ mod qubo_reductions { assert_eq!(qubo.num_variables(), data.qubo_num_vars); let solver = BruteForce::new(); - let solutions = solver.find_best(qubo); + let solutions = solver.find_all_best(qubo); for sol in &solutions { let extracted = reduction.extract_solution(sol); @@ -761,7 +761,7 @@ mod qubo_reductions { assert!(qubo.num_variables() >= data.qubo_num_vars); let solver = BruteForce::new(); - let solutions = solver.find_best(qubo); + let solutions = solver.find_all_best(qubo); for sol in &solutions { let extracted = reduction.extract_solution(sol); @@ -842,20 +842,20 @@ mod end_to_end { // Solve directly let solver = BruteForce::new(); - let is_solutions = solver.find_best(&is); + let is_solutions = solver.find_all_best(&is); let direct_size = is_solutions[0].iter().sum::(); // Reduce to VC and solve let to_vc = ReduceTo::>::reduce_to(&is); let vc = to_vc.target_problem(); - let vc_solutions = solver.find_best(vc); + let vc_solutions = solver.find_all_best(vc); let vc_extracted = to_vc.extract_solution(&vc_solutions[0]); let via_vc_size = vc_extracted.iter().sum::(); // Reduce to MaximumSetPacking and solve let to_sp = ReduceTo::>::reduce_to(&is); let sp = to_sp.target_problem(); - let sp_solutions = solver.find_best(sp); + let sp_solutions = solver.find_all_best(sp); let sp_extracted = to_sp.extract_solution(&sp_solutions[0]); let via_sp_size = sp_extracted.iter().sum::(); @@ -875,7 +875,7 @@ mod end_to_end { // Solve directly let solver = BruteForce::new(); - let sg_solutions = solver.find_best(&sg); + let sg_solutions = solver.find_all_best(&sg); // Convert usize solution to i32 spin values for compute_energy let direct_spins: Vec = sg_solutions[0].iter().map(|&x| x as i32).collect(); @@ -884,7 +884,7 @@ mod end_to_end { // Reduce to MaxCut and solve let to_maxcut = ReduceTo::>::reduce_to(&sg); let maxcut = to_maxcut.target_problem(); - let maxcut_solutions = solver.find_best(maxcut); + let maxcut_solutions = solver.find_all_best(maxcut); let maxcut_extracted = to_maxcut.extract_solution(&maxcut_solutions[0]); // Convert extracted solution to spins for energy computation @@ -911,7 +911,7 @@ mod end_to_end { // Solve VC let solver = BruteForce::new(); - let vc_solutions = solver.find_best(vc); + let vc_solutions = solver.find_all_best(vc); // Extract back through chain let is_sol = is_to_vc.extract_solution(&vc_solutions[0]);