oxicuda_quantum/algorithm/
simon.rs1use crate::error::{QuantumError, QuantumResult};
32use crate::gates::hadamard::gate_h;
33use crate::handle::LcgRng;
34use crate::statevec::apply_1q::apply_1q_inplace;
35use crate::statevec::state::StateVector;
36
37#[derive(Debug, Clone)]
39pub struct SimonResult {
40 pub period: usize,
44 pub constraints: Vec<usize>,
47 pub shots_used: usize,
49}
50
51fn sample_constraint<F>(oracle: F, n: usize, rng: &mut LcgRng) -> QuantumResult<usize>
58where
59 F: Fn(usize) -> usize,
60{
61 let total_qubits = 2 * n;
62 let mut sv = StateVector::new_zero_state(total_qubits)?;
63
64 for q in 0..n {
66 apply_1q_inplace(&mut sv, q, &gate_h())?;
67 }
68
69 let dim = 1usize << total_qubits;
72 let mut new_amps = vec![num_complex::Complex::<f32>::new(0.0, 0.0); dim];
73 let query_mask = (1usize << n) - 1;
74 let out_mask = ((1usize << n) - 1) << n;
75 for (idx, a) in sv.amps.iter().enumerate() {
76 if a.norm_sqr() == 0.0 {
77 continue;
78 }
79 let x = idx & query_mask;
80 let y_in = (idx & out_mask) >> n;
81 let fx = oracle(x) & ((1usize << n) - 1);
82 let y_out = y_in ^ fx;
83 let target = x | (y_out << n);
84 new_amps[target] += *a;
85 }
86 sv.amps = new_amps;
87
88 for q in 0..n {
90 apply_1q_inplace(&mut sv, q, &gate_h())?;
91 }
92
93 let mut y = 0usize;
95 for q in 0..n {
96 let (bit, collapsed) = sv.sample_measure(q, rng)?;
97 sv = collapsed;
98 if bit {
99 y |= 1 << q;
100 }
101 }
102 Ok(y)
103}
104
105fn gf2_insert(basis: &mut Vec<usize>, mut y: usize) -> bool {
108 if y == 0 {
109 return false;
110 }
111 for &row in basis.iter() {
112 let pivot = row & row.wrapping_neg();
114 if (y & pivot) != 0 {
115 y ^= row;
116 }
117 if y == 0 {
118 return false;
119 }
120 }
121 basis.push(y);
122 true
123}
124
125pub fn recover_period_from_constraints(constraints: &[usize], n: usize) -> QuantumResult<usize> {
136 if n == 0 || n > 30 {
137 return Err(QuantumError::InvalidQubitCount { n });
138 }
139 let mut basis: Vec<usize> = Vec::new();
141 for &c in constraints {
142 let masked = c & ((1usize << n) - 1);
143 gf2_insert(&mut basis, masked);
144 }
145 let rank = basis.len();
146 if rank != n - 1 {
149 return Ok(0);
150 }
151
152 let mut rref = basis.clone();
154 for i in 0..rref.len() {
155 let pivot_i = rref[i] & rref[i].wrapping_neg();
156 for j in 0..rref.len() {
157 if i != j && (rref[j] & pivot_i) != 0 {
158 rref[j] ^= rref[i];
159 }
160 }
161 }
162 let pivot_cols: Vec<usize> = rref
163 .iter()
164 .map(|&r| (r & r.wrapping_neg()).trailing_zeros() as usize)
165 .collect();
166
167 let mut free_col = None;
169 for col in 0..n {
170 if !pivot_cols.contains(&col) {
171 free_col = Some(col);
172 break;
173 }
174 }
175 let free_col = match free_col {
176 Some(c) => c,
177 None => return Ok(0),
178 };
179
180 let mut s = 1usize << free_col;
183 for (row_idx, &row) in rref.iter().enumerate() {
184 if (row >> free_col) & 1 == 1 {
185 s |= 1 << pivot_cols[row_idx];
186 }
187 }
188 Ok(s & ((1usize << n) - 1))
189}
190
191pub fn simon<F>(
204 oracle: F,
205 n: usize,
206 max_shots: usize,
207 rng: &mut LcgRng,
208) -> QuantumResult<SimonResult>
209where
210 F: Fn(usize) -> usize,
211{
212 if n == 0 || n > 15 {
213 return Err(QuantumError::InvalidQubitCount { n });
214 }
215 let mut basis: Vec<usize> = Vec::new();
216 let mut shots_used = 0usize;
217 while basis.len() < n - 1 && shots_used < max_shots {
218 let y = sample_constraint(&oracle, n, rng)?;
219 shots_used += 1;
220 gf2_insert(&mut basis, y);
221 }
222 let period = recover_period_from_constraints(&basis, n)?;
223 Ok(SimonResult {
224 period,
225 constraints: basis,
226 shots_used,
227 })
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 fn two_to_one(s: usize) -> impl Fn(usize) -> usize {
239 move |x: usize| x.min(x ^ s)
240 }
241
242 #[test]
243 fn gf2_insert_rejects_zero() {
244 let mut basis = Vec::new();
245 assert!(!gf2_insert(&mut basis, 0));
246 assert!(basis.is_empty());
247 }
248
249 #[test]
250 fn gf2_insert_detects_dependence() {
251 let mut basis = Vec::new();
252 assert!(gf2_insert(&mut basis, 0b011));
253 assert!(gf2_insert(&mut basis, 0b101));
254 assert!(!gf2_insert(&mut basis, 0b110));
256 assert_eq!(basis.len(), 2);
257 }
258
259 #[test]
260 fn recover_period_simple_2bit() {
261 let s = recover_period_from_constraints(&[0b10], 2)
263 .expect("single valid 2-bit constraint for n=2");
264 assert_eq!(s, 0b01);
265 }
266
267 #[test]
268 fn recover_period_3bit() {
269 let s = recover_period_from_constraints(&[0b010, 0b101], 3)
272 .expect("two valid 3-bit constraints for n=3");
273 assert_eq!(s, 0b101);
274 }
275
276 #[test]
277 fn recover_period_underdetermined_returns_zero() {
278 let s = recover_period_from_constraints(&[0b010], 3)
280 .expect("single constraint for n=3 is valid, returns 0 for underdetermined");
281 assert_eq!(s, 0);
282 }
283
284 #[test]
285 fn recover_period_invalid_n() {
286 assert!(recover_period_from_constraints(&[1], 0).is_err());
287 }
288
289 #[test]
290 fn sample_constraint_is_orthogonal() {
291 let s = 0b11usize;
292 let mut rng = LcgRng::new(7);
293 for _ in 0..20 {
294 let y = sample_constraint(two_to_one(s), 2, &mut rng)
295 .expect("2-qubit Simon circuit with valid two-to-one oracle");
296 assert_eq!((y & s).count_ones() & 1, 0, "y={y:b} not orthogonal to s");
297 }
298 }
299
300 #[test]
301 fn simon_recovers_period_2bit() {
302 let s = 0b01usize;
303 let mut rng = LcgRng::new(123);
304 let res = simon(two_to_one(s), 2, 200, &mut rng)
305 .expect("simon with valid 2-qubit oracle and 200 shots");
306 assert_eq!(res.period, s, "constraints={:?}", res.constraints);
307 }
308
309 #[test]
310 fn simon_recovers_period_3bit() {
311 let s = 0b110usize;
312 let mut rng = LcgRng::new(456);
313 let res = simon(two_to_one(s), 3, 500, &mut rng)
314 .expect("simon with valid 3-qubit oracle and 500 shots");
315 assert_eq!(res.period, s, "constraints={:?}", res.constraints);
316 }
317
318 #[test]
319 fn simon_constraints_all_orthogonal() {
320 let s = 0b101usize;
321 let mut rng = LcgRng::new(999);
322 let res = simon(two_to_one(s), 3, 500, &mut rng)
323 .expect("simon with valid 3-qubit oracle and 500 shots");
324 for &y in &res.constraints {
325 assert_eq!((y & s).count_ones() & 1, 0, "y={y:b} not orthogonal");
326 }
327 }
328
329 #[test]
330 fn simon_collects_n_minus_one_constraints() {
331 let s = 0b110usize;
332 let mut rng = LcgRng::new(2024);
333 let res = simon(two_to_one(s), 3, 500, &mut rng)
334 .expect("simon with valid 3-qubit oracle and 500 shots");
335 assert_eq!(
336 res.constraints.len(),
337 2,
338 "should reach n-1 independent rows"
339 );
340 assert!(res.shots_used >= 2);
341 }
342
343 #[test]
344 fn simon_invalid_qubit_count() {
345 let mut rng = LcgRng::new(1);
346 assert!(simon(|x| x, 0, 10, &mut rng).is_err());
347 assert!(simon(|x| x, 16, 10, &mut rng).is_err());
348 }
349
350 #[test]
351 fn simon_deterministic_with_same_seed() {
352 let s = 0b11usize;
353 let mut rng_a = LcgRng::new(55);
354 let mut rng_b = LcgRng::new(55);
355 let a = simon(two_to_one(s), 2, 200, &mut rng_a)
356 .expect("simon with valid 2-qubit oracle, rng_a");
357 let b = simon(two_to_one(s), 2, 200, &mut rng_b)
358 .expect("simon with valid 2-qubit oracle, rng_b");
359 assert_eq!(a.period, b.period);
360 assert_eq!(a.constraints, b.constraints);
361 }
362}