use crate::quantum_cal::{exp_neg_fx, seal_artifacts, CalArtifacts, CAL_FRAC, CAL_ONE};
use crate::quantum_ops::{content_hash, CalibrationReceipt, GrantRef, MitigationReceipt};
use ed25519_dalek::SigningKey;
const ONE: i64 = CAL_ONE;
const FRAC: u32 = CAL_FRAC;
#[inline]
fn fmul(a: i64, b: i64) -> i64 {
((a as i128 * b as i128) >> FRAC) as i64
}
#[inline]
fn fdiv(a: i64, b: i64) -> i64 {
if b == 0 {
return 0;
}
(((a as i128) << FRAC) / b as i128) as i64
}
fn ln_fx(x: i64) -> i64 {
if x <= 0 {
return -20 * ONE;
}
let z = fdiv(x - ONE, x + ONE);
let z2 = fmul(z, z);
let mut term = z;
let mut acc = 0i64;
let mut k = 1i64;
for _ in 0..12 {
acc += term / k;
term = fmul(term, z2);
k += 2;
}
2 * acc
}
fn splitmix64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Pauli {
pub x: u32,
pub z: u32,
}
impl Pauli {
pub const fn new(x: u32, z: u32) -> Pauli {
Pauli { x, z }
}
pub fn anticommutes(&self, other: &Pauli) -> bool {
((self.x & other.z).count_ones() + (self.z & other.x).count_ones()) & 1 == 1
}
pub fn weight(&self) -> u32 {
(self.x | self.z).count_ones()
}
fn code(&self) -> u64 {
((self.x as u64) << 32) | self.z as u64
}
}
pub fn all_paulis(n: u32) -> Vec<Pauli> {
let mut v = Vec::new();
let dim = 1u32 << n;
for x in 0..dim {
for z in 0..dim {
if x != 0 || z != 0 {
v.push(Pauli::new(x, z));
}
}
}
v
}
pub fn sparse_generators(n: u32, edges: &[(u32, u32)]) -> Vec<Pauli> {
let single = [(1u32, 0u32), (0, 1), (1, 1)]; let mut g = Vec::new();
for q in 0..n {
for &(px, pz) in &single {
g.push(Pauli::new(px << q, pz << q));
}
}
for &(i, j) in edges {
for &(ax, az) in &single {
for &(bx, bz) in &single {
g.push(Pauli::new((ax << i) | (bx << j), (az << i) | (bz << j)));
}
}
}
g
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NoiseModel {
pub generators: Vec<Pauli>,
pub lambdas: Vec<i64>,
}
impl NoiseModel {
pub fn fidelity(&self, a: &Pauli) -> i64 {
let mut s = 0i64;
for (k, g) in self.generators.iter().enumerate() {
if a.anticommutes(g) {
s += self.lambdas[k];
}
}
exp_neg_fx(2 * s)
}
pub fn bytes(&self) -> Vec<u8> {
let mut b = Vec::with_capacity(self.generators.len() * 16 + 8);
b.extend_from_slice(b"wai:qc-noise-model\x01");
let mut idx: Vec<usize> = (0..self.generators.len()).collect();
idx.sort_by_key(|&i| self.generators[i].code());
for &i in &idx {
b.extend_from_slice(&self.generators[i].code().to_le_bytes());
b.extend_from_slice(&self.lambdas[i].to_le_bytes());
}
b
}
pub fn model_hash(&self) -> [u8; 32] {
content_hash(&self.bytes())
}
pub fn total_rate(&self) -> i64 {
self.lambdas.iter().sum()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LearnResult {
pub learned: NoiseModel,
pub truth: NoiseModel,
pub probes: Vec<Pauli>,
pub fidelity_meas: Vec<i64>,
pub rate_error_fx: i64,
pub depths: Vec<u32>,
}
fn measure_fidelity(f_true: i64, depths: &[u32], shots: u32, seed: u64) -> i64 {
let mut xs = Vec::new();
let mut ys = Vec::new();
for (di, &m) in depths.iter().enumerate() {
let mut e = ONE;
for _ in 0..m {
e = fmul(e, f_true);
}
let mut st = seed.wrapping_mul(0x100_0001).wrapping_add(di as u64 + 1);
let p_plus = (ONE + e) / 2;
let mut hits = 0u64;
for _ in 0..shots {
let r = (splitmix64(&mut st) >> (64 - FRAC)) as i64;
if r < p_plus {
hits += 1;
}
}
let e_hat = (2 * (hits as i128 * ONE as i128) / shots.max(1) as i128 - ONE as i128) as i64;
let e_hat = e_hat.clamp(ONE / 1000, ONE);
xs.push(m as i64 * ONE);
ys.push(ln_fx(e_hat)); }
let n = xs.len() as i128;
let sx: i128 = xs.iter().map(|&v| v as i128).sum();
let sy: i128 = ys.iter().map(|&v| v as i128).sum();
let mut sxx = 0i128;
let mut sxy = 0i128;
for i in 0..xs.len() {
sxx += xs[i] as i128 * xs[i] as i128;
sxy += xs[i] as i128 * ys[i] as i128;
}
let denom = n * sxx - sx * sx;
if denom == 0 {
return ONE;
}
let slope = (((n * sxy - sx * sy) << FRAC) / denom) as i64; exp_neg_fx(-slope).clamp(0, ONE)
}
pub fn learn_noise_model(
truth: &NoiseModel,
generators: &[Pauli],
probes: &[Pauli],
depths: &[u32],
shots: u32,
iters: u32,
seed: u64,
) -> LearnResult {
let ng = generators.len();
let np = probes.len();
let mut m = vec![false; np * ng];
for (ai, a) in probes.iter().enumerate() {
for (k, g) in generators.iter().enumerate() {
m[ai * ng + k] = a.anticommutes(g);
}
}
let mut y = Vec::with_capacity(np);
let mut fmeas = Vec::with_capacity(np);
for (ai, a) in probes.iter().enumerate() {
let f_true = truth.fidelity(a);
let f_hat = measure_fidelity(f_true, depths, shots, seed.wrapping_add(ai as u64 * 0x9E37));
fmeas.push(f_hat);
y.push((-ln_fx(f_hat)) / 2); }
let mut mty = vec![0i64; ng];
for ai in 0..np {
for k in 0..ng {
if m[ai * ng + k] {
mty[k] += y[ai];
}
}
}
let mut lam = vec![ONE / 100; ng]; for _ in 0..iters {
let mut ml = vec![0i64; np];
for ai in 0..np {
let mut s = 0i64;
for k in 0..ng {
if m[ai * ng + k] {
s += lam[k];
}
}
ml[ai] = s;
}
for k in 0..ng {
let mut denom = 0i64;
for ai in 0..np {
if m[ai * ng + k] {
denom += ml[ai];
}
}
if denom > 0 {
lam[k] = fmul(lam[k], fdiv(mty[k], denom));
}
}
}
let learned = NoiseModel { generators: generators.to_vec(), lambdas: lam };
let rate_error_fx = {
let s: i64 = truth
.lambdas
.iter()
.zip(&learned.lambdas)
.map(|(&a, &b)| (a - b).abs())
.sum();
s / ng.max(1) as i64
};
LearnResult {
learned,
truth: truth.clone(),
probes: probes.to_vec(),
fidelity_meas: fmeas,
rate_error_fx,
depths: depths.to_vec(),
}
}
impl LearnResult {
pub fn hash(&self) -> [u8; 32] {
self.learned.model_hash()
}
pub fn artifacts(&self) -> CalArtifacts {
CalArtifacts {
target: "noise-model:cycle".into(),
config_bytes: self.learned.bytes(),
evidence_kind: "cycle_error_reconstruction".into(),
evidence_bytes: {
let mut ev = Vec::new();
for &f in &self.fidelity_meas {
ev.extend_from_slice(&f.to_le_bytes());
}
ev
},
summary: format!(
"sparse Pauli–Lindblad model, {} generators, total rate {}",
self.learned.generators.len(),
self.learned.total_rate()
),
}
}
pub fn seal(&self, signer: &SigningKey, signer_id: &str, joules_micro: u64, grant: GrantRef) -> CalibrationReceipt {
seal_artifacts(signer, signer_id, "sim:transmon:cycle", &self.artifacts(), joules_micro, grant, None)
}
}
pub fn pec_pauli(model: &NoiseModel, observable: &Pauli, noisy_expectation_fx: i64) -> (i64, i64) {
let f_p = model.fidelity(observable);
let mitigated = fdiv(noisy_expectation_fx, f_p.max(1));
(noisy_expectation_fx, mitigated.clamp(-ONE, ONE))
}
#[allow(clippy::too_many_arguments)]
pub fn seal_pec(
signer: &SigningKey,
signer_id: &str,
model: &NoiseModel,
observable_label: &str,
raw_input_bytes: &[u8],
raw_fx: i64,
mitigated_fx: i64,
error_bar_fx: i64,
shots: u64,
joules_micro: u64,
grant: GrantRef,
) -> MitigationReceipt {
MitigationReceipt::seal(
signer,
signer_id,
"sim:transmon:cycle",
"pec.pauli_lindblad",
observable_label,
content_hash(raw_input_bytes),
Some(model.model_hash()),
raw_fx,
mitigated_fx,
error_bar_fx,
shots,
joules_micro,
grant,
None,
)
}
pub fn hidden_model(n: u32, edges: &[(u32, u32)], seed: u64) -> NoiseModel {
let generators = sparse_generators(n, edges);
let mut st = seed.wrapping_mul(0xD1B5_4A32).wrapping_add(0x1234_5678);
let lambdas = generators
.iter()
.map(|g| {
let r = (splitmix64(&mut st) % 1000) as i64; let base = if g.weight() == 1 { ONE / 80 } else { ONE / 300 };
base + fmul(base, r * ONE / 1000)
})
.collect();
NoiseModel { generators, lambdas }
}
#[cfg(test)]
mod tests {
use super::*;
fn key(s: u8) -> SigningKey {
SigningKey::from_bytes(&[s; 32])
}
fn line3() -> (u32, Vec<(u32, u32)>) {
(3, vec![(0, 1), (1, 2)])
}
fn f(v: i64) -> f64 {
v as f64 / ONE as f64
}
#[test]
fn pauli_anticommutation() {
let x0 = Pauli::new(0b001, 0);
let z0 = Pauli::new(0, 0b001);
let x1 = Pauli::new(0b010, 0);
assert!(x0.anticommutes(&z0)); assert!(!x0.anticommutes(&x1)); assert!(!x0.anticommutes(&x0)); }
#[test]
fn fidelity_is_below_one_and_multiplicative() {
let (n, e) = line3();
let m = hidden_model(n, &e, 1);
for a in all_paulis(n).iter().take(10) {
let fa = m.fidelity(a);
assert!(fa > 0 && fa <= ONE, "fidelity in (0,1]: {}", f(fa));
}
}
#[test]
fn cer_recovers_hidden_rates() {
let (n, e) = line3();
let truth = hidden_model(n, &e, 7);
let gens = sparse_generators(n, &e);
let probes = all_paulis(n);
let depths = [1u32, 2, 4, 8, 16];
let r = learn_noise_model(&truth, &gens, &probes, &depths, 60_000, 200, 0xCAFE);
let mean_rate = truth.total_rate() / truth.lambdas.len() as i64;
assert!(
r.rate_error_fx < mean_rate,
"rate error {} should be below mean rate {}",
f(r.rate_error_fx), f(mean_rate)
);
let dt = (r.learned.total_rate() - truth.total_rate()).abs();
assert!(dt < truth.total_rate() / 3, "total rate close: Δ {}", f(dt));
}
#[test]
fn learning_is_deterministic() {
let (n, e) = line3();
let truth = hidden_model(n, &e, 3);
let gens = sparse_generators(n, &e);
let probes = all_paulis(n);
let d = [1u32, 2, 4, 8];
let a = learn_noise_model(&truth, &gens, &probes, &d, 20_000, 100, 42);
let b = learn_noise_model(&truth, &gens, &probes, &d, 20_000, 100, 42);
assert_eq!(a, b);
}
#[test]
fn pec_recovers_ideal_pauli_expectation() {
let (n, e) = line3();
let truth = hidden_model(n, &e, 5);
let gens = sparse_generators(n, &e);
let probes = all_paulis(n);
let r = learn_noise_model(&truth, &gens, &probes, &[1, 2, 4, 8, 16], 60_000, 200, 9);
let obs = Pauli::new(0b111, 0); let ideal = (0.9 * ONE as f64) as i64;
let noisy = fmul(ideal, truth.fidelity(&obs)); let (raw, mit) = pec_pauli(&r.learned, &obs, noisy);
assert_eq!(raw, noisy);
assert!((mit - ideal).abs() < (0.05 * ONE as f64) as i64, "PEC ~ ideal: {} vs {}", f(mit), f(ideal));
}
#[test]
fn model_seals_and_mitigation_binds_it() {
let (n, e) = line3();
let truth = hidden_model(n, &e, 11);
let gens = sparse_generators(n, &e);
let probes = all_paulis(n);
let r = learn_noise_model(&truth, &gens, &probes, &[1, 2, 4, 8], 40_000, 150, 1);
let model_rec = r.seal(&key(1), "did:key:lab", 2_000_000, GrantRef::unbounded("quantum.calibrate"));
assert!(model_rec.verify());
let obs = Pauli::new(0, 0b111); let raw_bytes = b"<twirled counts for Z0Z1Z2>";
let mit = seal_pec(
&key(1), "did:key:lab", &r.learned, "Z0Z1Z2",
raw_bytes, ONE / 2, fdiv(ONE / 2, r.learned.fidelity(&obs)), ONE / 100,
60_000, 500_000, GrantRef::unbounded("quantum.mitigate"),
);
assert!(mit.verify());
assert!(mit.noise_model_matches(&r.learned.bytes()));
assert_eq!(mit.noise_model_hash, Some(r.learned.model_hash()));
}
}