Skip to main content

oxicuda_quantum/algorithm/
deutsch_jozsa.rs

1//! Deutsch–Jozsa algorithm.
2//!
3//! Given a promise that a Boolean function `f : {0,1}^n → {0,1}` is either
4//! *constant* (same output for every input) or *balanced* (output `0` for
5//! exactly half the inputs and `1` for the other half), the Deutsch–Jozsa
6//! algorithm decides which case holds with a **single** oracle query —
7//! exponentially fewer than the `2^{n-1}+1` classical queries needed in the
8//! worst case for deterministic certainty.
9//!
10//! The circuit acts on `n` query qubits (the ancilla / phase-kickback target is
11//! folded into the oracle as a relative sign, so no explicit ancilla is
12//! simulated):
13//!
14//! 1. Prepare `H^{⊗n}|0⟩`, the uniform superposition.
15//! 2. Apply the phase oracle `O_f|x⟩ = (-1)^{f(x)}|x⟩`.
16//! 3. Apply `H^{⊗n}` again.
17//! 4. Measure. The probability of reading `|0…0⟩` is `1` if `f` is constant and
18//!    `0` if `f` is balanced.
19//!
20//! This implementation evaluates the user-supplied `f` over the truth table to
21//! build the phase oracle directly on the amplitude vector, which is both exact
22//! and `O(2^n)`.
23
24use crate::error::{QuantumError, QuantumResult};
25use crate::gates::hadamard::gate_h;
26use crate::statevec::apply_1q::apply_1q_inplace;
27use crate::statevec::state::StateVector;
28
29/// Classification returned by [`deutsch_jozsa`].
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum FunctionKind {
32    /// `f` outputs the same bit for every input.
33    Constant,
34    /// `f` outputs `0` for half the inputs and `1` for the other half.
35    Balanced,
36}
37
38/// Outcome of [`deutsch_jozsa`].
39#[derive(Debug, Clone)]
40pub struct DeutschJozsaResult {
41    /// Decision: constant vs. balanced.
42    pub kind: FunctionKind,
43    /// Probability of measuring the all-zeros string `|0…0⟩`.
44    pub all_zero_probability: f32,
45}
46
47/// Run Deutsch–Jozsa for a function given as a truth-table closure.
48///
49/// `oracle(x)` must return `f(x) ∈ {false, true}` for every basis index
50/// `x ∈ [0, 2^n)`. The routine returns [`FunctionKind::Constant`] when the
51/// post-circuit all-zeros probability is `≈ 1`, and [`FunctionKind::Balanced`]
52/// otherwise (the promise guarantees these are the only two possibilities).
53///
54/// # Errors
55/// [`QuantumError::InvalidQubitCount`] if `n_qubits` is `0` or `> 30`.
56pub fn deutsch_jozsa<F>(oracle: F, n_qubits: usize) -> QuantumResult<DeutschJozsaResult>
57where
58    F: Fn(usize) -> bool,
59{
60    if n_qubits == 0 || n_qubits > 30 {
61        return Err(QuantumError::InvalidQubitCount { n: n_qubits });
62    }
63
64    // Step 1: uniform superposition.
65    let mut sv = StateVector::new_zero_state(n_qubits)?;
66    for q in 0..n_qubits {
67        apply_1q_inplace(&mut sv, q, &gate_h())?;
68    }
69
70    // Step 2: phase oracle (-1)^{f(x)} applied directly to each amplitude.
71    for (x, a) in sv.amps.iter_mut().enumerate() {
72        if oracle(x) {
73            *a = -*a;
74        }
75    }
76
77    // Step 3: re-apply Hadamards.
78    for q in 0..n_qubits {
79        apply_1q_inplace(&mut sv, q, &gate_h())?;
80    }
81
82    // Step 4: probability of the all-zeros basis string.
83    let all_zero_probability = sv.amps[0].norm_sqr();
84    let kind = if all_zero_probability > 0.5 {
85        FunctionKind::Constant
86    } else {
87        FunctionKind::Balanced
88    };
89
90    Ok(DeutschJozsaResult {
91        kind,
92        all_zero_probability,
93    })
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn constant_zero_is_constant() {
102        // f(x) = 0 for all x ⇒ all-zero probability 1.
103        let res = deutsch_jozsa(|_| false, 3)
104            .expect("n=3 is within [1, 30] and constant-zero oracle is well-defined");
105        assert_eq!(res.kind, FunctionKind::Constant);
106        assert!(
107            (res.all_zero_probability - 1.0).abs() < 1e-5,
108            "p={}",
109            res.all_zero_probability
110        );
111    }
112
113    #[test]
114    fn constant_one_is_constant() {
115        // f(x) = 1 for all x ⇒ global phase only, still all-zero probability 1.
116        let res = deutsch_jozsa(|_| true, 4)
117            .expect("n=4 is within [1, 30] and constant-one oracle is well-defined");
118        assert_eq!(res.kind, FunctionKind::Constant);
119        assert!(
120            (res.all_zero_probability - 1.0).abs() < 1e-5,
121            "p={}",
122            res.all_zero_probability
123        );
124    }
125
126    #[test]
127    fn parity_function_is_balanced() {
128        // f(x) = parity(x) is balanced ⇒ all-zero probability 0.
129        let res = deutsch_jozsa(|x| (x.count_ones() & 1) == 1, 3)
130            .expect("n=3 is within [1, 30] and parity is a well-defined boolean function");
131        assert_eq!(res.kind, FunctionKind::Balanced);
132        assert!(
133            res.all_zero_probability < 1e-5,
134            "p={}",
135            res.all_zero_probability
136        );
137    }
138
139    #[test]
140    fn single_bit_balanced() {
141        // f(x) = x_0 (least-significant bit) is balanced.
142        let res = deutsch_jozsa(|x| (x & 1) == 1, 3)
143            .expect("n=3 is within [1, 30] and the LSB oracle is well-defined");
144        assert_eq!(res.kind, FunctionKind::Balanced);
145        assert!(
146            res.all_zero_probability < 1e-5,
147            "p={}",
148            res.all_zero_probability
149        );
150    }
151
152    #[test]
153    fn balanced_half_outputs_one() {
154        // Mark exactly the upper half of a 3-qubit space (x >= 4): balanced.
155        let res = deutsch_jozsa(|x| x >= 4, 3)
156            .expect("n=3 is within [1, 30] and the threshold oracle is well-defined");
157        assert_eq!(res.kind, FunctionKind::Balanced);
158    }
159
160    #[test]
161    fn single_qubit_constant() {
162        // n=1 constant function: all-zero probability 1.
163        let res = deutsch_jozsa(|_| false, 1)
164            .expect("n=1 is within [1, 30] and the constant-zero oracle is well-defined");
165        assert_eq!(res.kind, FunctionKind::Constant);
166        assert!((res.all_zero_probability - 1.0).abs() < 1e-5);
167    }
168
169    #[test]
170    fn single_qubit_balanced() {
171        // n=1, f(x)=x is balanced (f(0)=0, f(1)=1).
172        let res = deutsch_jozsa(|x| x == 1, 1)
173            .expect("n=1 is within [1, 30] and the identity oracle is well-defined");
174        assert_eq!(res.kind, FunctionKind::Balanced);
175        assert!(res.all_zero_probability < 1e-5);
176    }
177
178    #[test]
179    fn n_qubits_zero_errors() {
180        assert!(deutsch_jozsa(|_| false, 0).is_err());
181    }
182
183    #[test]
184    fn probability_in_range_and_finite() {
185        let res = deutsch_jozsa(|x| (x & 2) != 0, 4)
186            .expect("n=4 is within [1, 30] and the bit-1 mask oracle is well-defined");
187        assert!(res.all_zero_probability.is_finite());
188        assert!(
189            (0.0..=1.0 + 1e-5).contains(&res.all_zero_probability),
190            "p={}",
191            res.all_zero_probability
192        );
193    }
194
195    #[test]
196    fn deterministic() {
197        let a = deutsch_jozsa(|x| (x & 1) == 1, 4)
198            .expect("n=4 is within [1, 30] and the LSB oracle is well-defined");
199        let b = deutsch_jozsa(|x| (x & 1) == 1, 4)
200            .expect("n=4 is within [1, 30] and the LSB oracle is well-defined");
201        assert_eq!(a.kind, b.kind);
202        assert!((a.all_zero_probability - b.all_zero_probability).abs() < 1e-7);
203    }
204}