|
| 1 | +# Design: ILP Type Parameter for Variable Domain |
| 2 | + |
| 3 | +## Motivation |
| 4 | + |
| 5 | +The current `ILP` struct carries a `bounds: Vec<VarBounds>` field, but every reduction into ILP produces binary variables (`VarBounds::binary()`), except Factoring which uses bounded integers for carries. Meanwhile, ILP → QUBO requires binary variables and checks this with a runtime assert. The `bounds` field adds complexity without value for the common case, and makes the ILP → QUBO overhead expression inaccurate (slack bits depend on coefficient magnitudes, not expressible symbolically). |
| 6 | + |
| 7 | +## Design |
| 8 | + |
| 9 | +### Type Parameter |
| 10 | + |
| 11 | +Replace `ILP` with `ILP<V>` where `V` determines the variable domain: |
| 12 | + |
| 13 | +```rust |
| 14 | +pub struct ILP<V> { |
| 15 | + pub num_vars: usize, |
| 16 | + pub constraints: Vec<LinearConstraint>, |
| 17 | + pub objective: Vec<(usize, f64)>, |
| 18 | + pub sense: ObjectiveSense, |
| 19 | + _marker: PhantomData<V>, |
| 20 | +} |
| 21 | +``` |
| 22 | + |
| 23 | +- `ILP<bool>`: Binary variables. `dims() = vec![2; n]`. Config `0 → 0`, `1 → 1`. |
| 24 | +- `ILP<i32>`: Non-negative integers `0..2_147_483_647`. `dims() = vec![i32::MAX as usize; n]`. Config index = value. Bounded ranges expressed as constraints (e.g., `x_i <= 5`). |
| 25 | + |
| 26 | +The `bounds` field is removed entirely. |
| 27 | + |
| 28 | +### Reduction Graph |
| 29 | + |
| 30 | +Two variant nodes following the existing pattern (like `MIS {graph: "SimpleGraph"}` / `MIS {graph: "KingsSubgraph"}`): |
| 31 | + |
| 32 | +- `ILP {variable: "bool"}` — binary integer linear programming |
| 33 | +- `ILP {variable: "i32"}` — general integer linear programming |
| 34 | + |
| 35 | +Natural cast edge: `ILP<bool>` → `ILP<i32>` (zero cost — every binary program is a valid integer program). |
| 36 | + |
| 37 | +### Impact on Reductions |
| 38 | + |
| 39 | +| Reduction | Before | After | |
| 40 | +|---|---|---| |
| 41 | +| MIS → ILP | `ILP` with `VarBounds::binary()` | `ILP<bool>` | |
| 42 | +| MVC → ILP (if re-added) | `ILP` with `VarBounds::binary()` | `ILP<bool>` | |
| 43 | +| MaxClique → ILP | `ILP` with `VarBounds::binary()` | `ILP<bool>` | |
| 44 | +| MaxMatching → ILP | `ILP` with `VarBounds::binary()` | `ILP<bool>` | |
| 45 | +| MinDS → ILP | `ILP` with `VarBounds::binary()` | `ILP<bool>` | |
| 46 | +| MinSetCovering → ILP | `ILP` with `VarBounds::binary()` | `ILP<bool>` | |
| 47 | +| KColoring → ILP | `ILP` with `VarBounds::binary()` | `ILP<bool>` | |
| 48 | +| TSP → ILP | `ILP` with `VarBounds::binary()` | `ILP<bool>` | |
| 49 | +| CircuitSAT → ILP | `ILP` with `VarBounds::binary()` | `ILP<bool>` | |
| 50 | +| Factoring → ILP | `ILP` with mixed bounds | `ILP<i32>` with carry ranges as constraints | |
| 51 | +| ILP → QUBO | Runtime assert binary | `impl ReduceTo<QUBO<f64>> for ILP<bool>` — compile-time guarantee | |
| 52 | +| QUBO → ILP | Produces binary ILP | `ILP<bool>` | |
| 53 | + |
| 54 | +### ILP → QUBO Overhead Fix |
| 55 | + |
| 56 | +With `ILP<bool>`, all source variables are binary. Slack variables from inequality constraints have a worst-case count bounded by `num_constraints * ceil(log2(num_vars + 1))`. The overhead expression becomes: |
| 57 | + |
| 58 | +```rust |
| 59 | +#[reduction(overhead = { |
| 60 | + num_vars = "num_vars + num_constraints * num_vars", // worst-case slack |
| 61 | +})] |
| 62 | +impl ReduceTo<QUBO<f64>> for ILP<bool> { ... } |
| 63 | +``` |
| 64 | + |
| 65 | +This removes ILP → QUBO from the `UNTRUSTED_EDGES` list in `analysis.rs`. |
| 66 | + |
| 67 | +### ILP Solver |
| 68 | + |
| 69 | +The `ILPSolver` works with both `ILP<bool>` and `ILP<i32>`: |
| 70 | +- `ILP<bool>`: HiGHS variables set as binary `[0, 1]` |
| 71 | +- `ILP<i32>`: HiGHS variables set as integer `[0, i32::MAX]`, constraints provide effective bounds |
| 72 | + |
| 73 | +No `VarBounds` needed in the solver interface. |
| 74 | + |
| 75 | +### VarBounds |
| 76 | + |
| 77 | +Removed from `ILP`. Retained for `ClosestVectorProblem<T>` which genuinely needs per-variable bounds for lattice enumeration. |
| 78 | + |
| 79 | +### Constructors |
| 80 | + |
| 81 | +- `ILP::<bool>::new(num_vars, constraints, objective, sense)` — replaces `ILP::binary()` |
| 82 | +- `ILP::<i32>::new(num_vars, constraints, objective, sense)` — general integer |
| 83 | +- `ILP::binary()` convenience removed (redundant with `ILP::<bool>::new`) |
| 84 | + |
| 85 | +### variant() Implementation |
| 86 | + |
| 87 | +```rust |
| 88 | +impl<V: VariableDomain> Problem for ILP<V> { |
| 89 | + fn variant() -> Vec<(&'static str, &'static str)> { |
| 90 | + vec![("variable", V::NAME)] |
| 91 | + } |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +Where `VariableDomain` is a sealed trait implemented for `bool` ("bool") and `i32` ("i32"). |
| 96 | + |
| 97 | +### Complexity |
| 98 | + |
| 99 | +```rust |
| 100 | +declare_variants! { |
| 101 | + ILP<bool> => "2^num_vars", |
| 102 | + ILP<i32> => "num_vars^num_vars", |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +Binary ILP is `O(2^n)` brute-force; general ILP is `O(n^n)` since each variable can take up to `n^n` combinations. |
0 commit comments