problem-reductions 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 shows all available problem types and transformations.
Add to your Cargo.toml:
[dependencies]
problemreductions = "0.2"The core workflow is: create a problem, reduce it to a target, solve the target, and extract the solution back.
Reduce Maximum Set Packing to Integer Linear Programming (ILP), solve with the ILP solver, and extract the solution back.
A small set system with pairwise overlaps gives a direct binary ILP.
use problemreductions::prelude::*;
use problemreductions::models::algebraic::ILP;
use problemreductions::solvers::ILPSolver;
let problem = MaximumSetPacking::<i32>::new(vec![
vec![0, 1],
vec![1, 2],
vec![2, 3],
vec![4, 5],
]);ReduceTo applies a single-step reduction. The result holds the target
problem and knows how to map solutions back. The ILP formulation introduces
binary variable x_i for each set, constraint x_i + x_j ≤ 1 for each
overlapping pair, and maximizes the weighted sum.
let reduction = ReduceTo::<ILP>::reduce_to(&problem);
let ilp = reduction.target_problem();
println!("ILP: {} variables, {} constraints", ilp.num_vars, ilp.constraints.len());ILP: 4 variables, 2 constraints
ILPSolver uses the HiGHS solver to find optimal solutions efficiently.
For small instances you can also use BruteForce, but ILPSolver scales
to much larger problems.
let solver = ILPSolver::new();
let ilp_solution = solver.solve(ilp).unwrap();
println!("ILP solution: {:?}", ilp_solution);ILP solution: [1, 0, 1, 0]
extract_solution maps the ILP solution back to the original problem's
configuration space.
let solution = reduction.extract_solution(&ilp_solution);
let metric = problem.evaluate(&solution);
println!("Packing solution: {:?} -> size {:?}", solution, metric);
assert!(metric.is_valid());Packing solution: [1, 0, 1, 1] -> size Valid(3)
For convenience, ILPSolver::solve_reduced combines reduce + solve + extract
in a single call:
let solution = ILPSolver::new().solve_reduced(&problem).unwrap();
assert!(problem.evaluate(&solution).is_valid());Real-world problems often require chaining multiple reductions. Here we factor the integer 6 by reducing Factoring through the reduction graph to SpinGlass, through automatic reduction path search. (full source)
Let's walk through each step.
ReductionGraph holds every registered reduction. find_cheapest_path
searches for the shortest chain from a source problem variant to a target
variant.
{{#include ../../examples/chained_reduction_factoring_to_spinglass.rs:step1}} Factoring → CircuitSAT → SpinGlass {graph: "SimpleGraph", weight: "i32"}
Factoring::new(m, n, target) creates a factoring instance: find two factors
p (m-bit) and q (n-bit) such that p × q = target. Here we factor 6
with two 2-bit factors, expecting 2 × 3 or 3 × 2.
{{#include ../../examples/chained_reduction_factoring_to_spinglass.rs:step2}}solve_reduced reduces the problem to ILP internally and solves it in one
call. It returns a configuration vector for the original problem — no manual
extraction needed. For small instances you can also use BruteForce, but
ILPSolver scales to much larger problems.
{{#include ../../examples/chained_reduction_factoring_to_spinglass.rs:step3}}read_factors decodes the binary configuration back into the two integer
factors.
{{#include ../../examples/chained_reduction_factoring_to_spinglass.rs:step4}}6 = 3 × 2
Each reduction edge carries a polynomial overhead mapping source problem
sizes to target sizes. path_overheads returns the per-edge
polynomials, and compose_path_overhead composes them symbolically into a
single end-to-end formula.
{{#include ../../examples/chained_reduction_factoring_to_spinglass.rs:overhead}}Factoring → CircuitSAT:
num_variables = num_bits_first * num_bits_second
num_assignments = num_bits_first * num_bits_second
CircuitSAT → SpinGlass {graph: "SimpleGraph", weight: "i32"}:
num_spins = num_assignments
num_interactions = num_assignments
SpinGlass {graph: "SimpleGraph", weight: "i32"} → SpinGlass {graph: "SimpleGraph", weight: "f64"}:
num_spins = num_spins
num_interactions = num_interactions
Composed (source → target):
num_spins = num_bits_first * num_bits_second
num_interactions = num_bits_first * num_bits_second
Two solvers are available:
| Solver | Use Case | Notes |
|---|---|---|
BruteForce |
Small instances (<20 variables) | Enumerates all configurations |
ILPSolver |
Larger instances | Enabled by default (ilp feature) |
ILP support is enabled by default. To disable it:
[dependencies]
problemreductions = { version = "0.2", default-features = false }The library exports machine-readable metadata useful for tooling and research:
These files are generated when you build the docs locally.
- reduction_graph.json lists all problem variants and reduction edges
- problem_schemas.json lists field definitions for each problem type
- Try the CLI tool to explore problems and reduction paths from your terminal
- Explore the interactive reduction graph to discover available reductions
- Read the Design guide for implementation details
- Browse the API Reference for full documentation