Benchmarking
Benchmarking QUBO samplers is partly about runtime and partly about solution quality. QUBODrivers exposes both through standard JuMP/MOI result queries, so a simple benchmark can collect elapsed time, number of returned states, objective values, read counts, objective-value frequencies, and the best state found.
Benchmark a JuMP Model
Build the problem once, then pass its MOI backend to QUBODrivers.benchmark. The benchmark helper copies that model into each sampler, applies any sampler configuration, runs MOI.optimize!, and returns timing and sample-quality metadata.
using JuMP
using QUBODrivers
model = Model()
@variable(model, x[1:4], Bin)
@objective(model, Min, -x[1] - 2x[2] - x[3] + 2x[1] * x[2] + x[3] * x[4])
result = QUBODrivers.benchmark(RandomSampler.Optimizer, backend(model)) do sampler
MOI.set(sampler, MOI.RawOptimizerAttribute("num_reads"), 100)
MOI.set(sampler, MOI.RawOptimizerAttribute("seed"), 1)
end
(result.variables, result.result_count, result.objective_summary.minimum)(4, 16, -3.0)For large models, use stochastic samplers such as RandomSampler as the cheap baseline. ExactSampler is useful for small reference instances and smoke tests, but its exhaustive enumeration grows exponentially.
Comparing Samplers
The built-in utility samplers are useful baselines. ExactSampler gives a complete reference on small instances, while RandomSampler provides a cheap stochastic baseline.
using QUBODrivers
source = MOI.Utilities.Model{Float64}()
x, _ = MOI.add_constrained_variables(source, fill(MOI.ZeroOne(), 3))
Q = [
-1.0 2.0 2.0
2.0 -1.0 2.0
2.0 2.0 -1.0
]
MOI.set(source, MOI.ObjectiveSense(), MOI.MIN_SENSE)
MOI.set(
source,
MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(),
MOI.ScalarQuadraticFunction{Float64}(
MOI.ScalarQuadraticTerm{Float64}[
MOI.ScalarQuadraticTerm{Float64}(Q[i, j], x[i], x[j])
for i = 1:3 for j = 1:3 if i != j
],
MOI.ScalarAffineTerm{Float64}[
MOI.ScalarAffineTerm{Float64}(Q[i, i], x[i])
for i = 1:3
],
0.0,
),
)
exact = QUBODrivers.benchmark(ExactSampler.Optimizer, source)
random = QUBODrivers.benchmark(RandomSampler.Optimizer, source) do sampler
MOI.set(sampler, MOI.RawOptimizerAttribute("num_reads"), 100)
MOI.set(sampler, MOI.RawOptimizerAttribute("seed"), 1)
end
(exact.best_objective, random.best_objective)(-1.0, -1.0)QUBODrivers.benchmark copies a user-provided MOI model into the sampler and returns a named tuple with solver metadata, objective values, result count, best state, best objective value, read counts, objective summary, MOI solve time, wall-clock time around MOI.optimize!, and status fields. result.objective_summary contains minimum, maximum, a read-weighted mean, total_reads, and a histogram mapping each observed objective value to its total read count. The optional do block receives the raw sampler optimizer after model copy and before optimization. If no model is provided, QUBODrivers.benchmark falls back to a small built-in QUBO instance as a smoke benchmark.
Timing Information
Samplers store timing metadata in the returned sample set. After solving, MOI.get(backend(model), MOI.SolveTimeSec()) returns the effective solve time reported through MOI. This can differ from wall-clock timing around optimize!, especially for wrappers that separate model conversion, backend submission, queue time, and sample decoding.
What to Report
For stochastic or hardware-backed samplers, report enough context for the result to be reproducible:
- package and backend versions;
- problem size and density;
- objective sense and variable domain;
- sampler attributes, especially seeds, number of reads, time limits, and threads;
- best objective value, distribution of objective values, and success rate if a reference optimum is known;
- wall-clock time and solver-reported timing metadata.
Tips
- Use
QUBODrivers.benchmarkas a quick interface smoke benchmark for a new sampler wrapper. - Use
ExactSampleron small instances to obtain a known optimum. - Run multiple independent trials for stochastic samplers and report summary statistics.
- Keep model generation outside the timed region unless model construction is part of the benchmark.
- Use
BenchmarkTools.jlfor micro-benchmarks with@benchmarkor@btime. - Scale problem sizes gradually to understand how sampler performance degrades.
QUBODrivers.benchmark — Function
benchmark(optimizer::Type{S}; objective_sense=MOI.MIN_SENSE) where {S<:AbstractSampler}
benchmark(config!::Function, optimizer::Type{S}; objective_sense=MOI.MIN_SENSE) where {S<:AbstractSampler}
benchmark(optimizer::Type{S}, source::MOI.ModelLike) where {S<:AbstractSampler}
benchmark(config!::Function, optimizer::Type{S}, source::MOI.ModelLike) where {S<:AbstractSampler}Run a QUBO benchmark problem with a sampler implementation.
When source is provided, benchmark the sampler on that MOI model. The no-source methods use a small built-in QUBO instance as a smoke benchmark.
The optional config! function receives the raw sampler optimizer after the source model is copied and before optimization. This is useful for setting sampler attributes such as seeds, number of reads, time limits, or warm-starts.
The return value is a named tuple with solver metadata, benchmark problem size, result count, objective values, read counts, objective-value summary statistics, the best result, MOI solve time, measured wall time around MOI.optimize!, and solver status fields.