#[derive(Clone, Copy)]
struct C {
re: f64,
im: f64,
}
impl C {
const ZERO: C = C { re: 0.0, im: 0.0 };
#[inline]
fn add(self, o: C) -> C {
C { re: self.re + o.re, im: self.im + o.im }
}
#[inline]
fn mul(self, o: C) -> C {
C { re: self.re * o.re - self.im * o.im, im: self.re * o.im + self.im * o.re }
}
#[inline]
fn scale(self, s: f64) -> C {
C { re: self.re * s, im: self.im * s }
}
#[inline]
fn norm2(self) -> f64 {
self.re * self.re + self.im * self.im
}
}
fn splitmix64(s: &mut u64) -> u64 {
*s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *s;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn rand_angle(s: &mut u64) -> f64 {
let u = (splitmix64(s) >> 11) as f64 / (1u64 << 53) as f64;
(u * 2.0 - 1.0) * std::f64::consts::PI
}
#[inline]
fn apply_ry(amps: &mut [C], n: u8, q: u8, theta: f64) {
let (s, c) = crate::repro::sin_cos(theta * 0.5);
let bit = 1usize << q;
let dim = 1usize << n;
let mut i = 0;
while i < dim {
if i & bit == 0 {
let j = i | bit;
let (a, b) = (amps[i], amps[j]);
amps[i] = a.scale(c).add(b.scale(-s));
amps[j] = a.scale(s).add(b.scale(c));
}
i += 1;
}
}
#[inline]
fn apply_rz(amps: &mut [C], q: u8, theta: f64) {
let (sm, cm) = crate::repro::sin_cos(theta * 0.5);
let e0 = C { re: cm, im: -sm }; let e1 = C { re: cm, im: sm }; let bit = 1usize << q;
for (i, a) in amps.iter_mut().enumerate() {
*a = a.mul(if i & bit == 0 { e0 } else { e1 });
}
}
#[inline]
fn apply_cx(amps: &mut [C], n: u8, c: u8, t: u8) {
let (cb, tb) = (1usize << c, 1usize << t);
let dim = 1usize << n;
for i in 0..dim {
if (i & cb) != 0 && (i & tb) == 0 {
amps.swap(i, i | tb);
}
}
}
fn ansatz_state(n: u8, layers: u8, params: &[f64]) -> Vec<C> {
let dim = 1usize << n;
let mut amps = vec![C::ZERO; dim];
amps[0] = C { re: 1.0, im: 0.0 };
let mut p = 0;
for _ in 0..layers {
for q in 0..n {
apply_ry(&mut amps, n, q, params[p]);
p += 1;
apply_rz(&mut amps, q, params[p]);
p += 1;
}
for q in 0..n {
apply_cx(&mut amps, n, q, (q + 1) % n);
}
}
amps
}
pub fn num_params(n: u8, layers: u8) -> usize {
layers as usize * 2 * n as usize
}
pub fn born_probs(n: u8, layers: u8, params: &[f64]) -> Vec<f64> {
ansatz_state(n, layers, params).iter().map(|a| a.norm2()).collect()
}
pub struct BornFit {
pub params: Vec<f64>,
pub loss_curve: Vec<f64>,
pub final_probs: Vec<f64>,
pub final_kl: f64,
}
fn kl(target: &[f64], model: &[f64]) -> f64 {
let mut s = 0.0;
for (&q, &p) in target.iter().zip(model) {
if q > 0.0 {
s += q * crate::repro::ln(q / p.max(1e-12));
}
}
s
}
pub fn train_born(target: &[f64], n: u8, layers: u8, steps: u32, lr: f64, seed: u64) -> BornFit {
let np = num_params(n, layers);
let mut st = seed.wrapping_mul(0xA24B_AED4).wrapping_add(1);
let mut params: Vec<f64> = (0..np).map(|_| rand_angle(&mut st)).collect();
let shift = std::f64::consts::FRAC_PI_2;
let mut loss_curve = Vec::with_capacity(steps as usize + 1);
for _ in 0..steps {
let probs = born_probs(n, layers, ¶ms);
loss_curve.push(kl(target, &probs));
let mut grad = vec![0.0; np];
for i in 0..np {
let saved = params[i];
params[i] = saved + shift;
let pp = born_probs(n, layers, ¶ms);
params[i] = saved - shift;
let pm = born_probs(n, layers, ¶ms);
params[i] = saved;
let mut g = 0.0;
for x in 0..target.len() {
if target[x] > 0.0 {
let dp = 0.5 * (pp[x] - pm[x]);
g -= target[x] / probs[x].max(1e-12) * dp;
}
}
grad[i] = g;
}
for i in 0..np {
params[i] -= lr * grad[i];
}
}
let final_probs = born_probs(n, layers, ¶ms);
let final_kl = kl(target, &final_probs);
loss_curve.push(final_kl);
BornFit { params, loss_curve, final_probs, final_kl }
}
fn cost_z0(n: u8, layers: u8, params: &[f64]) -> f64 {
let probs = born_probs(n, layers, params);
let mut acc = 0.0;
for (i, p) in probs.iter().enumerate() {
acc += if i & 1 == 0 { *p } else { -*p };
}
acc
}
pub fn barren_plateau_variance(n: u8, layers: u8, samples: u32, seed: u64) -> f64 {
let np = num_params(n, layers);
let mut st = seed.wrapping_mul(0x2545_F491).wrapping_add(7);
let shift = std::f64::consts::FRAC_PI_2;
let mut grads = Vec::with_capacity(samples as usize);
for _ in 0..samples {
let mut params: Vec<f64> = (0..np).map(|_| rand_angle(&mut st)).collect();
let saved = params[0];
params[0] = saved + shift;
let cp = cost_z0(n, layers, ¶ms);
params[0] = saved - shift;
let cm = cost_z0(n, layers, ¶ms);
grads.push(0.5 * (cp - cm));
}
let mean = grads.iter().sum::<f64>() / grads.len() as f64;
grads.iter().map(|g| (g - mean) * (g - mean)).sum::<f64>() / grads.len() as f64
}
pub fn parity_target(n: u8) -> Vec<f64> {
let dim = 1usize << n;
let even: Vec<usize> = (0..dim).filter(|x| (x.count_ones() % 2) == 0).collect();
let w = 1.0 / even.len() as f64;
let mut t = vec![0.0; dim];
for x in even {
t[x] = w;
}
t
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn born_probs_normalized() {
let n = 4;
let l = 3;
let mut st = 1u64;
let params: Vec<f64> = (0..num_params(n, l)).map(|_| rand_angle(&mut st)).collect();
let p = born_probs(n, l, ¶ms);
assert!((p.iter().sum::<f64>() - 1.0).abs() < 1e-9, "Born probs must sum to 1");
}
#[test]
fn born_machine_learns_a_bell_target() {
let target = vec![0.5, 0.0, 0.0, 0.5];
let fit = train_born(&target, 2, 3, 120, 0.3, 7);
assert!(fit.loss_curve[0] > fit.final_kl, "training must reduce KL");
assert!(fit.final_kl < 0.05, "should fit the target well: KL={}", fit.final_kl);
assert!(fit.final_probs[0] + fit.final_probs[3] > 0.9, "mass on the correlated pair");
}
#[test]
fn born_machine_learns_the_parity_target() {
let n = 3;
let target = parity_target(n);
let fit = train_born(&target, n, 4, 200, 0.2, 3);
assert!(fit.final_kl < 0.15, "parity target learned: KL={}", fit.final_kl);
let odd_mass: f64 = (0..(1 << n)).filter(|x: &usize| x.count_ones() % 2 == 1).map(|x| fit.final_probs[x]).sum();
assert!(odd_mass < 0.2, "odd sector suppressed by interference: {odd_mass}");
}
#[test]
fn barren_plateau_variance_decays_with_qubits() {
let v_small = barren_plateau_variance(2, 6, 400, 1);
let v_mid = barren_plateau_variance(4, 6, 400, 1);
let v_big = barren_plateau_variance(6, 6, 400, 1);
assert!(v_mid < v_small, "gradient variance shrinks with n: {v_mid} < {v_small}");
assert!(v_big < v_mid, "…and keeps shrinking: {v_big} < {v_mid}");
assert!(v_big < v_small * 0.4, "clear exponential-ish decay");
}
#[test]
fn deterministic() {
let a = train_born(&[0.5, 0.0, 0.0, 0.5], 2, 2, 30, 0.3, 42).final_kl;
let b = train_born(&[0.5, 0.0, 0.0, 0.5], 2, 2, 30, 0.3, 42).final_kl;
assert_eq!(a.to_bits(), b.to_bits(), "training must be bit-reproducible");
}
}