Benchmarks
Every key of benchmarks/cases.yaml, the exact pass and fail rules, the eight validation gates, hardware cases, how results enter the track record and what the workflow page shows, with the lint code for each mistake.
Updated 13 November 2026 View as markdown
On this page
A benchmark case is a known-answer input with the result you expect, stored in benchmarks/cases.yaml. Qsolve runs every case on qsolve test, on submission, and again after publication as ordinary platform runs that build the track record. Each case compares the quantum method with the baseline, your reference method's result. The file is required: without it lint reports E050. Lint codes are listed in Lint codes.
The file
cases:
- name: ring-6-with-chord
input_file: examples/ring_6.json
seed: 1
expect: {max_gap: 0.0}
- name: noise-model-ring-6
input_file: examples/ring_6.json
target: fake:fez@1.6
seed: 1
expect: {max_gap: 0.0, min_fidelity: 0.97, max_two_qubit_gates: 40}
hardware_cases:
- name: hardware-ring-6
input_file: examples/ring_6.json
seed: 1
target: ibm:ibm_fez
expect: {max_gap: 0.0, min_fidelity: 0.97, max_two_qubit_gates: 40}
This is from the max-cut workflow. The file has two top-level keys, cases and hardware_cases. Any other top-level key is ignored. A missing cases key means zero cases, which fails the benchmark_cases and cases_pass gates. A misspelled key inside a case or inside expect is rejected: the unknown key makes qsolve test stop with a validation error, and the platform's gates stage fails. Lint does not check the content of cases, only of hardware_cases, so run qsolve test to find such mistakes.
Case keys
| Key | Required | Default | Meaning |
|---|---|---|---|
name |
Yes | Text shown in the track record. Uniqueness is not checked, but the track record keeps one current row per name and target, so two cases with the same name and target hide each other: keep names unique. | |
input_file |
One of the two | Path, relative to the workflow folder, to a JSON file holding one object. The resolved path must stay inside the workflow folder. | |
input |
One of the two | The input object written inline. If both are given, input wins and input_file is ignored. |
|
params |
No | {} |
Parameter overrides, for example {shots: 20000}. Checked like any run's parameters against the manifest. |
seed |
No | 0 |
Integer seed of the run. |
target |
No | the run's target | For example fake:fez@1.6. Without it the case runs on the target qsolve test was given, sim-cpu by default, and on the platform sim-cpu. |
expect |
No | all defaults below | What the result must satisfy. |
At submission, before the sandbox gates, every case (cases and hardware_cases) is checked and the submission is rejected, naming the case, if: it has neither input nor input_file; input_file is absolute, contains .., leaves the workflow folder (including through a link) or does not exist or is not a JSON object; the input does not match your input schema; or params.shots is above 100,000. One bad case therefore rejects the submission; it never silently skips the others. Put large inputs in benchmarks/inputs/ and refer to them with input_file.
A case that raises before it produces a result is listed as FAIL <ExceptionType>: <message>.
Expectations
Every expect key is optional and the object may be empty.
| Key | Type | Default | The case passes when |
|---|---|---|---|
max_gap |
number | 0.0 |
gap <= max_gap + 1e-9. The gap is (quantum - baseline) / max(abs(baseline), 1e-12). A quantum result that beats the baseline has a negative gap and passes. A gap of 0.05 allows a quantum objective 5% worse. |
max_abs_error |
number | none | abs(quantum - baseline) <= max_abs_error. When set, max_gap is ignored entirely. Use it when the objective is large and the interesting difference small (total energies), and for classifiers, where it bounds the difference of two error rates. It is two-sided: a result better than the baseline by more than this also fails. |
min_fidelity |
number | none | The mitigated fidelity against the ideal output is at least this, and at least 90% of the quantum method's shots could be scored. Values outside 0 to 1 are not rejected. |
min_postselection |
number | none | The share of shots kept by the error checks is at least this. Values outside 0 to 1 are not rejected. |
max_two_qubit_gates |
integer | none | No circuit the quantum method sent has more two-qubit gates than this. |
The objective is lower-is-better, so a maximisation problem returns a negative number from evaluate; see Writing the workflow.
What fails a case whatever expect says
The checks run in this order and the first one that applies gives the message. A case fails when:
| Message | Condition |
|---|---|
no headline metric: quantum and reference could not be compared |
The quantum method or the reference produced no usable value. |
method x failed: ... |
A method's unavailable reason starts with failed: (a classical method crashed). A method that is merely unavailable (MethodUnavailable) does not fail the case by itself. |
method x claimed A, its solution gives B |
A method's reported objective differs from evaluate(solution) by more than 1e-6 * max(1, abs(value)). |
quantum method x sent no circuit to the backend |
The quantum method made no dispatch through ctx. |
no mitigation numbers reported |
min_fidelity or min_postselection is set and the run reported none. |
only N% of the quantum method's shots could be scored against an ideal output ... |
min_fidelity is set and coverage is under 90%. Circuits over 20 qubits or with mid-circuit measurement cannot be scored. |
fidelity F < B |
The mitigated fidelity is below min_fidelity. |
post-selection P < B |
The share of shots kept is below min_postselection. |
no circuit size reported |
max_two_qubit_gates is set and no circuit was recorded. |
N two-qubit gates > M |
A circuit exceeds max_two_qubit_gates. |
Missing max_gap or max_abs_error fails without a message; the table shows the gap. The same rules decide a case in qsolve test, in the platform's gates stage, in the history runs and in hardware runs.
Comparison with the baseline
Every result carries a gap, the absolute error, and a headline accuracy with a verdict, "Matches the best classical answer" or "Below the best classical answer". The baseline is the reference method. Qsolve recomputes every method's objective with your evaluate where a method returns a solution; the platform, not your code, produces each number. Classical methods run with a backend that refuses circuits.
You choose one of three metric kinds in the manifest under metric. The default is relative_gap.
metric.kind |
Accuracy | Use for |
|---|---|---|
relative_gap |
1 - max((q - r) / abs(r), 0). Only a worse result loses accuracy. |
Optimisation with a lower-is-better objective. |
relative_error |
1 - abs(q - r) / abs(r), with stderr / abs(r) as its plus-or-minus. |
Measured quantities such as energies. |
error_rate |
1 - q, where q is a share of mistakes. The plus-or-minus is a 95% Wilson interval for n_test cases (the quantum method's details["n_test"]). |
Classifiers. |
q is the quantum objective, r the baseline's. A result matches when any of these holds:
relative_gaporrelative_error: accuracy is at least1 - tolerance.relative_gaporrelative_error:abs(q - r)is at mosttolerance_abs, in the objective's units.relative_gaporrelative_error: the difference is at most twice the quantum method'sstderr. This counts only whilestderris at most a quarter ofabs(r).relative_gap: the quantum objective is no worse than the baseline's.error_rate:q <= r + tolerance + tolerance_abs, or the lower end of the 95% interval ofqis at mostr + tolerance + tolerance_abs.
tolerance is between 0 and 1 (default 0), tolerance_abs is at least 0 (default none), note is at most 600 characters and is shown beside the number. Set a tolerance only when exact equality is the wrong standard, for example chemical accuracy of 1.6 mHa, written tolerance_abs: 0.0016. The manifest fields are documented in Manifest.
This verdict is separate from expect: a case can pass its expect and still show "Below the best classical answer", and the other way round.
The validation gates
qsolve test and the platform's gates stage run these gates on sim-cpu with no network. A case with its own target keeps it.
| Gate | Passes when |
|---|---|
manifest |
The manifest and both schemas load and validate. |
example_inputs |
At least one example exists and every example validates against the input schema. |
benchmark_cases |
At least one case is defined. |
methods_declared |
The manifest declares at least two methods. |
fidelity_checked |
At least one case sets a non-zero min_fidelity and runs on a fake: or ibm: target. |
cases_pass |
At least one case ran and every case meets its expect. |
reproducible |
Re-running the first case that ran, same input, params, target and seed, gives the same result hash, or a result equal up to relative 1e-8 (absolute 1e-12) on every number. Wall-clock method times are ignored. |
estimate_honest |
Your estimate() is within a factor of two of the circuits and the shots that case sent (ratio from 0.5 to 2.0, each checked only when the run sent some). Passes with a note when you give no estimate() or the run sent no circuits. |
reproducible and estimate_honest are added only when at least one case ran to a result, so a workflow whose every case raises shows six gates. All gates must pass to publish. Gate messages and fixes are in Troubleshooting.
Noise-model cases
Include one case on a noisy target with min_fidelity set; the fidelity_checked gate needs it. Use fake:fez@1.6, a device noise model with errors scaled up by 1.6 (the scale is between 1 and 10). It runs on a CPU and costs nothing. This is from generic-qubo:
- name: noise-model-qubo-5
input_file: examples/qubo_5.json
target: fake:fez@1.6
seed: 1
expect: {max_gap: 0.0, min_fidelity: 0.97, max_two_qubit_gates: 30}
qsolve test --target fake:fez also satisfies the gate for a min_fidelity case without its own target, but the platform always validates on sim-cpu, so declare the target in the case.
Do not put ibm: targets under cases. Validation has no network and no device access, so such a case cannot run and fails. Lint does not flag it. Real-device cases go under hardware_cases.
Hardware cases
A device run is slow, scarce and costs money, so Qsolve never starts one for you and validation ignores them. List the cases you want measured on a real IBM device under hardware_cases. This is from electrolyte-solvent-screen:
hardware_cases:
- name: hardware-water-only
input:
basis: sto-3g
solvents: [water]
params: {shots: 1000}
seed: 1
target: ibm:ibm_fez
expect: {max_abs_error: 0.02, min_fidelity: 0.97, min_postselection: 0.9}
A hardware case has the same keys as a case, and target is required.
| Rule | Value | Lint code |
|---|---|---|
hardware_cases must load: valid YAML, keys known, types right |
E052 |
|
target matches ibm:<device>: letters, digits, _ . -, 1 to 60 characters |
for example ibm:ibm_fez |
E052 |
| At most this many entries | 3 | E053 |
The manifest lists ibm under targets.qpu |
E054 |
|
| All hardware cases together, inputs written out, are at most | 600 KB | none: publication stops with "The hardware cases are too large" |
Each case has input or an input_file that exists inside the workflow folder |
none: the submission is rejected with a message naming the case | |
| Each case's input matches your input schema | none: the submission is rejected with the schema path that failed |
Lint itself does not check these last two; the platform does, right after lint and before the gates.
Keep them few and small: a case a device can handle at a price you accept.
How they run
- Publish the workflow. At publication Qsolve copies each hardware case, with its input file written inline, into the version. Hardware cases of a version are fixed at that moment.
- Open the workflow page as its developer (admins can too) and choose Get a quote. The platform creates one run per case in your account and prices each. Nothing runs until you confirm each quote. Only the latest published version's cases are started. If a previous batch is still being priced or run, the request is refused as in progress.
- The run needs the target to be in the manifest's
targetsand the device to be available, or it is refused withtarget_not_supportedor an availability error. - The runs are ordinary runs paid from your own credits, each capped by the platform's per-run limit (50 credits unless the operator changed it).
- A maintenance pass adds each finished run to the track record, 20 runs per pass, so it can take a minute. A run that failed for platform reasons (
platform_error,backend_unavailable,run_too_large,insufficient_credits,unsupported_target,cancelled) says nothing about the workflow and is not recorded. A run that failed because of the workflow (a crash, a timeout) is recorded as failed. - The pass or fail uses the same rules as any case.
A newer result of the same case name on the same target replaces the earlier one, which stays as history.
How results enter the track record
| Source | When | Target | Billed to |
|---|---|---|---|
| Gates stage | On every submission | sim-cpu and each case's own target |
Nothing recorded; a check only |
| History stage | After publication of a new version | Each cases entry, on its own target or sim-cpu |
The platform's system account, at most 20 credits and 900 seconds per case, three cases at a time |
| Hardware cases | When the developer confirms a quote | Each hardware_cases entry |
The developer |
The history stage runs each case through the real platform pipeline (estimate, credit hold, sandbox, settlement), so the recorded cost is what a researcher would pay. For each run Qsolve stores the case name, target, device, pass or fail, gap, absolute error, both objectives, shots, run time, credits, the seed, the params and the quality numbers. A case the platform could not run is not recorded and is counted in the submission report. If the history stage fails as a whole, the stage shows as skipped and the version stays published. Resubmitting identical content (the same version and digest) does not run the history stage again when the version already has recorded results, so the track record is never duplicated.
Resubmitting identical content of an existing version changes nothing. A new version starts with its own empty track record.
What the workflow page shows
| Part | What it contains |
|---|---|
| Track record | Latest result of each case on a sim- target for the current version, with accuracy, verdict, cost and circuit numbers. The headline accuracy is the median over these rows. The workflow cards count passed and total cases on sim- targets only. |
| Noise-model estimate | Latest results on fake: targets, with fidelity and share of shots kept. Shown apart, not in the headline. |
| On quantum hardware | Latest results on every other target (ibm:). Shown apart, not in the headline, because the hardware measures the device as much as the workflow. |
| Earlier results | Older results of the same case and target, kept as history. |
Versions and track record: only the current version's sim- and fake: results are shown. Hardware results of earlier versions stay on the page, labelled with their version, until a newer run of the same case replaces them, because they are scarce. See Versioning.
Lint codes for benchmark mistakes
| Mistake | Code |
|---|---|
benchmarks/cases.yaml missing |
E050 |
hardware_cases unreadable or invalid, or a hardware case without an ibm:<device> target |
E052 |
More than three hardware_cases |
E053 |
hardware_cases without qpu: [ibm] in the manifest |
E054 |
Everything else in the file is checked by qsolve test, not by lint.
Writing good cases
- Use inputs where you know the answer, or where the reference is exact.
- Cover the range: a tiny case, one near your envelope, a degenerate one.
generic-qubohas cases with and without its cardinality constraint, up to 18 variables. - Include a case where the quantum method is expected to do worse, with an honest tolerance.
portfolio-optimizationallowsmax_gap: 0.05and0.10on its larger synthetic cases;max-cutincludes a complete graph, where low-depth QAOA struggles. - Keep cases quick. The whole gates run, including the second run of the first case, is one sandbox job. Its time limit is your manifest's
resources.timeout_sraised to at least 300 seconds and capped at 7200; its memory isresources.memory_mbcapped at 8192. - For classifiers, use test sets of at least 200 points. On fewer, one point moves accuracy by several percent and the interval is wide.
quantum-kernel-classifierbounds the error-rate difference withmax_abs_error: 0.06. - Set noise-model bounds from measured values and leave a margin; the
electrolyte-solvent-screenworkflow sets its tolerances at about three times the shot noise.