Contact: www.anulum.li | protoscience@anulum.li
The hardware package provides the full stack from circuit compilation
to QPU execution, noise modelling, classical reference computation, and
multi-backend support. 17 modules (April 2026: added qubit_mapper.py
for DynQ topology-agnostic placement) covering IBM superconducting,
trapped ion, PennyLane, Cirq, GPU acceleration, and circuit cutting.
Hardware evidence status:
| Device | Family | Campaign | Highlight |
|---|---|---|---|
ibm_fez |
Heron r2, 156 q | Legacy March 2026 baseline artifacts | Artifact-backed Bell/QKD/VQE/ZNE/UPDE observations; quote only rows named in the hardware ledger. |
ibm_kingston |
Heron r2, 156 q | April 2026 Phase 1, 342 circuits | Promoted raw-count DLA parity dataset: peak scripts/run_dla_parity_suite.py. |
V2, frontier, queued-job, placeholder, and aggregate-only IBM outputs are not promoted unless the hardware ledger names raw counts, private retrieval map, analysis code, and review status.
Experiment Definition (experiments.py)
│
├── HardwareRunner (runner.py)
│ ├── connect() → IBM Runtime / AerSimulator
│ ├── transpile() → native gate set
│ ├── run_circuit() → JobResult
│ └── run_with_zne() → ZNE-mitigated result
│
├── Noise Model (noise_model.py)
│ └── heron_r2_noise_model() → NoiseModel (thermal + depolarizing)
│
├── Classical Reference (classical.py)
│ ├── classical_kuramoto_reference() → Euler integration
│ ├── classical_exact_diag() → full eigendecomposition
│ ├── classical_exact_evolution() → matrix expm
│ └── classical_brute_mpc() → brute-force MPC
│
├── Multi-Backend
│ ├── PennyLane (pennylane_adapter.py)
│ ├── Cirq (cirq_adapter.py)
│ ├── Trapped Ion (trapped_ion.py)
│ ├── GPU (gpu_accel.py, jax_accel.py)
│ └── Plugin Registry (plugin_registry.py)
│
└── Circuit Tools
├── Circuit Cutting (circuit_cutting.py, cutting_runner.py)
├── QASM Export (qasm_export.py)
├── Circuit Export (circuit_export.py)
└── QCVV (qcvv.py)
- Account: https://quantum.cloud.ibm.com
- Credentials (use
ibm_cloudchannel, NOT deprecatedibm_quantum):export IBM_QUANTUM_TOKEN="your-token-here" export IBM_QUANTUM_CRN="your-crn-instance-id"
- Install IBM runtime:
pip install -e ".[ibm]"
The primary execution interface. Handles authentication, backend selection, transpilation, job submission, and result collection.
from scpn_quantum_control.hardware import HardwareRunner
# Real hardware
runner = HardwareRunner(use_simulator=False)
runner.connect() # Authenticates with IBM_QUANTUM_TOKEN env var
# Local simulator (default)
runner = HardwareRunner(use_simulator=True, results_dir="results/")
runner.connect()When use_simulator=True, uses AerSimulator with the Heron r2 noise
model for realistic local testing without QPU budget consumption.
transpiled = runner.transpile(circuit, optimization_level=3)Uses Qiskit's preset pass manager with Heron r2 target. Optimization level 3 performs heavy gate cancellation and routing.
result = runner.run_circuit(
circuit,
experiment_name="kuramoto_4osc",
shots=10000,
)
# result: JobResult with counts, wall_time_s, metadataresult = runner.run_with_zne(
circuit,
experiment_name="kuramoto_zne",
noise_scales=[1, 3, 5],
shots=10000,
)Internally calls gate_fold_circuit from the mitigation package.
| Field | Type | Description |
|---|---|---|
job_id |
str | IBM job ID or "simulator" |
backend_name |
str | Backend identifier |
experiment_name |
str | User-specified experiment name |
counts |
dict or None | Measurement counts |
expectation_values |
ndarray or None | Computed expectations |
metadata |
dict | Arbitrary metadata |
wall_time_s |
float | Total execution time |
timestamp |
str | ISO timestamp |
Results are serialised to JSON via to_dict() and saved to results_dir.
IBM Heron r2 calibration (ibm_fez, February 2026 median):
| Parameter | Value | Description |
|---|---|---|
| T1 | 300 us | Longitudinal relaxation |
| T2 | 200 us | Transverse relaxation |
| CZ error | 0.5% | Two-qubit gate error rate |
| Readout error | 0.2% | Measurement error rate |
| Single-gate time | 0.06 us | SX/X/RZ duration |
| Two-gate time | 0.66 us | CZ/ECR duration |
Constructs a Qiskit-Aer NoiseModel:
- Single-qubit gates: thermal relaxation only
- Two-qubit gates (ECR/CZ): thermal relaxation + depolarizing
- Readout: symmetric bit-flip error
Qiskit-Aer is imported lazily when the noise-model function is called.
Importing scpn_quantum_control.hardware does not require a working
local Aer installation unless a simulator noise model is actually built.
from scpn_quantum_control.hardware import heron_r2_noise_model
model = heron_r2_noise_model()
# Use with AerSimulator:
from qiskit_aer import AerSimulator
backend = AerSimulator(noise_model=model)Exact classical computations for hardware experiment comparison. Every quantum result should be compared against these references.
Euler integration of the classical Kuramoto model:
d(theta_i)/dt = omega_i + sum_j K[i,j] * sin(theta_j - theta_i)
Returns {times, theta, R} — phase trajectories and order parameter.
Rust acceleration: scpn_quantum_engine.kuramoto_euler() at 33x
speedup for n >= 8.
Full eigendecomposition of the XY Hamiltonian. Returns eigenvalues, eigenvectors, ground state, and ground energy.
For n <= 14: direct dense diagonalisation via numpy.linalg.eigh.
For n > 14: sparse ARPACK via scipy.sparse.linalg.eigsh.
Matrix exponential evolution: psi(t+dt) = exp(-iHdt) psi(t).
Returns time series of R(t) and energy E(t) for direct comparison with Trotter evolution on quantum hardware.
Brute-force model predictive control: enumerate all 2^horizon action sequences and select the one maximising R(t_final).
Rust acceleration: scpn_quantum_engine.brute_mpc() with rayon
parallel enumeration at 5-50x speedup.
PennyLaneRunner exposes the same Kuramoto-XY Hamiltonian through any
PennyLane-compatible device. run_trotter() evaluates the energy after
Trotterized time evolution and reconstructs the Kuramoto order parameter
from local transverse expectations:
theta_i = atan2(<Y_i>, <X_i>)
R = |mean_i exp(i theta_i)|
run_vqe() uses the optimized hardware-efficient ansatz for both the
energy objective and the post-optimization observable pass. The returned
order_parameter is therefore measured from the final ansatz via the
same X/Y phase reconstruction; it is not a sentinel, simulator-only
statevector value, or unmeasured placeholder.
Pre-defined experiment configurations for systematic QPU characterisation.
Registry of all 19 experiment functions:
| Experiment | Qubits | Description |
|---|---|---|
kuramoto_4osc |
4 | Basic Trotter evolution, R(t) |
kuramoto_4osc_trotter2 |
4 | Suzuki-Trotter 2nd order |
kuramoto_4osc_zne |
4 | ZNE-mitigated Kuramoto |
kuramoto_8osc |
8 | 8-qubit Kuramoto dynamics |
kuramoto_8osc_zne |
8 | ZNE-mitigated 8-qubit |
vqe_4q |
4 | VQE ground state search |
vqe_8q |
8 | VQE with physics-informed ansatz |
vqe_8q_hardware |
8 | VQE targeting real QPU |
vqe_landscape |
4 | Energy landscape scan |
qaoa_mpc_4 |
4 | QAOA-based MPC |
upde_16_snapshot |
16 | Full 16-qubit UPDE state snapshot |
upde_16_dd |
16 | UPDE with dynamical decoupling |
noise_baseline |
4 | Noise characterisation baseline |
ansatz_comparison_hw |
4 | Compare ansatz architectures |
sync_threshold |
4 | Synchronisation threshold detection |
decoherence_scaling |
4 | Depth vs fidelity scaling |
zne_higher_order |
4 | Higher-order ZNE extrapolation |
bell_test_4q |
4 | CHSH Bell test on hardware |
correlator_4q |
4 | XY correlator measurement |
Each experiment function returns a dict with circuit, shots,
n_qubits, and experiment-specific metadata.
Free tier: 10 minutes/month on ibm_fez (Heron r2, 156 qubits).
| Experiment | Circuits | Shots | QPU Seconds |
|---|---|---|---|
| kuramoto_4osc (1 step) | 3 | 10k | ~15 |
| vqe_4q (100 COBYLA iter) | ~100 | 10k | ~15 |
| qaoa_mpc_4 (p=1) | ~30 | 10k | ~100 |
| upde_16 snapshot | 3 | 20k | ~60 |
from scpn_quantum_control.hardware.pennylane_adapter import PennyLaneRunner
runner = PennyLaneRunner(K, omega, device="default.qubit")
result = runner.run_trotter(t=0.5, reps=2)
# result: PennyLaneResult(energy, order_parameter, n_qubits, device_name, statevector)VQE via PennyLane optimisers:
result = runner.run_vqe(ansatz_depth=1, maxiter=5, seed=42)Requires: pip install pennylane
from scpn_quantum_control.hardware.cirq_adapter import CirqRunner
runner = CirqRunner(K, omega)
result = runner.run_trotter(t=0.5, reps=2)Enables targeting Google Sycamore/Weber QPUs via Cirq.
Requires: pip install cirq-core
from scpn_quantum_control.hardware import transpile_for_trapped_ion, trapped_ion_noise_model
ion_circuit = transpile_for_trapped_ion(circuit, allow_proxy_basis=True)
model = trapped_ion_noise_model()Representative target: all-to-all QCCD-style trapped-ion devices. The helper emits a CX-basis proxy for MS/RXX-style entangling operations, records that proxy in circuit metadata, and is not a vendor-native IonQ or Quantinuum compiler path.
cuQuantum integration for large-scale statevector simulation. Falls back to CPU when CUDA is not available.
JAX-based compilation for VQE parameter optimisation. Enables automatic differentiation of quantum circuits.
Dynamic backend registration. Third-party backends register via:
from scpn_quantum_control.hardware.plugin_registry import register_backend
register_backend("my_backend", MyBackendClass)Decomposes large circuits (> available qubits) into subcircuits connected by classical communication. Enables running n-qubit circuits on n/2-qubit hardware with polynomial overhead.
from scpn_quantum_control.hardware.circuit_cutting import partition_circuit
from scpn_quantum_control.hardware.cutting_runner import CuttingRunner
subcircuits = partition_circuit(circuit, max_partition_size=8)
runner = CuttingRunner(backend)
result = runner.run_partitioned(subcircuits)Export circuits to OpenQASM 2.0/3.0 for platform-independent storage and submission to third-party systems.
Export circuits to JSON, LaTeX (Qiskit drawer), and SVG formats for documentation and publication.
Quantum Characterisation, Verification, and Validation protocols. Randomised benchmarking and gate set tomography for hardware qualification.
| Depth Range | Expected Error | Recommendation |
|---|---|---|
| < 50 | < 5% | Publishable as-is |
| 50-150 | 5-15% | Publishable with error bars |
| 150-250 | 15-25% | Apply ZNE mitigation |
| 250-400 | 25-40% | Qualitative trends only |
| > 400 | > 40% | Do not trust individual values |
| Gate | Description | Duration |
|---|---|---|
| CZ | Two-qubit entangling (native) | 0.66 us |
| RZ(theta) | Z rotation (virtual) | 0 us |
| SX | sqrt(X) | 0.06 us |
| X | Pauli-X | 0.06 us |
| ID | Identity (delay) | 0.06 us |
Transpilation from Qiskit standard gates increases depth. Typical expansion: 1 CNOT → 2 SX + 1 CZ + RZ gates.
The classical.py module transparently uses Rust via scpn_quantum_engine
when available:
| Python Function | Rust Function | Speedup | Method |
|---|---|---|---|
classical_kuramoto_reference |
kuramoto_euler, kuramoto_trajectory |
33x | rayon parallel Euler steps |
_expectation_pauli |
expectation_pauli_fast |
3-10x | Bitwise Pauli ops |
classical_brute_mpc |
brute_mpc |
5-50x | rayon parallel 2^horizon enumeration |
_state_order_param |
state_order_param_sparse |
2-5x | SIMD-friendly inner loop |
_order_parameter (Floquet) |
all_xy_expectations |
5-20x | Batch bitwise, single FFI call |
All Rust functions accept split real/imaginary arrays (no complex64 across FFI). Python fallback always available when the Rust crate is not installed.
Order parameter R from qubit expectation values:
R = (1/N) × |sum_i (<X_i> + i×<Y_i>)|
where <X_i> = 2×P(|0>)_x - 1 from X-basis measurement. Requires 3
measurement bases (X, Y, Z) for full reconstruction.
Compare hw_R against exact_R (from classical_kuramoto_reference or
classical_exact_evolution) to quantify hardware error.
72 tests across 8 test files:
test_runner.py— HardwareRunner lifecycle, simulator mode, job serialisationtest_noise_model.py— NoiseModel construction, error rates, parameter overridestest_classical.py— Kuramoto reference, exact diag, evolution paritytest_experiments.py— All 19 experiment definitions, circuit validitytest_pennylane_adapter.py— PennyLane Trotter, VQE, device selectiontest_cirq_adapter.py— Cirq Trotter, simulator paritytest_circuit_cutting.py— Partitioning, recombination, overhead boundstest_qcvv.py— RB, gate set tomography, fidelity extraction
scpn_quantum_control.hardware.qubit_mapper (added April 2026) implements
the DynQ method (Liu et al., arXiv:2601.19635) for selecting an
execution region on a heavy-hex device based on the live calibration
data. The QPU is modelled as a graph weighted by inverse two-qubit gate
errors, and Louvain community detection partitions it into high-fidelity
sub-regions; the sub-region with the best composite quality score is
chosen for the circuit. Quality scoring is Rust-accelerated.
See dynq_qubit_mapping.md for the full
theory, the dynq_initial_layout() API, and a Qiskit transpiler
integration recipe.
scpn_quantum_control.mitigation.symmetry_decay (added April 2026)
provides physics-informed zero-noise extrapolation specifically for the
XY Hamiltonian, using its conserved
See symmetry_decay_guess.md for the full
theory, API, and a worked example calibrating
The Phase 1 campaign is recorded in
.coordination/IBM_CAMPAIGN_STATE.md and IBM_EXECUTION_LOG.md, and
the analysis is reproduced by scripts/analyse_phase1_dla_parity.py.
The four sub-phases progressively increased the per-point repetition
count from 2 to 21 to drive the per-depth uncertainty below the 5 %
asymmetry signal:
| Sub-phase | Circuits | Wall time | Reps per (depth, sector) point |
|---|---|---|---|
| Pipe cleaner | 2 | ~0.1 s | sanity check |
| Phase 1 (A/B/C) | 42 | 44.1 s | 2 |
| Phase 1.5 (D/E) | 72 | 56.7 s | +4 → 6 |
| Phase 2 exhaust (F/G/H/I) | 138 | 97.5 s | +6 → 12 |
| Phase 2.5 final burn (J) | 90 | 65.1 s | +9 → 21 (at the 5 strongest depths) |
| Total |
342 | ~264 s wall | up to 21 |
Headline result:
Measured on ML350 Gen8 (128 GB RAM, Xeon E5-2620v2):
| Operation | System | Wall Time |
|---|---|---|
HardwareRunner.connect (simulator) |
— | 50 ms |
runner.transpile (opt level 3) |
4 qubits | 120 ms |
runner.run_circuit (simulator) |
4 qubits, 10k shots | 800 ms |
classical_kuramoto_reference (Rust) |
8 oscillators | 0.3 ms |
classical_kuramoto_reference (Python) |
8 oscillators | 12 ms |
classical_exact_diag |
8 qubits | 15 ms |
classical_exact_evolution |
8 qubits | 120 ms |
heron_r2_noise_model |
— | 5 ms |
PennyLaneRunner.run_trotter |
3 qubits | 50 ms |
partition_circuit |
16 → 2×8 qubits | 25 ms |
dynq_initial_layout (156-qubit graph, 5-qubit circuit) |
— | < 100 ms |
learn_symmetry_decay (5 noise scales, Rust) |
— | < 1 µs |
guess_extrapolate_batch (1,000 observables, Rust) |
— | < 50 µs |
hypergeometric_envelope (10,000 points, Rust) |
— | 2.6 ms |
ici_three_level_evolution (2,000 points, Rust) |
— | 0.04 ms |