oxicuda_quantum/algorithm/
swap_test.rs1use num_complex::Complex;
18
19use crate::error::{QuantumError, QuantumResult};
20use crate::gates::hadamard::{gate_h, gate_sdg};
21use crate::statevec::apply_1q::{apply_1q_controlled, apply_1q_inplace};
22use crate::statevec::state::StateVector;
23
24type Complex32 = Complex<f32>;
25
26fn apply_cswap(sv: &mut StateVector, ctrl: usize, q1: usize, q2: usize) -> QuantumResult<()> {
33 let n = sv.n_qubits;
34 for &qq in &[ctrl, q1, q2] {
35 if qq >= n {
36 return Err(QuantumError::QubitIndexOutOfRange {
37 index: qq,
38 n_qubits: n,
39 });
40 }
41 }
42 if ctrl == q1 || ctrl == q2 || q1 == q2 {
43 return Err(QuantumError::InvalidParameter {
44 name: "ctrl, q1, q2 must be distinct".into(),
45 });
46 }
47 let cm = 1usize << ctrl;
48 let m1 = 1usize << q1;
49 let m2 = 1usize << q2;
50 let dim = sv.amps.len();
51 for i in 0..dim {
52 if (i & cm) != 0 && (i & m1) != 0 && (i & m2) == 0 {
54 let j = (i & !m1) | m2;
55 sv.amps.swap(i, j);
56 }
57 }
58 Ok(())
59}
60
61fn tensor_with_ancilla(psi1: &StateVector, psi2: &StateVector) -> StateVector {
65 let k = psi1.n_qubits;
66 let n = 1 + 2 * k;
67 let dim = 1usize << n;
68 let mut amps = vec![Complex32::new(0.0, 0.0); dim];
69 for (i1, &a1) in psi1.amps.iter().enumerate() {
70 for (i2, &a2) in psi2.amps.iter().enumerate() {
71 let global = (i1 << 1) | (i2 << (1 + k));
72 amps[global] = a1 * a2;
73 }
74 }
75 StateVector { amps, n_qubits: n }
76}
77
78pub fn swap_test(psi1: &StateVector, psi2: &StateVector) -> QuantumResult<f32> {
90 if psi1.n_qubits != psi2.n_qubits {
91 return Err(QuantumError::DimensionMismatch {
92 expected: psi1.n_qubits,
93 got: psi2.n_qubits,
94 });
95 }
96 let k = psi1.n_qubits;
97 if k == 0 {
98 return Err(QuantumError::InvalidQubitCount { n: 0 });
99 }
100 let n = 1 + 2 * k;
101 if n > 30 {
102 return Err(QuantumError::InvalidQubitCount { n });
103 }
104
105 let mut sv = tensor_with_ancilla(psi1, psi2);
106 apply_1q_inplace(&mut sv, 0, &gate_h())?;
107 for j in 0..k {
108 apply_cswap(&mut sv, 0, 1 + j, 1 + k + j)?;
109 }
110 apply_1q_inplace(&mut sv, 0, &gate_h())?;
111
112 let p0 = sv.measure_prob(0, false)?;
113 Ok((2.0 * p0 - 1.0).clamp(0.0, 1.0))
114}
115
116pub fn hadamard_test(
127 psi: &StateVector,
128 gate: &[[Complex32; 2]; 2],
129 imaginary: bool,
130) -> QuantumResult<f32> {
131 if psi.n_qubits != 1 {
132 return Err(QuantumError::InvalidQubitCount { n: psi.n_qubits });
133 }
134 let mut amps = vec![Complex32::new(0.0, 0.0); 4];
136 amps[0] = psi.amps[0]; amps[2] = psi.amps[1]; let mut sv = StateVector { amps, n_qubits: 2 };
139
140 apply_1q_inplace(&mut sv, 0, &gate_h())?;
141 if imaginary {
142 apply_1q_inplace(&mut sv, 0, &gate_sdg())?;
143 }
144 apply_1q_controlled(&mut sv, 0, 1, gate)?;
145 apply_1q_inplace(&mut sv, 0, &gate_h())?;
146
147 let p0 = sv.measure_prob(0, false)?;
148 Ok(2.0 * p0 - 1.0)
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::gates::controlled::apply_cnot;
155 use crate::gates::hadamard::gate_s;
156 use crate::gates::pauli::{gate_x, gate_z};
157
158 fn ket_zero(n: usize) -> StateVector {
159 StateVector::new_zero_state(n).expect("valid n-qubit zero state")
160 }
161
162 fn ket_one() -> StateVector {
163 let mut sv = StateVector::new_zero_state(1).expect("valid 1-qubit zero state");
164 apply_1q_inplace(&mut sv, 0, &gate_x()).expect("X gate applied to qubit 0");
165 sv
166 }
167
168 fn ket_plus() -> StateVector {
169 let mut sv = StateVector::new_zero_state(1).expect("valid 1-qubit zero state");
170 apply_1q_inplace(&mut sv, 0, &gate_h()).expect("H gate applied to qubit 0");
171 sv
172 }
173
174 #[test]
175 fn swap_test_identical_states_overlap_one() {
176 let a = ket_zero(1);
177 let b = ket_zero(1);
178 let ov = swap_test(&a, &b).expect("swap test on identical 1-qubit states");
179 assert!((ov - 1.0).abs() < 1e-5, "overlap={ov}");
180 }
181
182 #[test]
183 fn swap_test_orthogonal_states_overlap_zero() {
184 let a = ket_zero(1);
185 let b = ket_one();
186 let ov = swap_test(&a, &b).expect("swap test on orthogonal 1-qubit states");
187 assert!(ov.abs() < 1e-5, "overlap={ov}");
188 }
189
190 #[test]
191 fn swap_test_zero_and_plus_overlap_half() {
192 let a = ket_zero(1);
194 let b = ket_plus();
195 let ov = swap_test(&a, &b).expect("swap test on |0⟩ and |+⟩ states");
196 assert!((ov - 0.5).abs() < 1e-5, "overlap={ov}");
197 }
198
199 #[test]
200 fn swap_test_two_qubit_identical_overlap_one() {
201 let mut a = StateVector::new_zero_state(2).expect("valid 2-qubit zero state");
203 apply_1q_inplace(&mut a, 0, &gate_h()).expect("H gate applied to qubit 0");
204 apply_cnot(&mut a, 0, 1).expect("CNOT with control 0 target 1");
205 let b = a.clone();
206 let ov = swap_test(&a, &b).expect("swap test on identical 2-qubit Bell states");
207 assert!((ov - 1.0).abs() < 1e-5, "overlap={ov}");
208 }
209
210 #[test]
211 fn swap_test_two_qubit_orthogonal_overlap_zero() {
212 let a = ket_zero(2); let mut b = StateVector::new_zero_state(2).expect("valid 2-qubit zero state");
214 apply_1q_inplace(&mut b, 1, &gate_x()).expect("X gate applied to qubit 1"); let ov = swap_test(&a, &b).expect("swap test on orthogonal 2-qubit states");
216 assert!(ov.abs() < 1e-5, "overlap={ov}");
217 }
218
219 #[test]
220 fn swap_test_mismatched_sizes_error() {
221 let a = ket_zero(1);
222 let b = ket_zero(2);
223 assert!(swap_test(&a, &b).is_err());
224 }
225
226 #[test]
227 fn hadamard_test_z_on_zero_is_plus_one() {
228 let psi = ket_zero(1);
229 let re = hadamard_test(&psi, &gate_z(), false)
230 .expect("Hadamard test with Z gate on |0⟩, real part");
231 assert!((re - 1.0).abs() < 1e-5, "re={re}");
232 }
233
234 #[test]
235 fn hadamard_test_z_on_plus_is_zero() {
236 let psi = ket_plus();
237 let re = hadamard_test(&psi, &gate_z(), false)
238 .expect("Hadamard test with Z gate on |+⟩, real part");
239 assert!(re.abs() < 1e-5, "re={re}");
240 }
241
242 #[test]
243 fn hadamard_test_x_on_zero_is_zero() {
244 let psi = ket_zero(1);
246 let re = hadamard_test(&psi, &gate_x(), false)
247 .expect("Hadamard test with X gate on |0⟩, real part");
248 assert!(re.abs() < 1e-5, "re={re}");
249 }
250
251 #[test]
252 fn hadamard_test_s_on_plus_real_and_imag() {
253 let psi = ket_plus();
255 let re = hadamard_test(&psi, &gate_s(), false)
256 .expect("Hadamard test with S gate on |+⟩, real part");
257 let im = hadamard_test(&psi, &gate_s(), true)
258 .expect("Hadamard test with S gate on |+⟩, imaginary part");
259 assert!((re - 0.5).abs() < 1e-5, "re={re}");
260 assert!((im - 0.5).abs() < 1e-5, "im={im}");
261 }
262
263 #[test]
264 fn hadamard_test_requires_single_qubit() {
265 let psi = ket_zero(2);
266 assert!(hadamard_test(&psi, &gate_z(), false).is_err());
267 }
268}