use crate::error::{QuantumError, QuantumResult};
use crate::gates::hadamard::gate_h;
use crate::handle::LcgRng;
use crate::statevec::apply_1q::apply_1q_inplace;
use crate::statevec::state::StateVector;
#[derive(Debug, Clone)]
pub struct SimonResult {
pub period: usize,
pub constraints: Vec<usize>,
pub shots_used: usize,
}
fn sample_constraint<F>(oracle: F, n: usize, rng: &mut LcgRng) -> QuantumResult<usize>
where
F: Fn(usize) -> usize,
{
let total_qubits = 2 * n;
let mut sv = StateVector::new_zero_state(total_qubits)?;
for q in 0..n {
apply_1q_inplace(&mut sv, q, &gate_h())?;
}
let dim = 1usize << total_qubits;
let mut new_amps = vec![num_complex::Complex::<f32>::new(0.0, 0.0); dim];
let query_mask = (1usize << n) - 1;
let out_mask = ((1usize << n) - 1) << n;
for (idx, a) in sv.amps.iter().enumerate() {
if a.norm_sqr() == 0.0 {
continue;
}
let x = idx & query_mask;
let y_in = (idx & out_mask) >> n;
let fx = oracle(x) & ((1usize << n) - 1);
let y_out = y_in ^ fx;
let target = x | (y_out << n);
new_amps[target] += *a;
}
sv.amps = new_amps;
for q in 0..n {
apply_1q_inplace(&mut sv, q, &gate_h())?;
}
let mut y = 0usize;
for q in 0..n {
let (bit, collapsed) = sv.sample_measure(q, rng)?;
sv = collapsed;
if bit {
y |= 1 << q;
}
}
Ok(y)
}
fn gf2_insert(basis: &mut Vec<usize>, mut y: usize) -> bool {
if y == 0 {
return false;
}
for &row in basis.iter() {
let pivot = row & row.wrapping_neg();
if (y & pivot) != 0 {
y ^= row;
}
if y == 0 {
return false;
}
}
basis.push(y);
true
}
pub fn recover_period_from_constraints(constraints: &[usize], n: usize) -> QuantumResult<usize> {
if n == 0 || n > 30 {
return Err(QuantumError::InvalidQubitCount { n });
}
let mut basis: Vec<usize> = Vec::new();
for &c in constraints {
let masked = c & ((1usize << n) - 1);
gf2_insert(&mut basis, masked);
}
let rank = basis.len();
if rank != n - 1 {
return Ok(0);
}
let mut rref = basis.clone();
for i in 0..rref.len() {
let pivot_i = rref[i] & rref[i].wrapping_neg();
for j in 0..rref.len() {
if i != j && (rref[j] & pivot_i) != 0 {
rref[j] ^= rref[i];
}
}
}
let pivot_cols: Vec<usize> = rref
.iter()
.map(|&r| (r & r.wrapping_neg()).trailing_zeros() as usize)
.collect();
let mut free_col = None;
for col in 0..n {
if !pivot_cols.contains(&col) {
free_col = Some(col);
break;
}
}
let free_col = match free_col {
Some(c) => c,
None => return Ok(0),
};
let mut s = 1usize << free_col;
for (row_idx, &row) in rref.iter().enumerate() {
if (row >> free_col) & 1 == 1 {
s |= 1 << pivot_cols[row_idx];
}
}
Ok(s & ((1usize << n) - 1))
}
pub fn simon<F>(
oracle: F,
n: usize,
max_shots: usize,
rng: &mut LcgRng,
) -> QuantumResult<SimonResult>
where
F: Fn(usize) -> usize,
{
if n == 0 || n > 15 {
return Err(QuantumError::InvalidQubitCount { n });
}
let mut basis: Vec<usize> = Vec::new();
let mut shots_used = 0usize;
while basis.len() < n - 1 && shots_used < max_shots {
let y = sample_constraint(&oracle, n, rng)?;
shots_used += 1;
gf2_insert(&mut basis, y);
}
let period = recover_period_from_constraints(&basis, n)?;
Ok(SimonResult {
period,
constraints: basis,
shots_used,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn two_to_one(s: usize) -> impl Fn(usize) -> usize {
move |x: usize| x.min(x ^ s)
}
#[test]
fn gf2_insert_rejects_zero() {
let mut basis = Vec::new();
assert!(!gf2_insert(&mut basis, 0));
assert!(basis.is_empty());
}
#[test]
fn gf2_insert_detects_dependence() {
let mut basis = Vec::new();
assert!(gf2_insert(&mut basis, 0b011));
assert!(gf2_insert(&mut basis, 0b101));
assert!(!gf2_insert(&mut basis, 0b110));
assert_eq!(basis.len(), 2);
}
#[test]
fn recover_period_simple_2bit() {
let s = recover_period_from_constraints(&[0b10], 2)
.expect("single valid 2-bit constraint for n=2");
assert_eq!(s, 0b01);
}
#[test]
fn recover_period_3bit() {
let s = recover_period_from_constraints(&[0b010, 0b101], 3)
.expect("two valid 3-bit constraints for n=3");
assert_eq!(s, 0b101);
}
#[test]
fn recover_period_underdetermined_returns_zero() {
let s = recover_period_from_constraints(&[0b010], 3)
.expect("single constraint for n=3 is valid, returns 0 for underdetermined");
assert_eq!(s, 0);
}
#[test]
fn recover_period_invalid_n() {
assert!(recover_period_from_constraints(&[1], 0).is_err());
}
#[test]
fn sample_constraint_is_orthogonal() {
let s = 0b11usize;
let mut rng = LcgRng::new(7);
for _ in 0..20 {
let y = sample_constraint(two_to_one(s), 2, &mut rng)
.expect("2-qubit Simon circuit with valid two-to-one oracle");
assert_eq!((y & s).count_ones() & 1, 0, "y={y:b} not orthogonal to s");
}
}
#[test]
fn simon_recovers_period_2bit() {
let s = 0b01usize;
let mut rng = LcgRng::new(123);
let res = simon(two_to_one(s), 2, 200, &mut rng)
.expect("simon with valid 2-qubit oracle and 200 shots");
assert_eq!(res.period, s, "constraints={:?}", res.constraints);
}
#[test]
fn simon_recovers_period_3bit() {
let s = 0b110usize;
let mut rng = LcgRng::new(456);
let res = simon(two_to_one(s), 3, 500, &mut rng)
.expect("simon with valid 3-qubit oracle and 500 shots");
assert_eq!(res.period, s, "constraints={:?}", res.constraints);
}
#[test]
fn simon_constraints_all_orthogonal() {
let s = 0b101usize;
let mut rng = LcgRng::new(999);
let res = simon(two_to_one(s), 3, 500, &mut rng)
.expect("simon with valid 3-qubit oracle and 500 shots");
for &y in &res.constraints {
assert_eq!((y & s).count_ones() & 1, 0, "y={y:b} not orthogonal");
}
}
#[test]
fn simon_collects_n_minus_one_constraints() {
let s = 0b110usize;
let mut rng = LcgRng::new(2024);
let res = simon(two_to_one(s), 3, 500, &mut rng)
.expect("simon with valid 3-qubit oracle and 500 shots");
assert_eq!(
res.constraints.len(),
2,
"should reach n-1 independent rows"
);
assert!(res.shots_used >= 2);
}
#[test]
fn simon_invalid_qubit_count() {
let mut rng = LcgRng::new(1);
assert!(simon(|x| x, 0, 10, &mut rng).is_err());
assert!(simon(|x| x, 16, 10, &mut rng).is_err());
}
#[test]
fn simon_deterministic_with_same_seed() {
let s = 0b11usize;
let mut rng_a = LcgRng::new(55);
let mut rng_b = LcgRng::new(55);
let a = simon(two_to_one(s), 2, 200, &mut rng_a)
.expect("simon with valid 2-qubit oracle, rng_a");
let b = simon(two_to_one(s), 2, 200, &mut rng_b)
.expect("simon with valid 2-qubit oracle, rng_b");
assert_eq!(a.period, b.period);
assert_eq!(a.constraints, b.constraints);
}
}