| """ |
| Does splitting a FIXED total measurement budget across more noise-scale |
| points actually help, or does the earlier "more points helps" finding |
| only look good because it secretly spent more measurements (more K per |
| point) than the 3-point baseline? |
| |
| This is the fair comparison: same total number of Monte Carlo trajectories |
| across all configurations, just split differently -- 3 points x K=400 |
| (1200 total) vs. 5 points x K=240 (1200 total) vs. 7 points x K=171 |
| (~1200 total). Answers "what did we actually win, all else equal." |
| |
| python experiments/matrix_healing_fixed_budget.py |
| """ |
| import os |
| import sys |
|
|
| import numpy as np |
| import jax.numpy as jnp |
|
|
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
|
|
| import dense_evolution as de |
| from dense_evolution.registry import NoiseModel |
| from dense_evolution.mitigation import ( |
| uhlmann_fidelity, richardson_extrapolate, project_to_physical, zne_density_matrix, |
| ) |
|
|
| N_QUBITS = 4 |
| BASE_P = 0.05 |
| SEEDS = (0, 1, 2, 3, 4) |
| NOISE_MODELS = ("depolarizing", "bitflip", "phaseflip", "amplitude_damping", "combined") |
|
|
| CONFIGS = { |
| "3pt K=400 (baseline, classic ZNE)": ((1.0, 2.0, 3.0), 400, "richardson"), |
| "5pt K=240 (deg2 lstsq)": ((1.0, 2.0, 3.0, 4.0, 5.0), 240, "lstsq2"), |
| "7pt K=171 (deg2 lstsq)": ((1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0), 171, "lstsq2"), |
| } |
|
|
|
|
| def ghz_sv(n): |
| sim = de.DenseSVSimulator(n) |
| ops = [("h", 0)] + [("cx", 0, i) for i in range(1, n)] |
| sim.run_circuit(ops) |
| return np.asarray(sim.get_statevector()) |
|
|
|
|
| def noisy_rho(ideal_sv, model, p, k, rng): |
| dim = len(ideal_sv) |
| rho = np.zeros((dim, dim), dtype=np.complex128) |
| for _ in range(k): |
| sv = NoiseModel.apply_to_sv(ideal_sv.copy(), N_QUBITS, model, p, rng=rng) |
| rho += np.outer(sv, sv.conj()) |
| rho /= k |
| return jnp.asarray(rho, dtype=jnp.complex128) |
|
|
|
|
| def run_one(ideal_sv, rho_ideal, model, scales, k, method, seed): |
| rng = np.random.default_rng(seed) |
| rho_at_scales = jnp.stack([noisy_rho(ideal_sv, model, BASE_P * s, k, rng) for s in scales]) |
| raw = uhlmann_fidelity(rho_at_scales[0], rho_ideal) |
| if method == "richardson": |
| corrected_rho = project_to_physical(richardson_extrapolate(rho_at_scales, scales)) |
| else: |
| corrected_rho = zne_density_matrix(rho_at_scales, scales, degree=2) |
| corrected = uhlmann_fidelity(corrected_rho, rho_ideal) |
| return raw, corrected |
|
|
|
|
| def main(): |
| ideal_sv = ghz_sv(N_QUBITS) |
| rho_ideal = jnp.asarray(np.outer(ideal_sv, ideal_sv.conj()), dtype=jnp.complex128) |
|
|
| print(f"n_qubits={N_QUBITS}, base_p={BASE_P}, ~1200 total trajectories per run regardless of config\n") |
| summary = {} |
| for label, (scales, k, method) in CONFIGS.items(): |
| actual_budget = k * len(scales) |
| all_deltas = [] |
| for model in NOISE_MODELS: |
| deltas = [] |
| for seed in SEEDS: |
| raw, corrected = run_one(ideal_sv, rho_ideal, model, scales, k, method, seed) |
| deltas.append(corrected - raw) |
| all_deltas.extend(deltas) |
| summary[label] = (np.array(all_deltas), actual_budget) |
|
|
| print(f"{'config':>36} {'actual_budget':>14} {'mean_delta':>12} {'std_delta':>10} {'wins/total':>12}") |
| print("-" * 90) |
| for label, (arr, budget) in summary.items(): |
| print(f"{label:>36} {budget:>14} {arr.mean():>+12.4f} {arr.std():>10.4f} " |
| f"{int(np.sum(arr>0))}/{len(arr):>10}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|