|
| 1 | +# Overhead System Redesign |
| 2 | + |
| 3 | +**Issue:** #61 — Introduce overhead system |
| 4 | +**Date:** 2026-02-25 |
| 5 | +**Approach:** Macro-first dual emission |
| 6 | + |
| 7 | +## Summary |
| 8 | + |
| 9 | +Replace the current `Polynomial`-based overhead system with a general `Expr` AST, compile-time macro-parsed expression strings, and per-problem inherent getters. The proc macro emits both compiled Rust code (for evaluation + compiler validation) and symbolic `Expr` AST literals (for composition + export). |
| 10 | + |
| 11 | +## Motivation |
| 12 | + |
| 13 | +Three pain points with the current system: |
| 14 | +1. **Ergonomics** — `problem_size_names()`/`problem_size_values()` parallel arrays are awkward; `poly!` macro is verbose |
| 15 | +2. **Correctness** — variable name mismatches between overhead expressions and problem size fields are caught only at runtime |
| 16 | +3. **Simplification** — `Polynomial` only supports sums of monomials; general math (exp, log) requires a new representation anyway |
| 17 | + |
| 18 | +## Design |
| 19 | + |
| 20 | +### 1. Expression AST (`Expr`) |
| 21 | + |
| 22 | +Replaces `Polynomial` and `Monomial` with a general math expression tree. |
| 23 | + |
| 24 | +```rust |
| 25 | +// src/expr.rs (replaces src/polynomial.rs) |
| 26 | + |
| 27 | +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] |
| 28 | +pub enum Expr { |
| 29 | + Const(f64), |
| 30 | + Var(&'static str), |
| 31 | + Add(Box<Expr>, Box<Expr>), |
| 32 | + Mul(Box<Expr>, Box<Expr>), |
| 33 | + Pow(Box<Expr>, Box<Expr>), |
| 34 | + Exp(Box<Expr>), |
| 35 | + Log(Box<Expr>), |
| 36 | + Sqrt(Box<Expr>), |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +Key operations: |
| 41 | +- `eval(&self, vars: &ProblemSize) -> f64` |
| 42 | +- `substitute(&self, mapping: &HashMap<&str, &Expr>) -> Expr` |
| 43 | +- `variables(&self) -> HashSet<&'static str>` |
| 44 | +- `is_polynomial(&self) -> bool` |
| 45 | +- `degree(&self) -> Option<u32>` |
| 46 | +- `Display` for human-readable formulas |
| 47 | +- `simplify(&self) -> Expr` — minimal constant folding |
| 48 | + |
| 49 | +### 2. Problem Getters |
| 50 | + |
| 51 | +Remove `problem_size_names()` and `problem_size_values()` from the `Problem` trait. Each problem type implements inherent getter methods instead. |
| 52 | + |
| 53 | +```rust |
| 54 | +// Before: trait methods returning parallel arrays |
| 55 | +impl Problem for MaximumIndependentSet<SimpleGraph, i32> { |
| 56 | + fn problem_size_names() -> &'static [&'static str] { &["num_vertices", "num_edges"] } |
| 57 | + fn problem_size_values(&self) -> Vec<usize> { |
| 58 | + vec![self.graph().num_vertices(), self.graph().num_edges()] |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +// After: inherent methods — natural, compiler-checked, IDE-friendly |
| 63 | +impl<G: Graph, W: WeightElement> MaximumIndependentSet<G, W> { |
| 64 | + pub fn num_vertices(&self) -> usize { self.graph().num_vertices() } |
| 65 | + pub fn num_edges(&self) -> usize { self.graph().num_edges() } |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +### 3. Proc Macro — Dual Emission |
| 70 | + |
| 71 | +The `#[reduction]` macro parses expression strings at compile time and emits two outputs. |
| 72 | + |
| 73 | +User-facing syntax: |
| 74 | +```rust |
| 75 | +#[reduction(overhead = { |
| 76 | + num_vars = "num_vertices", |
| 77 | + num_constraints = "num_edges + num_vertices^2", |
| 78 | +})] |
| 79 | +impl ReduceTo<QUBO<f64>> for MaximumIndependentSet<SimpleGraph, i32> { ... } |
| 80 | +``` |
| 81 | + |
| 82 | +Macro emits: |
| 83 | +1. **Compiled evaluation function** — `src.num_vertices()`, `src.num_edges()` calls. Compiler catches missing getters. |
| 84 | +2. **Symbolic Expr AST** — `Expr::Add(...)` construction for composition/export. |
| 85 | + |
| 86 | +Expression grammar (Pratt parser, ~200 LOC in proc macro crate): |
| 87 | +``` |
| 88 | +expr = term (('+' | '-') term)* |
| 89 | +term = factor (('*' | '/') factor)* |
| 90 | +factor = base ('^' factor)? |
| 91 | +base = NUMBER | IDENT | func_call | '(' expr ')' |
| 92 | +func_call = ('exp' | 'log' | 'sqrt') '(' expr ')' |
| 93 | +``` |
| 94 | + |
| 95 | +### 4. Updated `ReductionOverhead` and `ReductionEntry` |
| 96 | + |
| 97 | +```rust |
| 98 | +pub struct ReductionOverhead { |
| 99 | + pub output_size: Vec<(&'static str, Expr)>, // Expr replaces Polynomial |
| 100 | +} |
| 101 | + |
| 102 | +pub struct ReductionEntry { |
| 103 | + // ...existing fields... |
| 104 | + pub overhead_fn: fn() -> ReductionOverhead, // symbolic (composition/export) |
| 105 | + pub overhead_eval_fn: fn(&dyn Any) -> ProblemSize, // compiled (evaluation) |
| 106 | + // REMOVED: source_size_names_fn, target_size_names_fn |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +`PathCostFn` uses the symbolic `ReductionOverhead` (via `Expr::eval`) since it operates on type-erased `ProblemSize` during graph traversal. |
| 111 | + |
| 112 | +### 5. Export Pipeline |
| 113 | + |
| 114 | +JSON format gains both structured AST and display string: |
| 115 | +```json |
| 116 | +{ |
| 117 | + "overhead": [{ |
| 118 | + "field": "num_vars", |
| 119 | + "expr": {"Pow": [{"Var": "num_vertices"}, {"Const": 2.0}]}, |
| 120 | + "formula": "num_vertices^2" |
| 121 | + }] |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +The paper reads `formula` strings — no Typst code changes needed. |
| 126 | + |
| 127 | +## Migration Strategy |
| 128 | + |
| 129 | +| Phase | Description | Files | Risk | |
| 130 | +|-------|-------------|-------|------| |
| 131 | +| 1 | Add `Expr` type alongside `Polynomial` | 2-3 new | Low (additive) | |
| 132 | +| 2 | Update proc macro with Pratt parser, support new syntax | 1 file | Medium | |
| 133 | +| 3 | Add inherent getters to all problem types | ~15 model files | Low (additive) | |
| 134 | +| 4 | Migrate all reductions to new syntax | ~20 rule files | Low (mechanical) | |
| 135 | +| 5 | Remove deprecated APIs (`problem_size_*`, `Polynomial`, `poly!`) | ~10 files | Medium (breaking) | |
| 136 | +| 6 | Update documentation and regenerate exports | 3-4 files | Low | |
| 137 | + |
| 138 | +Phases 1-3 are purely additive. Phase 4 is bulk migration. Phase 5 is cleanup. |
0 commit comments