Skip to content

Add benchmark harness to compare solver revisions for correctness and speed#1

Merged
adelaett merged 7 commits into
mainfrom
alain/simplex-benchmark-harness
Jul 2, 2026
Merged

Add benchmark harness to compare solver revisions for correctness and speed#1
adelaett merged 7 commits into
mainfrom
alain/simplex-benchmark-harness

Conversation

@adelaett

@adelaett adelaett commented Jul 1, 2026

Copy link
Copy Markdown
Owner

What

Adds bench/, a benchmarking harness that decides whether one solver revision is
both correct and significantly faster than another. Intended as the
fitness function and correctness gate for an automated optimization loop.

uv run python evaluate.py --baseline <git-ref> --candidate <git-ref>
# exit 0 = ACCEPT, 1 = REJECT, 2 = infra error

Each ref is checked out into its own throwaway git worktree, built there, and
measured on the same held-out instances.

How it works

  • Correctness gate — every instance's optimum is cross-checked against two
    independent external solvers (GLPK + Gurobi). The solver is never graded against
    its own frozen outputs, so the gate can't be fooled by editing test files.
  • Fitness gate — geometric mean of per-instance solve-time ratios with a 95%
    bootstrap confidence interval; a change is accepted only if the interval lies
    entirely above 1.0 (guards against reading noise as signal). Pivot count is
    reported alongside as a deterministic anchor.
  • Timing — the solver gained a -json --repeat N --warmup W mode that times
    the solve in-process, excluding process-startup/parse overhead. Metrics are
    emitted as JSON and parsed by the harness.
  • Anti-overfitting — instances span random-dense, assignment, bipartite-
    matching, Klee–Minty, and degenerate families, split into disjoint train/heldout
    seed ranges; the verdict is decided on heldout only.

Notes

  • The solver algorithm (solver/tableau.ml) is unchanged — this PR only adds
    the benchmark tooling and a -json metrics-output mode to the CLI.
  • AGENTS.md documents the commands, prerequisites, and invariants.
  • Design follows the SIGPLAN Empirical Evaluation Checklist and Heiser's
    benchmarking-crimes guidance (external baseline, geomean of ratios, significance
    testing, held-out set).
  • Requires uv, dune, glpsol, and gurobi_cl.

adelaett added 7 commits July 1, 2026 11:56
Emit a single JSON object (status, objective as exact rational + float,
pivots, and in-process solve timing) when -json is passed. --repeat N
re-solves N times in-process so the reported time excludes process
startup and parsing. Used by the bench/ harness as the metric source.
bench/ builds each of two git refs in its own throwaway worktree, runs a
seeded corpus of LP instances through the solver's -json metrics mode, and
decides whether the candidate is both correct and significantly faster than
the baseline.

- Correctness gate: every instance's optimum is cross-checked against the
  GLPK and Gurobi oracles, so a change is graded against independent ground
  truth rather than the solver's own frozen outputs.
- Fitness gate: geometric mean of per-instance solve-time ratios with a 95%
  bootstrap confidence interval; a change is accepted only if the interval
  lies entirely above 1.0. Pivot count is reported alongside as a
  deterministic anchor.
- Instances span random-dense, assignment, bipartite-matching, Klee-Minty,
  and degenerate families, split into disjoint train/heldout seed ranges to
  guard against overfitting; the verdict is decided on heldout only.

evaluate.py is the entry point:
  uv run python evaluate.py --baseline <ref> --candidate <ref>

AGENTS.md documents the commands, prerequisites, and invariants.
Run W untimed solves before the timed --repeat ones so the reported
time_min/time_median reflect steady state rather than a cold first solve.
evaluate.py/run_bench.py gain --warmup (default 3), passed to the solver's
-json --warmup. run_bench also fails fast with a clear message when a ref's
solver lacks -json, instead of surfacing an unparseable-JSON error.
- Vectorize compare.py's bootstrap: the 10k-iteration Python loop becomes a
  single numpy draw (identical CI, faster and clearer).
- Drop the unused standalone CLIs from compare.py and run_bench.py; evaluate.py
  is the sole entry point and imports the functions directly.
- Remove dead imports (json in evaluate.py) and dead locals (n, m in two
  instance generators).
- Inline the trivial index_by_instance/load helpers.
@adelaett adelaett merged commit 02c7d9b into main Jul 2, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant