Skip to content

Benchmark coverage: solver_direct, storage/MILP cases, sparsity sweep, speed-of-light floor #147

Description

@FBumann

bench/ (#143) ships two cases on one sink. These are the gaps, in the order they would change a decision. Each is a small addition — the runner, the parity gate and the report are already case- and arm-agnostic.

1. A solver_direct arm

The biggest gap, because the LP file is not the shipped path. bench/ currently measures write_lp against to_file(io_api='lp-polars'), and the phase split (see the build-perf issue) shows the emit phase is at parity while build is 6-10x slower. On solver_direct there is no emit phase, so the ratio users actually experience is the build ratio — unmeasured today.

Wants a matching linopy arm (io_api="direct" / to_highspy) and a peak that separates the build side from HiGHS's own model, which hard rule 4 exempts. The earlier harness measured 5.76 GB end-to-end at 35.6M variables, dominated by the solver.

2. storage — the roll self-join

roll/shift is the only locality class in the ceiling with no eager analogue in cost: xarray shifts an array, we join on dim.ord - k. It is also the shape that makes real energy models large (temporal coupling), so its absence from the ladder is the most misleading thing about the current numbers. examples/storage.yaml is the model; it needs a scaling twin under bench/models/ with data-driven dimension members.

3. A MILP through solver_direct

Solve time dwarfs build, so this measures the handoff rather than the build — a different question, and the one where being 2x slower to build is irrelevant. tests/test_milp.py has the shape. Also the only case where vtype streaming gets exercised at scale.

4. A where-density sweep

Masks are row absence here and NaN-dense in xarray. That is a claim ARCHITECTURE.md makes and nothing has ever measured. Sweeping mask density (100% / 30% / 5% active) is the one axis where the gap should be qualitative rather than 2x — the eager lane pays for every masked-out cell and we pay for none.

The dispatch model already carries where: p_max > 0; the generator only needs to zero a configurable fraction of p_max, and the ladder a density parameter.

5. A hand-written highspy/CSR arm — the speed-of-light floor

Without it, "2x slower than linopy" has no denominator: there is no way to tell engine overhead from the irreducible cost of emitting 10^7 coefficients. With it, the sentence becomes "we are at Nx the floor and linopy is at Mx", which is a claim about engineering rather than a ranking.

Not a fair modelling comparison — it hardcodes one model — which is exactly why it belongs as a floor rather than as an arm in the headline table.

6. A 10^8-variable capability run

The old harness's 107M-variable run (0.57 GB peak, a model whose dense build does not fit on the machine) is the single most persuasive number this project has ever produced, and it is currently in docs/benchmarks.md under "not reproducible". One rung above l on the existing ladder gets it back, by hand rather than in the default run.

Explicitly not planned

  • CI regression gating. Shared runners make peak RSS noisy enough that the gate either fails constantly or is loosened until it catches nothing.
  • Pyomo / PuLP / Calliope arms. Beating a Python-object modelling layer on build time proves nothing anyone disputes, and the high-level frameworks would mostly be measuring their own data prep. If it comes up, a paragraph in the doc answers it more honestly than a bar chart.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:engineLowering, IR, relational executor, sinkschorePackaging, CI, dependencies

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions