dr.David
Rhodus
Chapter 44 / 27

Run Your First Hybrid Experiment

Operating Quantum Computers · 4 min read

The first experiment combines a quantum circuit model with classical scheduling, analysis, and evidence storage. Its question is deliberately narrow: does the measured Bell score satisfy a declared quality threshold with enough precision? The experiment is small enough to inspect end to end, while retaining the distinctions needed in larger systems.

Open the lab and Python project instructions. Use the project's supported Python environment and locked dependencies. The command-line service and the browser laboratory implement the same declared measurement model, but use different sampling implementations. Their seeds do not promise matching bitstrings across engines.

Understand the configured workload

The baseline prepares Φ+, applies a two-qubit depolarizing channel once after preparation, and then measures one of XX, YY, or ZZ. Its preparation parameter is p = 0.02. Each measured bit independently flips with probability q = 0.01 in the readout model. Basis rotations are ideal in this teaching model.

The workload has cumulative checkpoints at 100, 200, 500, 1,000, 2,000, 5,000, and 10,000 shots per basis. At each permitted look, the classical analysis updates correlations, the raw Bell score, and simultaneous uncertainty bounds. Acceptance requires a lower bound of at least 0.9 and a half-width no larger than 0.04. Rejection occurs when the upper bound falls below 0.9. Otherwise execution continues until its declared budget, where the result can remain inconclusive.

Runnable source · quantum_ops/model.py · python
"""The executable preparation circuit and declared measurement model."""
from importlib.metadata import version
from pathlib import Path
import hashlib
import platform
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel, ReadoutError, depolarizing_error

BASES = ("XX", "YY", "ZZ")
OUTCOMES = ("00", "01", "10", "11")

def circuit(basis: str, p: float, measure: bool = True) -> QuantumCircuit:
    if basis not in BASES:
        raise ValueError("Expected XX, YY, or ZZ")
    qc = QuantumCircuit(2, 2) if measure else QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    # Aer lambda=p means (1-p) rho + p I/4, not total Pauli error probability.
    qc.append(depolarizing_error(p, 2).to_instruction(), [0, 1])
    if basis == "XX":
        qc.h([0, 1])
    elif basis == "YY":
        qc.sdg([0, 1])
        qc.h([0, 1])
    if measure:
        qc.measure([0, 1], [0, 1])
    return qc

def analytic_probabilities(basis: str, p: float, q: float) -> dict[str, float]:
    if basis not in BASES or not 0<=p<=1 or not 0<=q<=.49: raise ValueError("Invalid model parameters")
    c = (1 - p) * (1 - 2 * q) ** 2 * (-1 if basis == "YY" else 1)
    return dict(zip(OUTCOMES, [(1+c)/4, (1-c)/4, (1-c)/4, (1+c)/4]))

def sample(basis: str, shots: int, p: float, q: float, seed: int) -> dict[str, int]:
    noise = NoiseModel()
    noise.add_all_qubit_readout_error(ReadoutError([[1-q, q], [q, 1-q]]))
    simulator = AerSimulator(method="density_matrix", noise_model=noise, max_parallel_threads=1)
    qc = transpile(circuit(basis, p), simulator, optimization_level=0, seed_transpiler=seed)
    result = simulator.run(qc, shots=shots, seed_simulator=seed).result().get_counts()
    return {key: int(result.get(key, 0)) for key in OUTCOMES}

def engine_versions() -> dict[str, str]:
    return {"engine": "qiskit-aer-density-matrix", "python": platform.python_version(), "qiskit": version("qiskit"), "aer": version("qiskit-aer"), "numpy": version("numpy"), "model": "bell-depolarizing-readout-v1", "implementation": hashlib.sha256(b"".join((Path(__file__).parent/name).read_bytes() for name in ("model.py","contract.py","runner.py","api.py"))).hexdigest()}

Reading this source connects the circuit, channel location, basis rotations, readout model, and sampling settings. Keep that connection visible when changing an experiment. A parameter named “noise” is insufficient unless its operation and placement are specified.

Execute and inspect

After installing the project, run:

Illustrative listing · sh
quantum-ops --db runs.sqlite3 run --scenario baseline

The local runner writes checkpoint evidence to SQLite and prints the resulting evidence object inside a list of scenario runs. Record the run identifier. Use it to inspect or validate the stored result:

Illustrative listing · sh
quantum-ops --db runs.sqlite3 inspect RUN_ID
quantum-ops --db runs.sqlite3 validate RUN_ID

RUN_ID is a placeholder to replace with the actual identifier. A successful validation checks the package's declared structure and consistency. It does not turn simulated evidence into a hardware certificate.

The evidence includes configuration, engine identity, contract hashes, incremental batches, cumulative analysis history, execution state, and scientific disposition. Batch counts are increments. The analysis history reports cumulative shots. Mixing these two meanings would double-count observations and understate uncertainty.

Worked example: read the captured baseline

The captured Aer baseline stops at 5,000 shots per basis, or 15,000 shots across all three settings. Its cumulative correlations are XX = 0.9380, YY = −0.9392, and ZZ = 0.9472. Substituting them into the score expression gives 0.9561.

The simultaneous half-width is approximately 0.038923. Its lower bound, approximately 0.917177, exceeds 0.9, and the half-width is below 0.04. Both acceptance conditions hold. Earlier checkpoints have attractive point estimates but do not yet satisfy the required bounds.

The same baseline with a budget of 1,000 shots per basis stops inconclusively in the captured evidence. Its score is approximately 0.9545, but its lower bound is only approximately 0.8675. A high point estimate and a completed job therefore do not supply the missing precision.

Make one controlled change

Run the budget scenario to observe a constrained stopping decision, or increase the readout parameter to explore measurement degradation. Change one assumption at a time and retain the new configuration. The preparation fidelity in the analytic model and the raw measured score are different quantities; a readout change can alter the latter without changing the prepared state.

The interrupted scenario stops after committed checkpoints. Resuming it preserves that recorded prefix and continues the local sampling plan. A new recovery context is represented by a separate run. Do not splice its counts into the old context merely because both appear in one scenario demonstration.

Exercise: audit the shot accounting

A run has reached the 2,000 checkpoint and next reaches 5,000. How many additional shots are required across the three bases? Does matching the captured score prove your environment reproduced the same run?

Answer. The increment is 3,000 shots per basis, totaling 9,000 additional shots. Matching one rounded score proves neither identical counts nor identical implementation. Compare configuration, engine provenance, seeds, batches, and analysis. Even an exact local replay would concern this simulator execution, not a new physical device experiment.

Continue with noise and physical error budgets to understand which errors additional sampling can and cannot address.

Related reference readings