Skip to main content

oxicuda_quantum/algorithm/
teleportation.rs

1//! Quantum teleportation.
2//!
3//! Teleportation transmits an unknown single-qubit state `|ψ⟩ = α|0⟩ + β|1⟩`
4//! from Alice to Bob using one pre-shared Bell pair and two classical bits, with
5//! no quantum channel for `|ψ⟩` itself. The protocol famously **destroys** the
6//! source copy (consistent with no-cloning) and reconstructs `|ψ⟩` on Bob's
7//! qubit after two Pauli corrections conditioned on Alice's measurement.
8//!
9//! Qubit layout (little-endian, three qubits total):
10//!
11//! * **qubit 0** — Alice's message qubit, prepared in `|ψ⟩`.
12//! * **qubit 1** — Alice's half of the Bell pair.
13//! * **qubit 2** — Bob's half of the Bell pair (the teleportation destination).
14//!
15//! Protocol:
16//!
17//! 1. Entangle qubits 1 and 2 into a Bell pair: `H₁ · CNOT₁→₂`.
18//! 2. Alice's Bell-basis measurement on (0,1): `CNOT₀→₁ · H₀`, then measure both.
19//! 3. Send the two classical bits `(m0, m1)` to Bob.
20//! 4. Bob corrects: apply `X` if `m1 = 1`, then `Z` if `m0 = 1`.
21//!
22//! After step 4 qubit 2 holds `|ψ⟩` exactly (up to floating-point round-off),
23//! while qubits 0 and 1 are left in a definite computational-basis state.
24
25use num_complex::Complex;
26
27use crate::error::{QuantumError, QuantumResult};
28use crate::gates::controlled::apply_cnot;
29use crate::gates::hadamard::gate_h;
30use crate::gates::pauli::{gate_x, gate_z};
31use crate::handle::LcgRng;
32use crate::midcircuit::measurement::{ClassicalRegister, apply_if, measure_and_collapse};
33use crate::statevec::apply_1q::apply_1q_inplace;
34use crate::statevec::state::StateVector;
35
36type Complex32 = Complex<f32>;
37
38/// Outcome of [`teleport`].
39#[derive(Debug, Clone)]
40pub struct TeleportResult {
41    /// Alice's two measurement bits `(m0, m1)` (qubit 0 then qubit 1).
42    pub classical_bits: (bool, bool),
43    /// Bob's reconstructed amplitudes `(⟨0|ψ'⟩, ⟨1|ψ'⟩)` on qubit 2, obtained by
44    /// projecting the post-correction three-qubit state onto Alice's measured
45    /// branch and tracing out qubits 0 and 1.
46    pub bob_amplitudes: (Complex32, Complex32),
47    /// Fidelity `|⟨ψ|ψ'⟩|²` between the input state and Bob's output.
48    pub fidelity: f32,
49}
50
51/// Teleport the single-qubit state `(alpha, beta)` from qubit 0 to qubit 2.
52///
53/// `alpha` and `beta` are the input amplitudes of `|ψ⟩ = α|0⟩ + β|1⟩`; they are
54/// renormalized internally, so any non-zero pair is accepted. `rng` drives
55/// Alice's measurement sampling. The returned [`TeleportResult`] reports the
56/// measured classical bits, Bob's reconstructed amplitudes, and the fidelity
57/// with the input (which is `≈ 1` for a correct protocol regardless of which
58/// measurement branch occurred).
59///
60/// # Errors
61/// [`QuantumError::InvalidParameter`] if both `alpha` and `beta` are (near) zero.
62pub fn teleport(
63    alpha: Complex32,
64    beta: Complex32,
65    rng: &mut LcgRng,
66) -> QuantumResult<TeleportResult> {
67    let norm = (alpha.norm_sqr() + beta.norm_sqr()).sqrt();
68    if norm < 1e-12 {
69        return Err(QuantumError::InvalidParameter {
70            name: "input state amplitudes are both zero".into(),
71        });
72    }
73    let a = alpha / norm;
74    let b = beta / norm;
75
76    // Build the 3-qubit state with qubit 0 = |ψ⟩, qubits 1,2 = |0⟩.
77    // Little-endian: index bit q corresponds to qubit q.
78    let mut amps = vec![Complex32::new(0.0, 0.0); 8];
79    amps[0b000] = a; // qubit 0 = 0
80    amps[0b001] = b; // qubit 0 = 1
81    let mut sv = StateVector { amps, n_qubits: 3 };
82
83    // Step 1: Bell pair on qubits 1 and 2.
84    apply_1q_inplace(&mut sv, 1, &gate_h())?;
85    apply_cnot(&mut sv, 1, 2)?;
86
87    // Step 2: Alice's Bell-basis measurement basis change on (0,1).
88    apply_cnot(&mut sv, 0, 1)?;
89    apply_1q_inplace(&mut sv, 0, &gate_h())?;
90
91    // Measure qubits 0 and 1, collapsing the state.
92    let mut creg = ClassicalRegister::new(2);
93    let m0 = measure_and_collapse(&mut sv, 0, rng, &mut creg, 0)?;
94    let m1 = measure_and_collapse(&mut sv, 1, rng, &mut creg, 1)?;
95
96    // Step 4: Bob's corrections — X if m1, then Z if m0.
97    apply_if(&mut sv, &creg, &[(1, true)], &gate_x(), 2)?;
98    apply_if(&mut sv, &creg, &[(0, true)], &gate_z(), 2)?;
99
100    // Extract Bob's amplitudes. After collapse qubits 0,1 are fixed to (m0,m1);
101    // the only non-zero amplitudes are at indices with those bits set.
102    let base = (usize::from(m0)) | (usize::from(m1) << 1);
103    let bob0 = sv.amps[base]; // qubit 2 = 0
104    let bob1 = sv.amps[base | (1 << 2)]; // qubit 2 = 1
105
106    // Fidelity |⟨ψ|ψ'⟩|² with the (normalized) input.
107    let overlap = a.conj() * bob0 + b.conj() * bob1;
108    let fidelity = overlap.norm_sqr();
109
110    Ok(TeleportResult {
111        classical_bits: (m0, m1),
112        bob_amplitudes: (bob0, bob1),
113        fidelity,
114    })
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use std::f32::consts::FRAC_1_SQRT_2;
121
122    #[test]
123    fn teleports_zero_state() {
124        // |ψ⟩ = |0⟩ ⇒ Bob recovers |0⟩ with fidelity 1.
125        let mut rng = LcgRng::new(1);
126        let res = teleport(Complex32::new(1.0, 0.0), Complex32::new(0.0, 0.0), &mut rng)
127            .expect("teleportation of |0⟩ state with normalized input");
128        assert!(res.fidelity > 0.999, "fidelity={}", res.fidelity);
129        assert!((res.bob_amplitudes.0.norm() - 1.0).abs() < 1e-4);
130        assert!(res.bob_amplitudes.1.norm() < 1e-4);
131    }
132
133    #[test]
134    fn teleports_one_state() {
135        // |ψ⟩ = |1⟩ ⇒ Bob recovers |1⟩.
136        let mut rng = LcgRng::new(2);
137        let res = teleport(Complex32::new(0.0, 0.0), Complex32::new(1.0, 0.0), &mut rng)
138            .expect("non-zero |1⟩ state is a valid teleportation input");
139        assert!(res.fidelity > 0.999, "fidelity={}", res.fidelity);
140        assert!(res.bob_amplitudes.0.norm() < 1e-4);
141        assert!((res.bob_amplitudes.1.norm() - 1.0).abs() < 1e-4);
142    }
143
144    #[test]
145    fn teleports_plus_state() {
146        // |ψ⟩ = |+⟩ = (|0⟩+|1⟩)/√2.
147        let mut rng = LcgRng::new(3);
148        let res = teleport(
149            Complex32::new(FRAC_1_SQRT_2, 0.0),
150            Complex32::new(FRAC_1_SQRT_2, 0.0),
151            &mut rng,
152        )
153        .expect("normalized |+⟩ state is a valid teleportation input");
154        assert!(res.fidelity > 0.999, "fidelity={}", res.fidelity);
155    }
156
157    #[test]
158    fn teleports_complex_superposition() {
159        // |ψ⟩ with a relative complex phase; fidelity must still be 1.
160        let mut rng = LcgRng::new(4);
161        let res = teleport(Complex32::new(0.6, 0.0), Complex32::new(0.0, 0.8), &mut rng)
162            .expect("non-zero complex superposition is a valid teleportation input");
163        assert!(res.fidelity > 0.999, "fidelity={}", res.fidelity);
164    }
165
166    #[test]
167    fn fidelity_high_across_many_seeds() {
168        // All four measurement branches should give fidelity ≈ 1.
169        for seed in 0..32u64 {
170            let mut rng = LcgRng::new(seed);
171            let res = teleport(Complex32::new(0.6, 0.0), Complex32::new(0.8, 0.0), &mut rng)
172                .expect("non-zero (0.6, 0.8) real state is a valid teleportation input");
173            assert!(res.fidelity > 0.99, "seed={seed} fidelity={}", res.fidelity);
174        }
175    }
176
177    #[test]
178    fn unnormalized_input_is_accepted() {
179        // Input (2, 0) renormalizes to |0⟩; teleportation still succeeds.
180        let mut rng = LcgRng::new(5);
181        let res = teleport(Complex32::new(2.0, 0.0), Complex32::new(0.0, 0.0), &mut rng)
182            .expect("unnormalized (2, 0) is non-zero and renormalized internally");
183        assert!(res.fidelity > 0.999, "fidelity={}", res.fidelity);
184    }
185
186    #[test]
187    fn zero_input_errors() {
188        let mut rng = LcgRng::new(6);
189        assert!(teleport(Complex32::new(0.0, 0.0), Complex32::new(0.0, 0.0), &mut rng).is_err());
190    }
191
192    #[test]
193    fn classical_bits_are_booleans() {
194        // Sanity: both bits are produced (no panic) and fidelity holds.
195        let mut rng = LcgRng::new(7);
196        let res = teleport(
197            Complex32::new(0.5, 0.5),
198            Complex32::new(0.5, -0.5),
199            &mut rng,
200        )
201        .expect("complex superposition with non-zero norm is a valid teleportation input");
202        let (m0, m1) = res.classical_bits;
203        // Compiler-checked booleans; just exercise them.
204        let _ = m0 & m1;
205        assert!(res.fidelity > 0.99, "fidelity={}", res.fidelity);
206    }
207
208    #[test]
209    fn bob_amplitudes_match_input_up_to_branch() {
210        // Bob's recovered amplitudes equal the input (renormalized) amplitudes.
211        let mut rng = LcgRng::new(11);
212        let a = Complex32::new(0.6, 0.0);
213        let b = Complex32::new(0.0, 0.8);
214        let res = teleport(a, b, &mut rng)
215            .expect("non-zero (0.6, 0.8i) state is a valid teleportation input");
216        assert!(
217            (res.bob_amplitudes.0 - a).norm() < 1e-4,
218            "bob0={:?}",
219            res.bob_amplitudes.0
220        );
221        assert!(
222            (res.bob_amplitudes.1 - b).norm() < 1e-4,
223            "bob1={:?}",
224            res.bob_amplitudes.1
225        );
226    }
227
228    #[test]
229    fn fidelity_is_finite_and_in_range() {
230        let mut rng = LcgRng::new(13);
231        let res = teleport(Complex32::new(0.6, 0.2), Complex32::new(0.3, 0.7), &mut rng)
232            .expect("non-zero complex state (0.6+0.2i, 0.3+0.7i) is a valid teleportation input");
233        assert!(res.fidelity.is_finite());
234        assert!((0.0..=1.0 + 1e-4).contains(&res.fidelity));
235    }
236}