oxicuda_quantum/algorithm/
grover.rs1use crate::error::{QuantumError, QuantumResult};
29use crate::gates::hadamard::gate_h;
30use crate::statevec::apply_1q::apply_1q_inplace;
31use crate::statevec::state::StateVector;
32
33#[derive(Debug, Clone)]
35pub struct GroverResult {
36 pub measured_index: usize,
38 pub probability: f32,
40 pub iterations: usize,
42}
43
44#[must_use]
53pub fn optimal_iterations(dim: usize, n_marked: usize) -> usize {
54 if n_marked == 0 || dim == 0 {
55 return 0;
56 }
57 let ratio = dim as f64 / n_marked as f64;
58 let theta = (n_marked as f64 / dim as f64).sqrt().asin();
60 if theta <= 0.0 {
61 return 0;
62 }
63 let raw = (std::f64::consts::FRAC_PI_2 / theta - 1.0) * 0.5;
64 let rounded = raw.round();
65 if rounded <= 0.0 {
66 if ratio >= 2.0 { 1 } else { 0 }
69 } else {
70 rounded as usize
71 }
72}
73
74fn validate(oracle_marks: &[usize], n_qubits: usize) -> QuantumResult<usize> {
76 if n_qubits == 0 || n_qubits > 30 {
77 return Err(QuantumError::InvalidQubitCount { n: n_qubits });
78 }
79 let dim = 1usize << n_qubits;
80 for &m in oracle_marks {
81 if m >= dim {
82 return Err(QuantumError::InvalidParameter {
83 name: format!("marked index {m} out of range for dim {dim}"),
84 });
85 }
86 }
87 Ok(dim)
88}
89
90fn apply_oracle(sv: &mut StateVector, marks: &[bool]) {
92 for (a, &is_marked) in sv.amps.iter_mut().zip(marks.iter()) {
93 if is_marked {
94 *a = -*a;
95 }
96 }
97}
98
99fn apply_diffusion(sv: &mut StateVector) {
101 let dim = sv.amps.len();
102 if dim == 0 {
103 return;
104 }
105 let inv_dim = 1.0 / dim as f32;
106 let mut mean = num_complex::Complex::<f32>::new(0.0, 0.0);
107 for a in &sv.amps {
108 mean += *a;
109 }
110 mean *= inv_dim;
111 let two_mean = mean * 2.0;
112 for a in &mut sv.amps {
113 *a = two_mean - *a;
114 }
115}
116
117pub fn grover_search(oracle_marks: &[usize], n_qubits: usize) -> QuantumResult<GroverResult> {
131 let dim = validate(oracle_marks, n_qubits)?;
132
133 let mut marks = vec![false; dim];
135 for &m in oracle_marks {
136 marks[m] = true;
137 }
138 let n_marked = marks.iter().filter(|&&b| b).count();
139
140 let mut sv = StateVector::new_zero_state(n_qubits)?;
142 for q in 0..n_qubits {
143 apply_1q_inplace(&mut sv, q, &gate_h())?;
144 }
145
146 let iterations = optimal_iterations(dim, n_marked);
147 for _ in 0..iterations {
148 apply_oracle(&mut sv, &marks);
149 apply_diffusion(&mut sv);
150 }
151
152 let mut measured_index = 0usize;
154 let mut best = f32::NEG_INFINITY;
155 for (i, a) in sv.amps.iter().enumerate() {
156 let p = a.norm_sqr();
157 if p > best {
158 best = p;
159 measured_index = i;
160 }
161 }
162
163 Ok(GroverResult {
164 measured_index,
165 probability: best,
166 iterations,
167 })
168}
169
170pub fn marked_probability(oracle_marks: &[usize], n_qubits: usize) -> QuantumResult<f32> {
178 let dim = validate(oracle_marks, n_qubits)?;
179 let mut marks = vec![false; dim];
180 for &m in oracle_marks {
181 marks[m] = true;
182 }
183 let n_marked = marks.iter().filter(|&&b| b).count();
184
185 let mut sv = StateVector::new_zero_state(n_qubits)?;
186 for q in 0..n_qubits {
187 apply_1q_inplace(&mut sv, q, &gate_h())?;
188 }
189 let iterations = optimal_iterations(dim, n_marked);
190 for _ in 0..iterations {
191 apply_oracle(&mut sv, &marks);
192 apply_diffusion(&mut sv);
193 }
194
195 let mass = sv
196 .amps
197 .iter()
198 .zip(marks.iter())
199 .filter(|&(_, &m)| m)
200 .map(|(a, _)| a.norm_sqr())
201 .sum::<f32>();
202 Ok(mass)
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn finds_marked_single_solution() {
211 let res = grover_search(&[5], 3).expect("valid 3-qubit grover search for mark 5");
213 assert_eq!(res.measured_index, 5, "got {}", res.measured_index);
214 assert!(res.probability > 0.9, "prob={}", res.probability);
215 }
216
217 #[test]
218 fn amplitude_amplified_above_uniform() {
219 let n = 4;
221 let uniform = 1.0 / (1u32 << n) as f32;
222 let mass =
223 marked_probability(&[9], n).expect("valid marked probability for 4-qubit single mark");
224 assert!(
225 mass > uniform * 4.0,
226 "mass={mass} not amplified over uniform {uniform}"
227 );
228 assert!(mass > 0.9, "mass={mass}");
229 }
230
231 #[test]
232 fn optimal_iterations_matches_known_values() {
233 assert_eq!(optimal_iterations(4, 1), 1);
235 assert_eq!(optimal_iterations(8, 1), 2);
237 assert_eq!(optimal_iterations(16, 1), 3);
239 }
240
241 #[test]
242 fn multiple_marked_items_found() {
243 let marks = [2usize, 6];
246 let res = grover_search(&marks, 3).expect("valid 3-qubit grover search for marks [2, 6]");
247 assert!(
248 marks.contains(&res.measured_index),
249 "got {}",
250 res.measured_index
251 );
252 let mass = marked_probability(&marks, 3)
253 .expect("valid 3-qubit marked probability for marks [2, 6]");
254 assert!(mass > 0.9, "mass={mass}");
255 }
256
257 #[test]
258 fn no_marked_items_zero_iterations() {
259 let res = grover_search(&[], 3).expect("valid 3-qubit grover search with empty oracle");
261 assert_eq!(res.iterations, 0);
262 assert!(
264 (res.probability - 1.0 / 8.0).abs() < 1e-5,
265 "p={}",
266 res.probability
267 );
268 }
269
270 #[test]
271 fn n_qubits_zero_errors() {
272 assert!(grover_search(&[0], 0).is_err());
273 }
274
275 #[test]
276 fn probability_concentrates_with_more_qubits() {
277 for n in 2..=6 {
279 let target = (1usize << n) - 1; let mass = marked_probability(&[target], n)
281 .expect("valid marked probability for last-index target");
282 assert!(mass > 0.8, "n={n} mass={mass}");
283 }
284 }
285
286 #[test]
287 fn deterministic_runs() {
288 let a = grover_search(&[3], 4).expect("valid 4-qubit grover search for mark 3 (run a)");
290 let b = grover_search(&[3], 4).expect("valid 4-qubit grover search for mark 3 (run b)");
291 assert_eq!(a.measured_index, b.measured_index);
292 assert!((a.probability - b.probability).abs() < 1e-7);
293 assert_eq!(a.iterations, b.iterations);
294 }
295
296 #[test]
297 fn single_item_space() {
298 let res = grover_search(&[2], 2).expect("valid 2-qubit grover search for mark 2");
303 assert_eq!(res.measured_index, 2, "got {}", res.measured_index);
304 assert!(res.probability > 0.99, "prob={}", res.probability);
305 }
306
307 #[test]
308 fn n_eq_one_is_degenerate() {
309 let mass =
312 marked_probability(&[1], 1).expect("valid 1-qubit marked probability for single mark");
313 assert!((mass - 0.5).abs() < 1e-4, "mass={mass}");
314 }
315
316 #[test]
317 fn out_of_range_mark_errors() {
318 assert!(grover_search(&[8], 3).is_err());
320 }
321
322 #[test]
323 fn preserves_norm_through_iterations() {
324 let n = 4;
326 let marks = [1usize, 7, 11];
327 let marked = marked_probability(&marks, n)
328 .expect("valid 4-qubit marked probability for marks [1, 7, 11]");
329 assert!(marked <= 1.0 + 1e-5, "marked mass {marked} exceeds 1");
330 let mut all = vec![false; 1 << n];
332 for &m in &marks {
333 all[m] = true;
334 }
335 let n_marked = marks.len();
336 let mut sv =
337 StateVector::new_zero_state(n).expect("valid 4-qubit zero state for norm check");
338 for q in 0..n {
339 apply_1q_inplace(&mut sv, q, &gate_h()).expect("valid qubit index for Hadamard gate");
340 }
341 for _ in 0..optimal_iterations(1 << n, n_marked) {
342 apply_oracle(&mut sv, &all);
343 apply_diffusion(&mut sv);
344 }
345 assert!((sv.norm_sq() - 1.0).abs() < 1e-4, "norm={}", sv.norm_sq());
346 }
347}