"""Experiment 3: Gate-level depolarising noise sweep."""
import time
from typing import Optional
import numpy as np
from numpy.random import default_rng
from experiments.harness.phi import make_random_parity
from experiments.harness.results import ExperimentResult
from experiments.harness.worker import TrialSpec, run_trials_parallel
[docs]
def run_gate_noise_experiment(
n_range: range = range(4, 7),
gate_noise_rates: Optional[list[float]] = None,
num_trials: int = 24,
epsilon: float = 0.3,
qfs_shots: int = 2000,
classical_samples_prover: int = 1000,
classical_samples_verifier: int = 3000,
base_seed: int = 42,
max_workers: int = 1,
shard_index: int | None = None,
num_shards: int | None = None,
) -> ExperimentResult:
r"""Gate-level noise sweep: verification under depolarising gate errors.
Unlike the label-flip noise experiment (Exp 2 / ``noise.py``), which
applies noise at the distribution level via
:math:`\varphi_{\mathrm{eff}}(x) = (1 - 2\eta)\,\varphi(x) + \eta`
(Definition 5(iii)), this experiment applies depolarising noise
channels directly to the quantum gates in the QFS circuit:
- 1-qubit depolarising error (rate *p*) on H and X gates
- 2-qubit depolarising error (rate *p*) on CX gates
Multi-controlled X gates (used in the oracle :math:`U_f`) decompose
into CX and single-qubit gates during transpilation, so gate noise
propagates through the full circuit.
This goes beyond the noise models analysed in Caro et al. (Lemmas
4--6, §4.2) and produces inherently empirical results with no
theoretical prediction to compare against.
The experiment uses ``qfs_mode="circuit"`` (required for gate-level
noise) and ``noise_rate=0`` (no label-flip noise), with
:math:`a^2 = b^2 = 1` (noiseless distribution class promise, since
the noise is purely at the gate level rather than the label level).
.. warning::
**Exploratory — the paper makes no predictions about gate
noise.** Caro et al. only analyse classical label-flip noise
on the data oracle (Definition 5(iii) / Definition 12). This
experiment is **NOT** a validation of any theorem.
Three substantive caveats from ``audit/gate_noise.md``:
1. The observed "threshold" at small :math:`n` is dominated by
**truth-table oracle synthesis cost** in
:func:`mos.MoSState._circuit_oracle_f`, which emits up to
:math:`2^n` multi-controlled-X gates per shot. Each MCX
decomposes into :math:`O(n)` CX gates after transpilation,
giving expected errors per shot :math:`\sim p \cdot n \cdot 2^n`
--- exponential in :math:`n`. The paper's Theorem 12 only
requires :math:`O(n \log(\dots))` *single-qubit* prover gates,
so the experiment is hitting a synthesis blow-up that does
not exist in the theory.
2. The high-:math:`p` end of the sweep (:math:`p \in \{0.2, 0.5\}`)
is unphysical: real superconducting/trapped-ion 2q gate
error rates are :math:`\sim 10^{-4}` to :math:`10^{-2}`.
At :math:`p = 0.5` the depolarising channel maps any state
to within :math:`1/4` of the maximally mixed state in one
application.
3. The noise model is attached only to ``h``, ``x``, ``cx``.
Other transpiled basis gates (``sx``, ``rz``, ``u3``,
``t``, ``tdg``) carry **no error**, so the effective
per-logical-op error is smaller than nominal :math:`p`.
The headline finding "the verifier fails sharply under gate
noise" is **NOT supported** without isolating circuit-
implementation cost from protocol robustness. See
``audit/FOLLOW_UPS.md`` for the proposed structured-oracle
fix that would actually probe the protocol.
Parameters
----------
n_range : range
Range of :math:`n` values to sweep.
gate_noise_rates : list[float] or None
Depolarising error rates *p* to sweep.
Default: ``[0.0, 0.001, 0.005, 0.01, 0.02, 0.05, 0.1]``.
num_trials : int
Trials per :math:`(n, p)` cell.
epsilon : float
Accuracy parameter :math:`\varepsilon`.
qfs_shots : int
QFS copy budget per trial.
classical_samples_prover : int
Classical samples for the prover.
classical_samples_verifier : int
Classical samples for the verifier.
base_seed : int
Base random seed.
max_workers : int
Number of parallel worker processes.
Returns
-------
ExperimentResult
"""
if gate_noise_rates is None:
gate_noise_rates = [0.0, 0.0001, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5]
print(
f"=== Gate Noise: n in {list(n_range)}, p in {gate_noise_rates}, "
f"{max_workers} workers ==="
)
rng = default_rng(base_seed)
specs: list[TrialSpec] = []
for n in n_range:
for p in gate_noise_rates:
for _ in range(num_trials):
seed = int(rng.integers(0, 2**31))
trial_rng = default_rng(seed)
phi, target_s = make_random_parity(n, trial_rng)
specs.append(
TrialSpec(
n=n,
phi=phi,
noise_rate=0.0,
target_s=target_s,
epsilon=epsilon,
delta=0.1,
theta=epsilon,
a_sq=1.0,
b_sq=1.0,
qfs_shots=qfs_shots,
classical_samples_prover=classical_samples_prover,
classical_samples_verifier=classical_samples_verifier,
seed=seed,
phi_description=f"gate_noise_p={p}_s={target_s}",
gate_noise_rate=p if p > 0 else None,
qfs_mode="circuit",
)
)
t0 = time.time()
trials = run_trials_parallel(
specs, max_workers=max_workers, label="gate_noise",
shard_index=shard_index, num_shards=num_shards,
)
wall = time.time() - t0
result = ExperimentResult(
experiment_name="gate_noise",
timestamp=time.strftime("%Y-%m-%dT%H:%M:%S"),
wall_clock_s=wall,
max_workers=max_workers,
trials=trials,
parameters={
"n_range": list(n_range),
"gate_noise_rates": gate_noise_rates,
"num_trials": num_trials,
"epsilon": epsilon,
},
)
# Print per-p summary
print(f"\n {'p':>7s} {'accept%':>8s} {'correct%':>9s}")
for p in gate_noise_rates:
et = [t for t in trials if f"p={p}" in t.phi_description]
ar = np.mean([t.accepted for t in et]) if et else 0.0
cr = np.mean([t.hypothesis_correct for t in et]) if et else 0.0
print(f" {p:7.4f} {ar:8.0%} {cr:9.0%}")
print(f" Wall-clock time: {wall:.1f}s")
return result