""" v2: hardware-non-ideality robustness study. Where exhaustive N/N verification ends, robustness begins. This quantifies how the analog optical core degrades under real hardware imperfections and shows that noise-aware training recovers most of the loss. A) yield curve -- process fidelity vs fabrication & thermal noise (Monte-Carlo) B) readout error -- detector saturation + ADC quantization distort intensities C) robustness -- a photonic classifier: naive-trained vs noise-aware-trained, evaluated on real (noisy) devices """ import math import torch import torch.nn as nn import torch.nn.functional as F from photonic.mzi import MZIMesh from photonic.nonideal import (NoiseModel, NoisyDevice, yield_curve, process_fidelity, calibrate) import statistics as st torch.manual_seed(0) print("=" * 64) print("v2 -- photonic hardware non-ideality & robustness") print("=" * 64) # ---------- A) fidelity yield curves ---------- mesh = MZIMesh(8, seed=1) print("A) process-fidelity yield curves (8-mode mesh, 200 devices each)\n") print(" thermal phase drift (rad):") nm = NoiseModel() for val, mean_f, yld in yield_curve(mesh, nm, [0.0, 0.01, 0.02, 0.05, 0.1], "phase_thermal_sigma"): print(f" sigma={val:4.2f} mean fidelity={mean_f:.4f} yield(F>0.99)={yld*100:5.1f}%") print(" fabrication: coupler split error (rad from 50:50):") nm = NoiseModel() for val, mean_f, yld in yield_curve(mesh, nm, [0.0, 0.01, 0.02, 0.05, 0.1], "split_sigma"): print(f" sigma={val:4.2f} mean fidelity={mean_f:.4f} yield(F>0.99)={yld*100:5.1f}%") # ---------- B) detector + ADC readout distortion ---------- print("\nB) readout distortion (detector saturation + 6-bit ADC):") dev = NoisyDevice(mesh, NoiseModel(detector_sat=1.5, adc_bits=6, adc_noise_sigma=0.01), torch.Generator().manual_seed(3)) x = torch.randn(8, dtype=torch.complex64) field = dev.forward(x, thermal=False) ideal_I = (field.abs() ** 2) meas_I = dev.readout(field) err = (meas_I - ideal_I).abs().mean().item() print(f" mean |measured - ideal| intensity = {err:.4f} " f"(nonlinearity + quantization -- not recoverable by more bits alone)") # ---------- C) noise-aware training recovers accuracy ---------- def make_moons(n=600, noise=0.15, seed=0): g = torch.Generator().manual_seed(seed) n2 = n // 2 t = torch.rand(n2, generator=g) * math.pi outer = torch.stack([torch.cos(t), torch.sin(t)], 1) inner = torch.stack([1 - torch.cos(t), 1 - torch.sin(t) - 0.5], 1) X = torch.cat([outer, inner], 0) + noise * torch.randn(n, 2, generator=g) y = torch.cat([torch.zeros(n2), torch.ones(n2)]).long() p = torch.randperm(n, generator=g) return X[p], y[p] class PhotonicClassifier(nn.Module): def __init__(self, modes=8): super().__init__() self.enc = nn.Linear(2, modes) self.mesh = MZIMesh(modes, seed=2) self.gain = nn.Parameter(torch.ones(modes)) self.bias = nn.Parameter(torch.zeros(modes)) self.head = nn.Linear(modes, 2) def forward(self, x, device=None): f = self.enc(x).to(torch.complex64) if device is None: f = self.mesh(f) I = f.abs() ** 2 else: f = device.forward(f) I = device.readout(f) h = F.relu(I * self.gain + self.bias) return self.head(h) def train(model, Xtr, ytr, steps=1500, noise=None): opt = torch.optim.Adam(model.parameters(), lr=0.02) for _ in range(steps): opt.zero_grad() dev = NoisyDevice(model.mesh, noise) if noise else None loss = F.cross_entropy(model(Xtr, dev), ytr) loss.backward() opt.step() return model @torch.no_grad() def eval_noisy(model, Xte, yte, noise, devices=30): accs = [] for _ in range(devices): dev = NoisyDevice(model.mesh, noise) accs.append((model(Xte, dev).argmax(1) == yte).float().mean().item()) return sum(accs) / len(accs) Xtr, ytr = make_moons(600, seed=1) Xte, yte = make_moons(400, seed=2) # training-time noise is analog only (differentiable: no hard ADC quantization) train_noise = NoiseModel(split_sigma=0.03, phase_static_sigma=0.05, phase_thermal_sigma=0.05, detector_resp_sigma=0.03, detector_sat=2.0) # deployment noise: the full pipeline incl. ADC quantization + read noise deploy_noise = NoiseModel(split_sigma=0.03, phase_static_sigma=0.05, phase_thermal_sigma=0.05, detector_resp_sigma=0.03, detector_sat=2.0, adc_bits=6, adc_noise_sigma=0.01) print("\nC) two-moons photonic classifier -- the ideal-HW illusion vs real hardware:") naive = train(PhotonicClassifier(), Xtr, ytr) # ideal training aware = train(PhotonicClassifier(), Xtr, ytr, noise=train_noise) # noise-aware with torch.no_grad(): acc_ideal_naive = (naive(Xte).argmax(1) == yte).float().mean().item() acc_noisy_naive = eval_noisy(naive, Xte, yte, deploy_noise) acc_noisy_aware = eval_noisy(aware, Xte, yte, deploy_noise) # calibrated to real device print(f" naive-trained: {acc_ideal_naive*100:5.1f}% on IDEALIZED HW " f"-> {acc_noisy_naive*100:5.1f}% on REAL (noisy) HW") print(f" noise-aware-trained: {acc_noisy_aware*100:5.1f}% on REAL (noisy) HW " f"(trained *for* the real device)") print(f" -> a '{acc_ideal_naive*100:.0f}%' that assumes perfect hardware is worth " f"{acc_noisy_naive*100:.0f}% in practice;") print(f" modeling the non-idealities and training through them recovers it to " f"{acc_noisy_aware*100:.0f}%.") print("\nD) per-device calibration -- fix the DETERMINISTIC errors:") U0 = mesh.unitary().detach() fab = NoiseModel(split_sigma=0.05, phase_static_sigma=0.08) # static fab, no drift rng = torch.Generator().manual_seed(7) before, after, cals = [], [], [] for _ in range(5): d = NoisyDevice(mesh, fab, rng) before.append(process_fidelity(U0, d.unitary(thermal=False).detach())) ctrl, fid = calibrate(d, U0, steps=1000) after.append(fid); cals.append((d, ctrl)) print(f" fabrication (coupler split 0.05, static phase 0.08), 5 devices:") print(f" mean fidelity BEFORE calibration = {st.mean(before):.4f}") print(f" mean fidelity AFTER calibration = {st.mean(after):.4f} (static errors removed)") # add thermal drift on the calibrated devices -> irreducible floor for d, _ in cals: d.noise.phase_thermal_sigma = 0.05 res = [st.mean([process_fidelity(U0, d.unitary(thermal=True, control=c).detach()) for _ in range(20)]) for d, c in cals] print(f" calibrated + thermal drift (0.05) = {st.mean(res):.4f} " f"(random drift remains -- the honest floor)") print("=" * 64) print("Exact where it can be proven (digital); error-bounded where it can't (analog).") print("Deterministic hardware error is calibratable; random drift/noise is the floor.")