File size: 5,200 Bytes
d2486ac | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | """
Zero-Noise Extrapolation (ZNE) sweep for the dashboard -- Phase 3 of the
dashboard refactor.
Wires dense_evolution.mitigation.zero_noise_extrapolation (added earlier
in this same development effort) into the dashboard, which until now had
no ZNE/predictive-healing feature at all despite the core package having
one. Reuses zero_noise_extrapolation exactly as-is -- no reimplementation
of the Richardson/healing math here, only the plumbing to run a circuit
at several noise scales and feed the results into it.
"""
import numpy as np
import dense_evolution as de
from .simulation_runner import run_simulation
def run_mitigation_sweep(source_mode, circuit_name, qasm_text, noise_model,
base_noise_p, shots, seed, use_float32=True, engine='dense',
noise_factors=(1.0, 2.0, 3.0),
healing_enabled=False, target_sigma_ideal=10.0) -> dict:
"""Runs the same circuit at several noise scales (base_noise_p * factor
for each factor in noise_factors) and extrapolates to zero noise via
dense_evolution.mitigation.zero_noise_extrapolation.
Raises ValueError if noise_model=='ideal' (there is no noise to
extrapolate away) or if healing_enabled with a noise_factors length
other than 3 -- the only point count zero_noise_extrapolation's
healing-adapted path supports; this is a friendlier dashboard-level
guard around that library-level NotImplementedError, not a new rule.
sigma_at_base_noise, when healing_enabled, is the shot-noise binomial
sigma already used and displayed elsewhere in the dashboard
(build_panel_overview's NISQ Shot Histogram: sigma = sqrt(shots *
p_max * (1 - p_max)), computed here at the base (1x) noise scale) --
a pragmatic proxy for the "coherence signal" the healing math expects,
not a first-principles derivation (there is no VQE/QM-MM telemetry in
a bare circuit run to derive one from more rigorously).
Returns a dict: noise_factors, prob_per_scale, fidelity_per_scale,
prob_zne, fidelity_zne, prob_ideal, healing_enabled, delta_preemp
(None when healing_enabled is False), n_qubits.
"""
if noise_model == 'ideal':
raise ValueError(
"Zero-Noise Extrapolation richiede un modello di rumore attivo -- "
"non ha senso estrapolare rumore da un circuito 'ideal'."
)
noise_factors = tuple(float(f) for f in noise_factors)
if healing_enabled and len(noise_factors) != 3:
raise ValueError(
f"L'healing predittivo richiede esattamente 3 fattori di rumore, "
f"non {len(noise_factors)} -- dense_evolution.mitigation."
f"zero_noise_extrapolation supporta l'healing-adapted path solo "
f"a 3 punti."
)
prob_per_scale = []
fidelity_per_scale = []
prob_ideal = None
n_qubits = None
sigma_at_base = None
for i, factor in enumerate(noise_factors):
res = run_simulation(
source_mode, circuit_name, qasm_text, noise_model,
base_noise_p * factor, shots, seed, use_float32=use_float32, engine=engine,
)
prob_per_scale.append(res['prob'])
fidelity_per_scale.append(res['fidelity'])
if i == 0:
prob_ideal = res['prob_ideal']
n_qubits = res['n_qubits']
if healing_enabled:
idx_max = int(np.argmax(res['prob']))
p_max = float(res['prob'][idx_max])
sigma_at_base = float(np.sqrt(shots * p_max * (1.0 - p_max)))
delta_preemp = None
if healing_enabled:
from dense_evolution.healing import calculate_delta_preemp
delta_preemp = float(calculate_delta_preemp(sigma_at_base, target_sigma_ideal))
prob_zne_raw = de.zero_noise_extrapolation(
prob_per_scale, noise_factors,
sigma_at_base_noise=sigma_at_base if healing_enabled else None,
target_sigma_ideal=target_sigma_ideal,
)
# Post-processing on the dashboard side only -- zero_noise_extrapolation
# itself stays a general-purpose numeric primitive that doesn't assume
# its input is a probability distribution. Richardson extrapolation of
# a probability vector is not guaranteed to stay non-negative or
# normalized -- that's a real property of the technique (it's fitting
# a polynomial through noisy points and evaluating it outside their
# range), not a bug to hide.
prob_zne = np.clip(np.asarray(prob_zne_raw), 0.0, None)
prob_sum = prob_zne.sum()
if prob_sum > 1e-12:
prob_zne = prob_zne / prob_sum
fidelity_zne = float(de.zero_noise_extrapolation(
fidelity_per_scale, noise_factors,
sigma_at_base_noise=sigma_at_base if healing_enabled else None,
target_sigma_ideal=target_sigma_ideal,
))
return {
'noise_factors': noise_factors,
'prob_per_scale': prob_per_scale,
'fidelity_per_scale': fidelity_per_scale,
'prob_zne': prob_zne,
'fidelity_zne': fidelity_zne,
'prob_ideal': prob_ideal,
'healing_enabled': healing_enabled,
'delta_preemp': delta_preemp,
'n_qubits': n_qubits,
}
|