Skip to main content

ruqu_algorithms/
grover.rs

1//! Grover's Search Algorithm
2//!
3//! Provides a quadratic speedup for **unstructured search**: given an oracle
4//! that marks M target states out of N = 2^n total states, Grover's algorithm
5//! finds a marked state with high probability in O(sqrt(N/M)) queries.
6//!
7//! # Implementation strategy
8//!
9//! Because this is a *simulation* library (not a hardware backend), the oracle
10//! and diffusion operator are implemented via **direct state-vector
11//! manipulation** through [`QuantumState::amplitudes_mut`]. This gives O(M)
12//! oracle cost and O(N) diffuser cost per iteration -- far cheaper than
13//! decomposing a general multi-controlled-Z into elementary gates.
14//!
15//! Single-qubit Hadamard gates are still applied through the normal gate
16//! pipeline so that the simulator's bookkeeping (metrics, noise, etc.)
17//! remains consistent.
18
19use ruqu_core::gate::Gate;
20use ruqu_core::state::QuantumState;
21use ruqu_core::types::{Complex, QubitIndex};
22
23// ---------------------------------------------------------------------------
24// Configuration and result types
25// ---------------------------------------------------------------------------
26
27/// Configuration for Grover's search.
28pub struct GroverConfig {
29    /// Number of qubits (search space has 2^num_qubits states).
30    pub num_qubits: u32,
31    /// Indices of the marked (target) basis states. Each index must be in
32    /// `0 .. 2^num_qubits`.
33    pub target_states: Vec<usize>,
34    /// Number of Grover iterations. When `None`, the theoretically optimal
35    /// count is computed from [`optimal_iterations`].
36    pub num_iterations: Option<u32>,
37    /// Optional RNG seed forwarded to [`QuantumState::new_with_seed`].
38    pub seed: Option<u64>,
39}
40
41/// Result of a Grover search run.
42pub struct GroverResult {
43    /// The basis-state index obtained by measuring all qubits.
44    pub measured_state: usize,
45    /// Whether `measured_state` is one of the target states.
46    pub target_found: bool,
47    /// Pre-measurement probability of observing *any* target state.
48    pub success_probability: f64,
49    /// Number of Grover iterations that were executed.
50    pub num_iterations: u32,
51    /// Post-measurement quantum state (collapsed).
52    pub state: QuantumState,
53}
54
55// ---------------------------------------------------------------------------
56// Optimal iteration count
57// ---------------------------------------------------------------------------
58
59/// Compute the theoretically optimal number of Grover iterations.
60///
61/// For N = 2^n states and M marked targets the optimal count is:
62///
63/// ```text
64/// k = round( (pi / 4) * sqrt(N / M) - 0.5 )
65/// ```
66///
67/// which maximizes the success probability (close to 1 when M << N).
68/// Returns at least 1.
69pub fn optimal_iterations(num_qubits: u32, num_targets: usize) -> u32 {
70    let n = 1usize << num_qubits;
71    let theta = (num_targets as f64 / n as f64).sqrt().asin();
72    let k = (std::f64::consts::FRAC_PI_4 / theta - 0.5).round().max(1.0);
73    k as u32
74}
75
76// ---------------------------------------------------------------------------
77// Core algorithm
78// ---------------------------------------------------------------------------
79
80/// Run Grover's search algorithm.
81///
82/// # Algorithm outline
83///
84/// 1. Prepare equal superposition |s> = H^n |0>.
85/// 2. Repeat for `num_iterations`:
86///    a. **Oracle** -- negate the amplitude of every target state.
87///    b. **Diffuser** -- reflect about |s>:
88///       i.  Apply H on all qubits.
89///       ii. Negate all amplitudes except the |0...0> component.
90///       iii.Apply H on all qubits.
91/// 3. Compute success probability from the final state.
92/// 4. Measure all qubits to obtain a classical bitstring.
93///
94/// # Errors
95///
96/// Returns a [`ruqu_core::error::QuantumError`] if the qubit count exceeds
97/// simulator limits or any gate application fails.
98pub fn run_grover(config: &GroverConfig) -> ruqu_core::error::Result<GroverResult> {
99    let n = config.num_qubits;
100    let dim = 1usize << n;
101
102    // Validate target indices.
103    for &t in &config.target_states {
104        assert!(
105            t < dim,
106            "target state index {} out of range for {} qubits (max {})",
107            t,
108            n,
109            dim - 1,
110        );
111    }
112
113    let iterations = config
114        .num_iterations
115        .unwrap_or_else(|| optimal_iterations(n, config.target_states.len()));
116
117    // ----- Step 1: Initialize to equal superposition -----
118    let mut state = match config.seed {
119        Some(s) => QuantumState::new_with_seed(n, s)?,
120        None => QuantumState::new(n)?,
121    };
122    for q in 0..n {
123        state.apply_gate(&Gate::H(q))?;
124    }
125
126    // ----- Step 2: Grover iterations -----
127    for _ in 0..iterations {
128        // (a) Oracle: negate amplitudes of target states.
129        {
130            let amps = state.amplitudes_mut();
131            for &target in &config.target_states {
132                let a = amps[target];
133                amps[target] = Complex {
134                    re: -a.re,
135                    im: -a.im,
136                };
137            }
138        }
139
140        // (b) Diffuser: 2|s><s| - I = H^n (2|0><0| - I) H^n
141        //     (2|0><0| - I) keeps |0> unchanged and negates everything else.
142        for q in 0..n {
143            state.apply_gate(&Gate::H(q))?;
144        }
145        {
146            let amps = state.amplitudes_mut();
147            for i in 1..amps.len() {
148                let a = amps[i];
149                amps[i] = Complex {
150                    re: -a.re,
151                    im: -a.im,
152                };
153            }
154        }
155        for q in 0..n {
156            state.apply_gate(&Gate::H(q))?;
157        }
158    }
159
160    // ----- Step 3: Compute success probability before measurement -----
161    let probs = state.probabilities();
162    let success_probability: f64 = config.target_states.iter().map(|&t| probs[t]).sum();
163
164    // ----- Step 4: Measure all qubits -----
165    let measured = measure_all_qubits(&mut state, n)?;
166    let target_found = config.target_states.contains(&measured);
167
168    Ok(GroverResult {
169        measured_state: measured,
170        target_found,
171        success_probability,
172        num_iterations: iterations,
173        state,
174    })
175}
176
177// ---------------------------------------------------------------------------
178// Helpers
179// ---------------------------------------------------------------------------
180
181/// Measure every qubit (0 through `num_qubits - 1`) and assemble the results
182/// into a single `usize` where bit `q` is 1 when qubit `q` measured |1>.
183///
184/// Measurements are performed in ascending qubit order. Each measurement
185/// collapses the state, so subsequent outcomes are conditioned on earlier
186/// ones. The joint distribution over all qubits matches `probabilities()`.
187fn measure_all_qubits(
188    state: &mut QuantumState,
189    num_qubits: u32,
190) -> ruqu_core::error::Result<usize> {
191    let mut result: usize = 0;
192    for q in 0..num_qubits {
193        let outcome = state.measure(q as QubitIndex)?;
194        if outcome.result {
195            result |= 1 << q;
196        }
197    }
198    Ok(result)
199}
200
201// ---------------------------------------------------------------------------
202// Tests
203// ---------------------------------------------------------------------------
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_optimal_iterations_single_target() {
211        // N=8, M=1 -> k = round(pi/4 * sqrt(8) - 0.5) = round(1.72) = 2
212        let k = optimal_iterations(3, 1);
213        assert_eq!(k, 2);
214    }
215
216    #[test]
217    fn test_optimal_iterations_half_marked() {
218        // N=4, M=2 -> theta = asin(sqrt(0.5)) = pi/4
219        // k = round(pi/4 / (pi/4) - 0.5) = round(0.5) = 1
220        let k = optimal_iterations(2, 2);
221        assert!(k >= 1);
222    }
223
224    #[test]
225    fn test_optimal_iterations_minimum_one() {
226        // Even pathological inputs should produce at least 1.
227        let k = optimal_iterations(1, 1);
228        assert!(k >= 1);
229    }
230}