Skip to main content

oxicuda_quantum/algorithm/
superdense.rs

1//! Superdense coding.
2//!
3//! Superdense coding is the dual of teleportation: it transmits **two** classical
4//! bits by sending a **single** qubit, given a pre-shared Bell pair. Alice
5//! encodes the two-bit message `(b0, b1)` into her half of the pair using one of
6//! four Pauli operations, sends that one qubit to Bob, and Bob recovers both bits
7//! by a Bell-basis measurement on the (now reunited) two qubits.
8//!
9//! Qubit layout (little-endian, two qubits):
10//!
11//! * **qubit 0** — Alice's half of the Bell pair (the qubit she physically sends).
12//! * **qubit 1** — Bob's half of the Bell pair.
13//!
14//! Protocol:
15//!
16//! 1. Share the Bell pair `|Φ⁺⟩ = (|00⟩ + |11⟩)/√2` via `H₀ · CNOT₀→₁`.
17//! 2. Alice's encoding on qubit 0, indexed by `(b0, b1)`:
18//!    `(0,0) → I`, `(0,1) → X`, `(1,0) → Z`, `(1,1) → ZX` (i.e. `iY` up to phase),
19//!    mapping `|Φ⁺⟩` onto the four orthogonal Bell states.
20//! 3. Bob decodes with the inverse Bell circuit `CNOT₀→₁ · H₀`, then reads the
21//!    two computational-basis bits, recovering `(b0, b1)` deterministically.
22
23use crate::error::QuantumResult;
24use crate::gates::controlled::apply_cnot;
25use crate::gates::hadamard::gate_h;
26use crate::gates::pauli::{gate_x, gate_z};
27use crate::statevec::apply_1q::apply_1q_inplace;
28use crate::statevec::state::StateVector;
29
30/// Outcome of [`superdense_decode`].
31#[derive(Debug, Clone)]
32pub struct SuperdenseResult {
33    /// The two decoded classical bits `(b0, b1)`.
34    pub decoded: (bool, bool),
35    /// Probability mass on the decoded computational-basis string (≈ 1).
36    pub probability: f32,
37}
38
39/// Run the full superdense-coding round-trip for the message `(b0, b1)`.
40///
41/// Prepares a Bell pair, applies Alice's Pauli encoding for the requested bits,
42/// then applies Bob's decoder and reads out both qubits via deterministic
43/// argmax. The decoded bits equal the input `(b0, b1)` exactly.
44///
45/// # Errors
46/// Propagates gate-application errors (none occur for the fixed 2-qubit circuit).
47pub fn superdense_decode(b0: bool, b1: bool) -> QuantumResult<SuperdenseResult> {
48    // Step 1: Bell pair |Φ⁺⟩ on qubits 0 and 1.
49    let mut sv = StateVector::new_zero_state(2)?;
50    apply_1q_inplace(&mut sv, 0, &gate_h())?;
51    apply_cnot(&mut sv, 0, 1)?;
52
53    // Step 2: Alice's encoding on qubit 0. Apply X for b1, Z for b0.
54    // Order ZX (X first then Z) maps to the standard Bell-state assignment.
55    if b1 {
56        apply_1q_inplace(&mut sv, 0, &gate_x())?;
57    }
58    if b0 {
59        apply_1q_inplace(&mut sv, 0, &gate_z())?;
60    }
61
62    // Step 3: Bob's decoder — inverse of the Bell-pair circuit.
63    apply_cnot(&mut sv, 0, 1)?;
64    apply_1q_inplace(&mut sv, 0, &gate_h())?;
65
66    // Deterministic argmax readout of the 2-qubit register.
67    let mut best_idx = 0usize;
68    let mut best = f32::NEG_INFINITY;
69    for (i, a) in sv.amps.iter().enumerate() {
70        let p = a.norm_sqr();
71        if p > best {
72            best = p;
73            best_idx = i;
74        }
75    }
76
77    // Little-endian: qubit 0 holds the first decoded bit, qubit 1 the second.
78    let decoded_b0 = (best_idx & 1) != 0;
79    let decoded_b1 = (best_idx & 2) != 0;
80
81    Ok(SuperdenseResult {
82        decoded: (decoded_b0, decoded_b1),
83        probability: best,
84    })
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn decodes_zero_zero() {
93        let res = superdense_decode(false, false)
94            .expect("superdense decode of (false, false) should succeed");
95        assert_eq!(res.decoded, (false, false));
96        assert!(res.probability > 0.999, "p={}", res.probability);
97    }
98
99    #[test]
100    fn decodes_zero_one() {
101        let res = superdense_decode(false, true)
102            .expect("superdense decode of (false, true) should succeed");
103        assert_eq!(res.decoded, (false, true));
104        assert!(res.probability > 0.999, "p={}", res.probability);
105    }
106
107    #[test]
108    fn decodes_one_zero() {
109        let res = superdense_decode(true, false)
110            .expect("superdense decode of (true, false) should succeed");
111        assert_eq!(res.decoded, (true, false));
112        assert!(res.probability > 0.999, "p={}", res.probability);
113    }
114
115    #[test]
116    fn decodes_one_one() {
117        let res = superdense_decode(true, true)
118            .expect("superdense decode of (true, true) should succeed");
119        assert_eq!(res.decoded, (true, true));
120        assert!(res.probability > 0.999, "p={}", res.probability);
121    }
122
123    #[test]
124    fn all_messages_round_trip() {
125        // Exhaustive: every two-bit message must round-trip exactly.
126        for &(b0, b1) in &[(false, false), (false, true), (true, false), (true, true)] {
127            let res = superdense_decode(b0, b1)
128                .expect("superdense decode should succeed for all two-bit messages");
129            assert_eq!(res.decoded, (b0, b1), "failed for ({b0},{b1})");
130            assert!(res.probability > 0.999, "({b0},{b1}) p={}", res.probability);
131        }
132    }
133
134    #[test]
135    fn encodings_are_orthogonal() {
136        // The four messages must decode to four *distinct* basis strings.
137        let mut seen = std::collections::HashSet::new();
138        for &(b0, b1) in &[(false, false), (false, true), (true, false), (true, true)] {
139            let res = superdense_decode(b0, b1)
140                .expect("superdense decode should succeed for orthogonality check");
141            let idx = usize::from(res.decoded.0) | (usize::from(res.decoded.1) << 1);
142            assert!(seen.insert(idx), "collision at message ({b0},{b1})");
143        }
144        assert_eq!(seen.len(), 4);
145    }
146
147    #[test]
148    fn probability_is_deterministic() {
149        let a = superdense_decode(true, false)
150            .expect("superdense decode (true, false) first call should succeed");
151        let b = superdense_decode(true, false)
152            .expect("superdense decode (true, false) second call should succeed");
153        assert_eq!(a.decoded, b.decoded);
154        assert!((a.probability - b.probability).abs() < 1e-7);
155    }
156
157    #[test]
158    fn probability_finite_and_in_range() {
159        let res = superdense_decode(true, true)
160            .expect("superdense decode of (true, true) should succeed");
161        assert!(res.probability.is_finite());
162        assert!((0.0..=1.0 + 1e-5).contains(&res.probability));
163    }
164
165    #[test]
166    fn decoded_state_is_normalized() {
167        // The decode circuit is unitary; the post-decode norm stays 1.
168        let mut sv = StateVector::new_zero_state(2).expect("valid 2-qubit zero state");
169        apply_1q_inplace(&mut sv, 0, &gate_h()).expect("H gate on qubit 0 should succeed");
170        apply_cnot(&mut sv, 0, 1).expect("CNOT on qubits 0 to 1 should succeed");
171        apply_1q_inplace(&mut sv, 0, &gate_x()).expect("X gate on qubit 0 should succeed");
172        apply_1q_inplace(&mut sv, 0, &gate_z()).expect("Z gate on qubit 0 should succeed");
173        apply_cnot(&mut sv, 0, 1).expect("CNOT on qubits 0 to 1 (decode step) should succeed");
174        apply_1q_inplace(&mut sv, 0, &gate_h())
175            .expect("H gate on qubit 0 (decode step) should succeed");
176        assert!((sv.norm_sq() - 1.0).abs() < 1e-5, "norm={}", sv.norm_sq());
177    }
178}