Skip to main content

oxicuda_quantum/algorithm/
error_correction.rs

1//! Multi-qubit entangled-state preparation and 3-qubit quantum error correction.
2//!
3//! This module collects two closely related primitives that underpin
4//! fault-tolerant quantum computing demos:
5//!
6//! ## Entangled state preparation
7//! * [`prepare_ghz`] — the `n`-qubit Greenberger–Horne–Zeilinger state
8//!   `(|0…0⟩ + |1…1⟩)/√2`, built from one Hadamard plus a CNOT ladder.
9//! * [`prepare_w`] — the `n`-qubit W state, the equal superposition of all
10//!   single-excitation basis vectors `Σ_i |0…1_i…0⟩ / √n`, built from a cascade
11//!   of controlled `Y`-rotations.
12//!
13//! ## 3-qubit repetition codes
14//! * [`bit_flip_encode`] / [`bit_flip_correct`] — protect against a single `X`
15//!   error by majority vote over the syndrome `{Z₀Z₁, Z₁Z₂}`.
16//! * [`phase_flip_encode`] / [`phase_flip_correct`] — the Hadamard-conjugated
17//!   code protecting against a single `Z` error.
18//!
19//! All routines act on this crate's **little-endian** [`StateVector`] (qubit `i`
20//! ↦ bit `i`), so the logical qubit is encoded across physical qubits
21//! `0, 1, 2`.
22
23use crate::error::{QuantumError, QuantumResult};
24use crate::gates::controlled::apply_cnot;
25use crate::gates::hadamard::gate_h;
26use crate::gates::parametric::gate_ry;
27use crate::statevec::apply_1q::{apply_1q_controlled, apply_1q_inplace};
28use crate::statevec::state::StateVector;
29
30/// Prepare the `n`-qubit GHZ state `(|0…0⟩ + |1…1⟩)/√2`.
31///
32/// # Errors
33/// [`QuantumError::InvalidQubitCount`] if `n_qubits` is `0` or `> 30`.
34pub fn prepare_ghz(n_qubits: usize) -> QuantumResult<StateVector> {
35    if n_qubits == 0 || n_qubits > 30 {
36        return Err(QuantumError::InvalidQubitCount { n: n_qubits });
37    }
38    let mut sv = StateVector::new_zero_state(n_qubits)?;
39    apply_1q_inplace(&mut sv, 0, &gate_h())?;
40    for q in 1..n_qubits {
41        apply_cnot(&mut sv, q - 1, q)?;
42    }
43    Ok(sv)
44}
45
46/// Prepare the `n`-qubit W state `Σ_{i} |0…1_i…0⟩ / √n`.
47///
48/// Construction (Cabello 2002 cascade): qubit `0` is rotated so that the
49/// excitation either stays on qubit `0` with amplitude `1/√n` or is forwarded.
50/// Successive controlled `Y`-rotations split the remaining amplitude evenly, and
51/// CNOTs move the excitation down the chain. The net result places exactly one
52/// `|1⟩` with equal amplitude on every qubit.
53///
54/// # Errors
55/// [`QuantumError::InvalidQubitCount`] if `n_qubits` is `0` or `> 30`.
56pub fn prepare_w(n_qubits: usize) -> QuantumResult<StateVector> {
57    if n_qubits == 0 || n_qubits > 30 {
58        return Err(QuantumError::InvalidQubitCount { n: n_qubits });
59    }
60    let mut sv = StateVector::new_zero_state(n_qubits)?;
61    if n_qubits == 1 {
62        apply_1q_inplace(&mut sv, 0, &crate::gates::pauli::gate_x())?;
63        return Ok(sv);
64    }
65    // Put the excitation on qubit 0 first (X), then peel amplitude off toward
66    // higher qubits with controlled rotations.
67    apply_1q_inplace(&mut sv, 0, &crate::gates::pauli::gate_x())?;
68    // For qubit k = 0..n-2: rotate qubit k+1 conditioned on qubit k so that a
69    // fraction 1/(n-k) of the population remains on qubit k.
70    for k in 0..n_qubits - 1 {
71        let remaining = (n_qubits - k) as f32;
72        // Want P(stay on k) = 1/remaining ⇒ split angle θ with cos²(θ/2)=1/remaining.
73        let theta = 2.0 * (1.0 / remaining).sqrt().acos();
74        // Controlled-Ry(θ) with control = qubit k, target = qubit k+1.
75        apply_1q_controlled(&mut sv, k, k + 1, &gate_ry(theta))?;
76        // Move excitation: if qubit k+1 became 1, clear qubit k via CNOT(k+1 → k).
77        apply_cnot(&mut sv, k + 1, k)?;
78    }
79    Ok(sv)
80}
81
82/// Encode a single logical qubit (state `α|0⟩ + β|1⟩` on physical qubit `0`)
83/// into the 3-qubit **bit-flip** repetition code `α|000⟩ + β|111⟩`.
84///
85/// The input state vector must have exactly 3 qubits with qubits 1 and 2 in
86/// `|0⟩` (the standard ancilla initialisation). Two CNOTs copy the logical bit.
87///
88/// # Errors
89/// [`QuantumError::InvalidQubitCount`] if the state does not have 3 qubits.
90pub fn bit_flip_encode(state: &mut StateVector) -> QuantumResult<()> {
91    if state.n_qubits != 3 {
92        return Err(QuantumError::InvalidQubitCount { n: state.n_qubits });
93    }
94    apply_cnot(state, 0, 1)?;
95    apply_cnot(state, 0, 2)?;
96    Ok(())
97}
98
99/// Measure the bit-flip syndrome `(Z₀Z₁, Z₁Z₂)` *coherently* via parity over the
100/// amplitude array, returning the two parity bits. A deterministic state yields a
101/// unique syndrome; superpositions of distinct syndromes return the dominant one.
102fn bit_flip_syndrome(state: &StateVector) -> (bool, bool) {
103    // s0 = parity(bit0 XOR bit1), s1 = parity(bit1 XOR bit2), weighted by prob.
104    let mut p_s0 = [0.0f32; 2];
105    let mut p_s1 = [0.0f32; 2];
106    for (idx, amp) in state.amps.iter().enumerate() {
107        let pr = amp.norm_sqr();
108        if pr == 0.0 {
109            continue;
110        }
111        let b0 = idx & 1;
112        let b1 = (idx >> 1) & 1;
113        let b2 = (idx >> 2) & 1;
114        p_s0[b0 ^ b1] += pr;
115        p_s1[b1 ^ b2] += pr;
116    }
117    (p_s0[1] > p_s0[0], p_s1[1] > p_s1[0])
118}
119
120/// Correct a single bit-flip error using the syndrome majority vote and return
121/// the index of the qubit that was corrected (`None` if no error detected).
122///
123/// Syndrome decoding:
124/// * `(0,0)` → no error.
125/// * `(1,0)` → error on qubit 0.
126/// * `(1,1)` → error on qubit 1.
127/// * `(0,1)` → error on qubit 2.
128///
129/// # Errors
130/// [`QuantumError::InvalidQubitCount`] if the state does not have 3 qubits.
131pub fn bit_flip_correct(state: &mut StateVector) -> QuantumResult<Option<usize>> {
132    if state.n_qubits != 3 {
133        return Err(QuantumError::InvalidQubitCount { n: state.n_qubits });
134    }
135    let (s0, s1) = bit_flip_syndrome(state);
136    let correct_qubit = match (s0, s1) {
137        (false, false) => None,
138        (true, false) => Some(0),
139        (true, true) => Some(1),
140        (false, true) => Some(2),
141    };
142    if let Some(q) = correct_qubit {
143        apply_1q_inplace(state, q, &crate::gates::pauli::gate_x())?;
144    }
145    Ok(correct_qubit)
146}
147
148/// Encode a single logical qubit into the 3-qubit **phase-flip** repetition code
149/// `α|+++⟩ + β|−−−⟩`.
150///
151/// This is the bit-flip code conjugated by Hadamards: encode in the
152/// computational basis, then map each qubit into the `±` (Hadamard) basis.
153///
154/// # Errors
155/// [`QuantumError::InvalidQubitCount`] if the state does not have 3 qubits.
156pub fn phase_flip_encode(state: &mut StateVector) -> QuantumResult<()> {
157    bit_flip_encode(state)?;
158    for q in 0..3 {
159        apply_1q_inplace(state, q, &gate_h())?;
160    }
161    Ok(())
162}
163
164/// Correct a single phase-flip error by rotating back to the computational
165/// basis, running the bit-flip decoder, and returning to the `±` basis.
166///
167/// Returns the index of the corrected qubit (`None` if no error detected).
168///
169/// # Errors
170/// [`QuantumError::InvalidQubitCount`] if the state does not have 3 qubits.
171pub fn phase_flip_correct(state: &mut StateVector) -> QuantumResult<Option<usize>> {
172    if state.n_qubits != 3 {
173        return Err(QuantumError::InvalidQubitCount { n: state.n_qubits });
174    }
175    // Map ± basis → computational basis.
176    for q in 0..3 {
177        apply_1q_inplace(state, q, &gate_h())?;
178    }
179    let corrected = bit_flip_correct(state)?;
180    // Map back to ± basis.
181    for q in 0..3 {
182        apply_1q_inplace(state, q, &gate_h())?;
183    }
184    Ok(corrected)
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::gates::pauli::{gate_x, gate_z};
191
192    const INV_SQRT2: f32 = std::f32::consts::FRAC_1_SQRT_2;
193
194    #[test]
195    fn ghz_two_qubit_is_bell() {
196        let sv = prepare_ghz(2).expect("valid 2-qubit GHZ state");
197        assert!((sv.amps[0].re - INV_SQRT2).abs() < 1e-5);
198        assert!((sv.amps[3].re - INV_SQRT2).abs() < 1e-5);
199        assert!(sv.amps[1].norm() < 1e-5);
200        assert!(sv.amps[2].norm() < 1e-5);
201    }
202
203    #[test]
204    fn ghz_three_qubit_amplitudes() {
205        let sv = prepare_ghz(3).expect("valid 3-qubit GHZ state");
206        assert!((sv.amps[0].re - INV_SQRT2).abs() < 1e-5, "|000>");
207        assert!((sv.amps[7].re - INV_SQRT2).abs() < 1e-5, "|111>");
208        // All other amplitudes vanish.
209        for i in 1..7 {
210            assert!(sv.amps[i].norm() < 1e-5, "amp[{i}]={:?}", sv.amps[i]);
211        }
212    }
213
214    #[test]
215    fn ghz_norm_preserved() {
216        for n in 1..=5 {
217            let sv = prepare_ghz(n).expect("valid n-qubit GHZ state");
218            assert!((sv.norm_sq() - 1.0).abs() < 1e-4, "n={n}");
219        }
220    }
221
222    #[test]
223    fn ghz_invalid_qubit_count() {
224        assert!(prepare_ghz(0).is_err());
225        assert!(prepare_ghz(31).is_err());
226    }
227
228    #[test]
229    fn w_state_single_excitation_amplitudes() {
230        let sv = prepare_w(3).expect("valid 3-qubit W state");
231        let amp = (1.0f32 / 3.0).sqrt();
232        // |001>, |010>, |100> at indices 1,2,4.
233        for &i in &[1usize, 2, 4] {
234            assert!(
235                (sv.amps[i].norm() - amp).abs() < 1e-4,
236                "amp[{i}]={:?}",
237                sv.amps[i]
238            );
239        }
240        // Zero / multi excitation amplitudes vanish.
241        for &i in &[0usize, 3, 5, 6, 7] {
242            assert!(sv.amps[i].norm() < 1e-4, "amp[{i}]={:?}", sv.amps[i]);
243        }
244    }
245
246    #[test]
247    fn w_state_norm_is_one() {
248        for n in 1..=5 {
249            let sv = prepare_w(n).expect("valid n-qubit W state");
250            assert!((sv.norm_sq() - 1.0).abs() < 1e-4, "n={n}");
251        }
252    }
253
254    #[test]
255    fn w_state_exactly_one_excitation() {
256        // Every populated basis state has Hamming weight 1.
257        let sv = prepare_w(4).expect("valid 4-qubit W state");
258        for (idx, amp) in sv.amps.iter().enumerate() {
259            if amp.norm() > 1e-4 {
260                assert_eq!(idx.count_ones(), 1, "idx={idx:b} has weight ≠ 1");
261            }
262        }
263    }
264
265    #[test]
266    fn w_state_uniform_marginals() {
267        // Each qubit has equal probability 1/n of being |1>.
268        let n = 4;
269        let sv = prepare_w(n).expect("valid 4-qubit W state");
270        for q in 0..n {
271            let p1 = sv
272                .measure_prob(q, true)
273                .expect("valid qubit index for probability measurement");
274            assert!((p1 - 1.0 / n as f32).abs() < 1e-4, "q={q} p1={p1}");
275        }
276    }
277
278    #[test]
279    fn bit_flip_encode_logical_one() {
280        // |1> on qubit 0 → |111>.
281        let mut sv = StateVector::new_zero_state(3).expect("valid 3-qubit zero state");
282        apply_1q_inplace(&mut sv, 0, &gate_x()).expect("X gate on qubit 0 succeeds");
283        bit_flip_encode(&mut sv).expect("bit-flip encoding succeeds on 3-qubit state");
284        assert!((sv.amps[7].norm() - 1.0).abs() < 1e-5, "expected |111>");
285    }
286
287    #[test]
288    fn bit_flip_corrects_single_error_on_each_qubit() {
289        for err_q in 0..3 {
290            // Encode logical |1> = |111>.
291            let mut sv = StateVector::new_zero_state(3).expect("valid 3-qubit zero state");
292            apply_1q_inplace(&mut sv, 0, &gate_x()).expect("X gate on qubit 0 succeeds");
293            bit_flip_encode(&mut sv).expect("bit-flip encoding succeeds on 3-qubit state");
294            // Inject X error.
295            apply_1q_inplace(&mut sv, err_q, &gate_x())
296                .expect("X error injection on target qubit succeeds");
297            // Correct.
298            let corrected = bit_flip_correct(&mut sv).expect("bit-flip correction succeeds");
299            assert_eq!(corrected, Some(err_q), "error on {err_q}");
300            // Back to |111>.
301            assert!((sv.amps[7].norm() - 1.0).abs() < 1e-5, "err_q={err_q}");
302        }
303    }
304
305    #[test]
306    fn bit_flip_no_error_detected() {
307        let mut sv = StateVector::new_zero_state(3).expect("valid 3-qubit zero state");
308        bit_flip_encode(&mut sv).expect("bit-flip encoding of |000> succeeds"); // |000>
309        let corrected =
310            bit_flip_correct(&mut sv).expect("bit-flip correction on clean state succeeds");
311        assert_eq!(corrected, None);
312        assert!((sv.amps[0].norm() - 1.0).abs() < 1e-5);
313    }
314
315    #[test]
316    fn bit_flip_preserves_superposition_after_correction() {
317        // Logical |+_L> = (|000> + |111>)/√2, error on qubit 1, then correct.
318        let mut sv = StateVector::new_zero_state(3).expect("valid 3-qubit zero state");
319        apply_1q_inplace(&mut sv, 0, &gate_h()).expect("Hadamard gate on qubit 0 succeeds");
320        bit_flip_encode(&mut sv).expect("bit-flip encoding of superposition succeeds");
321        let before0 = sv.amps[0];
322        let before7 = sv.amps[7];
323        apply_1q_inplace(&mut sv, 1, &gate_x()).expect("X error injection on qubit 1 succeeds");
324        let corrected =
325            bit_flip_correct(&mut sv).expect("bit-flip correction restores superposition");
326        assert_eq!(corrected, Some(1));
327        assert!((sv.amps[0] - before0).norm() < 1e-5);
328        assert!((sv.amps[7] - before7).norm() < 1e-5);
329    }
330
331    #[test]
332    fn bit_flip_wrong_qubit_count_errors() {
333        let mut sv = StateVector::new_zero_state(2).expect("valid 2-qubit zero state");
334        assert!(bit_flip_encode(&mut sv).is_err());
335        assert!(bit_flip_correct(&mut sv).is_err());
336    }
337
338    #[test]
339    fn phase_flip_corrects_single_z_error() {
340        for err_q in 0..3 {
341            // Encode logical |1>.
342            let mut sv = StateVector::new_zero_state(3).expect("valid 3-qubit zero state");
343            apply_1q_inplace(&mut sv, 0, &gate_x()).expect("X gate on qubit 0 succeeds");
344            phase_flip_encode(&mut sv).expect("phase-flip encoding succeeds on 3-qubit state");
345            let encoded = sv.clone();
346            // Inject Z error.
347            apply_1q_inplace(&mut sv, err_q, &gate_z())
348                .expect("Z error injection on target qubit succeeds");
349            // Correct.
350            let corrected = phase_flip_correct(&mut sv).expect("phase-flip correction succeeds");
351            assert_eq!(corrected, Some(err_q), "z-error on {err_q}");
352            // State restored to the encoded logical |1>.
353            for i in 0..8 {
354                assert!(
355                    (sv.amps[i] - encoded.amps[i]).norm() < 1e-4,
356                    "err_q={err_q} amp[{i}]"
357                );
358            }
359        }
360    }
361
362    #[test]
363    fn phase_flip_no_error_detected() {
364        let mut sv = StateVector::new_zero_state(3).expect("valid 3-qubit zero state");
365        phase_flip_encode(&mut sv).expect("phase-flip encoding succeeds");
366        let corrected =
367            phase_flip_correct(&mut sv).expect("phase-flip correction on clean state succeeds");
368        assert_eq!(corrected, None);
369    }
370}