1use crate::error::{QuantRS2Error, QuantRS2Result};
7use crate::networking::channel::{
8 fidelity_pure, measure_computational, pure_state_density, DepolarizingChannel, NoiseChannel,
9};
10use scirs2_core::ndarray::Array2;
11use scirs2_core::random::prelude::*;
12use scirs2_core::random::ChaCha20Rng;
13use scirs2_core::Complex64;
14use std::f64::consts::SQRT_2;
15
16fn seed_from_u64(seed: u64) -> [u8; 32] {
20 let mut bytes = [0u8; 32];
21 let s = seed.to_le_bytes();
22 bytes[..8].copy_from_slice(&s);
23 bytes[8..16].copy_from_slice(&s);
24 bytes[16..24].copy_from_slice(&s);
25 bytes[24..32].copy_from_slice(&s);
26 bytes
27}
28
29fn apply_noise_2q(rho4: &mut Array2<Complex64>, p: f64) {
35 if p <= 0.0 {
36 return;
37 }
38 apply_depolarizing_qubit_top(rho4, p);
39 apply_depolarizing_qubit_bot(rho4, p);
40}
41
42fn apply_depolarizing_qubit_top(rho4: &mut Array2<Complex64>, p: f64) {
44 let rho_orig = rho4.clone();
45 let scale_id = Complex64::new(1.0 - p, 0.0);
46 let scale_p = Complex64::new(p / 3.0, 0.0);
47
48 rho4.mapv_inplace(|v| v * scale_id);
50
51 let mut term = Array2::<Complex64>::zeros((4, 4));
53 for i in 0..4 {
54 for j in 0..4 {
55 term[[i, j]] = rho_orig[[i ^ 2, j ^ 2]];
56 }
57 }
58 for i in 0..4 {
59 for j in 0..4 {
60 rho4[[i, j]] += scale_p * term[[i, j]];
61 }
62 }
63
64 let phase_top = |i: usize| -> Complex64 {
66 if (i >> 1) & 1 == 0 {
67 Complex64::new(0.0, 1.0)
68 } else {
69 Complex64::new(0.0, -1.0)
70 }
71 };
72 let mut term2 = Array2::<Complex64>::zeros((4, 4));
73 for i in 0..4 {
74 for j in 0..4 {
75 term2[[i, j]] = phase_top(i) * rho_orig[[i ^ 2, j ^ 2]] * phase_top(j).conj();
76 }
77 }
78 for i in 0..4 {
79 for j in 0..4 {
80 rho4[[i, j]] += scale_p * term2[[i, j]];
81 }
82 }
83
84 let sign_top = |i: usize| -> f64 {
86 if (i >> 1) & 1 == 0 {
87 1.0
88 } else {
89 -1.0
90 }
91 };
92 let mut term3 = Array2::<Complex64>::zeros((4, 4));
93 for i in 0..4 {
94 for j in 0..4 {
95 term3[[i, j]] = Complex64::new(sign_top(i) * sign_top(j), 0.0) * rho_orig[[i, j]];
96 }
97 }
98 for i in 0..4 {
99 for j in 0..4 {
100 rho4[[i, j]] += scale_p * term3[[i, j]];
101 }
102 }
103}
104
105fn apply_depolarizing_qubit_bot(rho4: &mut Array2<Complex64>, p: f64) {
107 let rho_orig = rho4.clone();
108 let scale_id = Complex64::new(1.0 - p, 0.0);
109 let scale_p = Complex64::new(p / 3.0, 0.0);
110
111 rho4.mapv_inplace(|v| v * scale_id);
112
113 let mut term = Array2::<Complex64>::zeros((4, 4));
115 for i in 0..4 {
116 for j in 0..4 {
117 term[[i, j]] = rho_orig[[i ^ 1, j ^ 1]];
118 }
119 }
120 for i in 0..4 {
121 for j in 0..4 {
122 rho4[[i, j]] += scale_p * term[[i, j]];
123 }
124 }
125
126 let phase_bot = |i: usize| -> Complex64 {
128 if i & 1 == 0 {
129 Complex64::new(0.0, 1.0)
130 } else {
131 Complex64::new(0.0, -1.0)
132 }
133 };
134 let mut term2 = Array2::<Complex64>::zeros((4, 4));
135 for i in 0..4 {
136 for j in 0..4 {
137 term2[[i, j]] = phase_bot(i) * rho_orig[[i ^ 1, j ^ 1]] * phase_bot(j).conj();
138 }
139 }
140 for i in 0..4 {
141 for j in 0..4 {
142 rho4[[i, j]] += scale_p * term2[[i, j]];
143 }
144 }
145
146 let sign_bot = |i: usize| -> f64 {
148 if i & 1 == 0 {
149 1.0
150 } else {
151 -1.0
152 }
153 };
154 let mut term3 = Array2::<Complex64>::zeros((4, 4));
155 for i in 0..4 {
156 for j in 0..4 {
157 term3[[i, j]] = Complex64::new(sign_bot(i) * sign_bot(j), 0.0) * rho_orig[[i, j]];
158 }
159 }
160 for i in 0..4 {
161 for j in 0..4 {
162 rho4[[i, j]] += scale_p * term3[[i, j]];
163 }
164 }
165}
166
167fn bell_phi_plus() -> Array2<Complex64> {
174 let v = 0.5_f64;
175 let mut rho = Array2::<Complex64>::zeros((4, 4));
176 rho[[0, 0]] = Complex64::new(v, 0.0);
177 rho[[0, 3]] = Complex64::new(v, 0.0);
178 rho[[3, 0]] = Complex64::new(v, 0.0);
179 rho[[3, 3]] = Complex64::new(v, 0.0);
180 rho
181}
182
183fn apply_x(rho: &mut Array2<Complex64>) {
189 let r00 = rho[[0, 0]];
190 let r01 = rho[[0, 1]];
191 let r10 = rho[[1, 0]];
192 let r11 = rho[[1, 1]];
193 rho[[0, 0]] = r11;
194 rho[[0, 1]] = r10;
195 rho[[1, 0]] = r01;
196 rho[[1, 1]] = r00;
197}
198
199fn apply_z(rho: &mut Array2<Complex64>) {
201 rho[[0, 1]] = -rho[[0, 1]];
202 rho[[1, 0]] = -rho[[1, 0]];
203}
204
205fn tensor3_density(rho_psi: &Array2<Complex64>, rho_ab: &Array2<Complex64>) -> Array2<Complex64> {
213 let mut out = Array2::<Complex64>::zeros((8, 8));
214 for p in 0..2_usize {
215 for p2 in 0..2_usize {
216 for a in 0..2_usize {
217 for a2 in 0..2_usize {
218 for b in 0..2_usize {
219 for b2 in 0..2_usize {
220 let i = 4 * p + 2 * a + b;
221 let j = 4 * p2 + 2 * a2 + b2;
222 out[[i, j]] = rho_psi[[p, p2]] * rho_ab[[2 * a + b, 2 * a2 + b2]];
223 }
224 }
225 }
226 }
227 }
228 }
229 out
230}
231
232fn apply_bell_circuit_8x8(rho8: Array2<Complex64>) -> Array2<Complex64> {
234 let cnot_perm = |i: usize| -> usize {
238 let psi_bit = (i >> 2) & 1;
239 if psi_bit == 1 {
240 i ^ 2
241 } else {
242 i
243 }
244 };
245 let mut after_cnot = Array2::<Complex64>::zeros((8, 8));
246 for i in 0..8 {
247 for j in 0..8 {
248 after_cnot[[cnot_perm(i), cnot_perm(j)]] = rho8[[i, j]];
249 }
250 }
251
252 let h_val = 1.0 / SQRT_2;
255 let mut after_h = Array2::<Complex64>::zeros((8, 8));
256 for i in 0..8 {
257 let pi = (i >> 2) & 1;
258 for j in 0..8 {
259 let pj = (j >> 2) & 1;
260 for pi2 in 0..2_usize {
261 for pj2 in 0..2_usize {
262 let phase_i: f64 = if pi == 1 && pi2 == 1 { -1.0 } else { 1.0 };
263 let phase_j: f64 = if pj == 1 && pj2 == 1 { -1.0 } else { 1.0 };
264 let i2 = (i & 3) | (pi2 << 2);
265 let j2 = (j & 3) | (pj2 << 2);
266 after_h[[i2, j2]] +=
267 Complex64::new(h_val * h_val * phase_i * phase_j, 0.0) * after_cnot[[i, j]];
268 }
269 }
270 }
271 }
272 after_h
273}
274
275fn prob_qubit_0_in_8x8(rho8: &Array2<Complex64>) -> f64 {
277 (0..4_usize)
278 .map(|i| rho8[[i, i]].re)
279 .sum::<f64>()
280 .clamp(0.0, 1.0)
281}
282
283fn project_qubit_2_in_8x8(rho8: &Array2<Complex64>, m: bool) -> Array2<Complex64> {
285 let start = if m { 4 } else { 0 };
286 let end = start + 4;
287 let prob: f64 = (start..end)
288 .map(|i| rho8[[i, i]].re)
289 .sum::<f64>()
290 .max(1e-15);
291 let mut out = Array2::<Complex64>::zeros((8, 8));
292 for i in start..end {
293 for j in start..end {
294 out[[i, j]] = rho8[[i, j]] / Complex64::new(prob, 0.0);
295 }
296 }
297 out
298}
299
300fn prob_qubit_1_in_8x8(rho8: &Array2<Complex64>) -> f64 {
302 (0..8_usize)
303 .filter(|&i| (i & 2) == 0)
304 .map(|i| rho8[[i, i]].re)
305 .sum::<f64>()
306 .clamp(0.0, 1.0)
307}
308
309fn project_qubit_1_in_8x8(rho8: &Array2<Complex64>, m: bool) -> Array2<Complex64> {
311 let bit_val = if m { 2 } else { 0 };
312 let prob: f64 = (0..8_usize)
313 .filter(|&i| (i & 2) == bit_val)
314 .map(|i| rho8[[i, i]].re)
315 .sum::<f64>()
316 .max(1e-15);
317 let mut out = Array2::<Complex64>::zeros((8, 8));
318 for i in 0..8 {
319 if (i & 2) == bit_val {
320 for j in 0..8 {
321 if (j & 2) == bit_val {
322 out[[i, j]] = rho8[[i, j]] / Complex64::new(prob, 0.0);
323 }
324 }
325 }
326 }
327 out
328}
329
330fn partial_trace_to_qubit_b(rho8: &Array2<Complex64>) -> Array2<Complex64> {
332 let mut rho2 = Array2::<Complex64>::zeros((2, 2));
333 for b in 0..2_usize {
334 for b2 in 0..2_usize {
335 for psi in 0..2_usize {
336 for a in 0..2_usize {
337 let i = 4 * psi + 2 * a + b;
338 let j = 4 * psi + 2 * a + b2;
339 rho2[[b, b2]] += rho8[[i, j]];
340 }
341 }
342 }
343 }
344 rho2
345}
346
347fn bell_measure_and_correct(
349 psi: &[Complex64; 2],
350 rho_ab: &Array2<Complex64>,
351 rng: &mut ChaCha20Rng,
352) -> (bool, bool, Array2<Complex64>) {
353 let rho_in = pure_state_density(psi);
354 let rho8 = tensor3_density(&rho_in, rho_ab);
355 let rho8_after = apply_bell_circuit_8x8(rho8);
356
357 let p0_psi = prob_qubit_0_in_8x8(&rho8_after);
359 let r0: f64 = rng.random();
360 let m0 = r0 >= p0_psi;
361 let rho8_m0 = project_qubit_2_in_8x8(&rho8_after, m0);
362
363 let p0_a = prob_qubit_1_in_8x8(&rho8_m0);
365 let r1: f64 = rng.random();
366 let m1 = r1 >= p0_a;
367 let rho8_m1 = project_qubit_1_in_8x8(&rho8_m0, m1);
368
369 let rho_b = partial_trace_to_qubit_b(&rho8_m1);
370 (m0, m1, rho_b)
371}
372
373#[derive(Debug, Clone)]
379pub struct TeleportationProtocol {
380 pub noise: f64,
382 pub rng_seed: u64,
384}
385
386#[derive(Debug, Clone)]
388pub struct TeleportationResult {
389 pub fidelity: f64,
391 pub correction_bits: (bool, bool),
393}
394
395impl TeleportationProtocol {
396 pub fn new(noise: f64, rng_seed: u64) -> Self {
398 Self {
399 noise: noise.clamp(0.0, 1.0),
400 rng_seed,
401 }
402 }
403
404 pub fn teleport(&self, state: [Complex64; 2]) -> QuantRS2Result<TeleportationResult> {
408 let norm_sq = state[0].norm_sqr() + state[1].norm_sqr();
409 if (norm_sq - 1.0).abs() > 0.05 {
410 return Err(QuantRS2Error::InvalidInput(
411 "Input state must be normalised".to_string(),
412 ));
413 }
414
415 let mut rng = ChaCha20Rng::from_seed(seed_from_u64(self.rng_seed));
416
417 let mut rho_ab = bell_phi_plus();
419 apply_noise_2q(&mut rho_ab, self.noise);
420
421 let (m0, m1, mut rho_b) = bell_measure_and_correct(&state, &rho_ab, &mut rng);
423
424 if m1 {
425 apply_x(&mut rho_b);
426 }
427 if m0 {
428 apply_z(&mut rho_b);
429 }
430
431 let fidelity = fidelity_pure(&state, &rho_b);
432
433 Ok(TeleportationResult {
434 fidelity,
435 correction_bits: (m0, m1),
436 })
437 }
438}
439
440#[derive(Debug, Clone)]
449pub struct EntanglementSwapping {
450 pub n_hops: usize,
452 pub noise_per_link: f64,
454 pub rng_seed: u64,
456}
457
458#[derive(Debug, Clone)]
460pub struct SwappingResult {
461 pub end_to_end_fidelity: f64,
463}
464
465impl EntanglementSwapping {
466 pub fn new(n_hops: usize, noise_per_link: f64, rng_seed: u64) -> QuantRS2Result<Self> {
468 if n_hops == 0 {
469 return Err(QuantRS2Error::InvalidInput(
470 "n_hops must be at least 1".to_string(),
471 ));
472 }
473 Ok(Self {
474 n_hops,
475 noise_per_link: noise_per_link.clamp(0.0, 1.0),
476 rng_seed,
477 })
478 }
479
480 pub fn run(&self) -> QuantRS2Result<SwappingResult> {
482 let mut rng = ChaCha20Rng::from_seed(seed_from_u64(self.rng_seed));
483
484 let mut links: Vec<Array2<Complex64>> = (0..self.n_hops)
486 .map(|_| {
487 let mut rho = bell_phi_plus();
488 apply_noise_2q(&mut rho, self.noise_per_link);
489 rho
490 })
491 .collect();
492
493 while links.len() > 1 {
495 let rho_left = links.remove(0);
496 let rho_right = links.remove(0);
497 let rho_ac = bell_swap_4qubit(&rho_left, &rho_right, &mut rng);
498 links.insert(0, rho_ac);
499 }
500
501 let rho_ac = &links[0];
502 let fidelity = bell_state_fidelity(rho_ac);
503
504 Ok(SwappingResult {
505 end_to_end_fidelity: fidelity,
506 })
507 }
508}
509
510fn tensor_product_4qubit(
517 rho_left: &Array2<Complex64>,
518 rho_right: &Array2<Complex64>,
519) -> Array2<Complex64> {
520 let mut out = Array2::<Complex64>::zeros((16, 16));
521 for ab in 0..4 {
522 for ab2 in 0..4 {
523 for cd in 0..4 {
524 for cd2 in 0..4 {
525 let i = 4 * ab + cd;
526 let j = 4 * ab2 + cd2;
527 out[[i, j]] = rho_left[[ab, ab2]] * rho_right[[cd, cd2]];
528 }
529 }
530 }
531 }
532 out
533}
534
535fn apply_bell_circuit_on_middle_qubits(rho: Array2<Complex64>) -> Array2<Complex64> {
538 let cnot = |i: usize| -> usize {
540 let bit2 = (i >> 2) & 1;
541 if bit2 == 1 {
542 i ^ 2
543 } else {
544 i
545 }
546 };
547 let mut after_cnot = Array2::<Complex64>::zeros((16, 16));
548 for i in 0..16 {
549 for j in 0..16 {
550 after_cnot[[cnot(i), cnot(j)]] = rho[[i, j]];
551 }
552 }
553
554 let h = 1.0 / SQRT_2;
556 let mut after_h = Array2::<Complex64>::zeros((16, 16));
557 for i in 0..16 {
558 let pi = (i >> 2) & 1;
559 for j in 0..16 {
560 let pj = (j >> 2) & 1;
561 for pi2 in 0..2_usize {
562 for pj2 in 0..2_usize {
563 let phi: f64 = if pi == 1 && pi2 == 1 { -1.0 } else { 1.0 };
564 let phj: f64 = if pj == 1 && pj2 == 1 { -1.0 } else { 1.0 };
565 let i2 = (i & !4) | (pi2 << 2);
566 let j2 = (j & !4) | (pj2 << 2);
567 after_h[[i2, j2]] +=
568 Complex64::new(h * h * phi * phj, 0.0) * after_cnot[[i, j]];
569 }
570 }
571 }
572 }
573 after_h
574}
575
576fn prob_qubit_k_16(rho16: &Array2<Complex64>, k: usize) -> f64 {
578 (0..16_usize)
579 .filter(|&i| (i >> k) & 1 == 0)
580 .map(|i| rho16[[i, i]].re)
581 .sum::<f64>()
582 .clamp(0.0, 1.0)
583}
584
585fn project_qubit_k_16(rho16: &Array2<Complex64>, k: usize, m: bool) -> Array2<Complex64> {
587 let bit_val = if m { 1 << k } else { 0 };
588 let mask = 1 << k;
589 let prob: f64 = (0..16_usize)
590 .filter(|&i| (i & mask) == bit_val)
591 .map(|i| rho16[[i, i]].re)
592 .sum::<f64>()
593 .max(1e-15);
594 let mut out = Array2::<Complex64>::zeros((16, 16));
595 for i in 0..16 {
596 if (i & mask) == bit_val {
597 for j in 0..16 {
598 if (j & mask) == bit_val {
599 out[[i, j]] = rho16[[i, j]] / Complex64::new(prob, 0.0);
600 }
601 }
602 }
603 }
604 out
605}
606
607fn partial_trace_middle_qubits(rho16: &Array2<Complex64>) -> Array2<Complex64> {
610 let mut out = Array2::<Complex64>::zeros((4, 4));
611 for a in 0..2_usize {
612 for c in 0..2_usize {
613 for a2 in 0..2_usize {
614 for c2 in 0..2_usize {
615 let out_i = 2 * a + c;
616 let out_j = 2 * a2 + c2;
617 for b in 0..2_usize {
618 for d in 0..2_usize {
619 let i = (a << 3) | (b << 2) | (d << 1) | c;
620 let j = (a2 << 3) | (b << 2) | (d << 1) | c2;
621 out[[out_i, out_j]] += rho16[[i, j]];
622 }
623 }
624 }
625 }
626 }
627 }
628 out
629}
630
631fn apply_x_bot(rho4: &mut Array2<Complex64>) {
633 let orig = rho4.clone();
634 for i in 0..4 {
635 for j in 0..4 {
636 rho4[[i, j]] = orig[[i ^ 1, j ^ 1]];
637 }
638 }
639}
640
641fn apply_z_bot(rho4: &mut Array2<Complex64>) {
643 let sign = |i: usize| -> f64 {
644 if i & 1 == 0 {
645 1.0
646 } else {
647 -1.0
648 }
649 };
650 for i in 0..4 {
651 for j in 0..4 {
652 rho4[[i, j]] *= Complex64::new(sign(i) * sign(j), 0.0);
653 }
654 }
655}
656
657fn bell_swap_4qubit(
659 rho_left: &Array2<Complex64>,
660 rho_right: &Array2<Complex64>,
661 rng: &mut ChaCha20Rng,
662) -> Array2<Complex64> {
663 let rho16 = tensor_product_4qubit(rho_left, rho_right);
664 let rho16_bell = apply_bell_circuit_on_middle_qubits(rho16);
665
666 let p0_bl = prob_qubit_k_16(&rho16_bell, 2);
668 let r_bl: f64 = rng.random();
669 let m_bl = r_bl >= p0_bl;
670 let rho16_mbl = project_qubit_k_16(&rho16_bell, 2, m_bl);
671
672 let p0_br = prob_qubit_k_16(&rho16_mbl, 1);
674 let r_br: f64 = rng.random();
675 let m_br = r_br >= p0_br;
676 let rho16_mbr = project_qubit_k_16(&rho16_mbl, 1, m_br);
677
678 let mut rho_ac = partial_trace_middle_qubits(&rho16_mbr);
679
680 if m_br {
682 apply_x_bot(&mut rho_ac);
683 }
684 if m_bl {
685 apply_z_bot(&mut rho_ac);
686 }
687
688 rho_ac
689}
690
691pub fn bell_state_fidelity(rho4: &Array2<Complex64>) -> f64 {
694 let f = 0.5 * (rho4[[0, 0]].re + rho4[[0, 3]].re + rho4[[3, 0]].re + rho4[[3, 3]].re);
695 f.clamp(0.0, 1.0)
696}
697
698#[cfg(test)]
703mod tests {
704 use super::*;
705
706 fn normalised_state(alpha: Complex64, beta: Complex64) -> [Complex64; 2] {
707 let n = (alpha.norm_sqr() + beta.norm_sqr()).sqrt();
708 [alpha / n, beta / n]
709 }
710
711 #[test]
712 fn teleportation_no_noise_fidelity_one() {
713 let psi = normalised_state(
714 Complex64::new(1.0 / SQRT_2, 0.0),
715 Complex64::new(0.0, 1.0 / SQRT_2),
716 );
717 let proto = TeleportationProtocol::new(0.0, 42);
718 let result = proto.teleport(psi).expect("teleport");
719 assert!(
720 result.fidelity > 0.98,
721 "expected fidelity ≈ 1, got {}",
722 result.fidelity
723 );
724 }
725
726 #[test]
727 fn teleportation_noisy_fidelity_decreases() {
728 let psi = normalised_state(Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0));
729 let proto_clean = TeleportationProtocol::new(0.0, 42);
730 let proto_noisy = TeleportationProtocol::new(0.3, 42);
731 let f_clean = proto_clean.teleport(psi).expect("teleport").fidelity;
732 let f_noisy = proto_noisy.teleport(psi).expect("teleport").fidelity;
733 assert!(
734 f_clean > f_noisy,
735 "clean fidelity ({}) should exceed noisy ({})",
736 f_clean,
737 f_noisy
738 );
739 }
740
741 #[test]
742 fn entanglement_swapping_single_hop_no_noise() {
743 let swap = EntanglementSwapping::new(1, 0.0, 42).expect("create");
744 let result = swap.run().expect("run");
745 assert!(
746 result.end_to_end_fidelity > 0.95,
747 "expected high fidelity for 1 hop noiseless, got {}",
748 result.end_to_end_fidelity
749 );
750 }
751
752 #[test]
753 fn entanglement_swapping_n_hops_degrades() {
754 let swap1 = EntanglementSwapping::new(1, 0.05, 42).expect("create");
755 let swap3 = EntanglementSwapping::new(3, 0.05, 42).expect("create");
756 let f1 = swap1.run().expect("run").end_to_end_fidelity;
757 let f3 = swap3.run().expect("run").end_to_end_fidelity;
758 assert!(
759 f1 > f3,
760 "fidelity should degrade with more hops: 1-hop={}, 3-hop={}",
761 f1,
762 f3
763 );
764 }
765}