paulistrings/channel/
identity.rs

1//! `IdentityChannel` — no-op channel that emits its input unchanged.
2//!
3//! Useful as a sanity scaffold for the sort-merge engine (§5) and as a
4//! neutral element when composing circuits.
5
6use super::{Channel, OutputBuffer};
7use num_complex::Complex64;
8
9/// A channel that maps every input Pauli to itself with the same coefficient.
10///
11/// `support()` is empty, so the engine's bucket layout collapses to a single
12/// bucket and the only effect is to copy the input through. `max_fanout()`
13/// is `1`.
14#[derive(Clone, Copy, Debug, Default)]
15pub struct IdentityChannel {
16    support: [u32; 0],
17}
18
19impl IdentityChannel {
20    /// Construct an identity channel.
21    pub const fn new() -> Self {
22        Self { support: [] }
23    }
24}
25
26impl<const W: usize> Channel<W> for IdentityChannel {
27    #[inline]
28    fn max_fanout(&self) -> usize {
29        1
30    }
31
32    #[inline]
33    fn support(&self) -> &[u32] {
34        &self.support
35    }
36
37    #[inline]
38    fn apply(
39        &self,
40        input_x: &[u64; W],
41        input_z: &[u64; W],
42        coeff: Complex64,
43        out: &mut OutputBuffer<'_, W>,
44    ) {
45        out.push(*input_x, *input_z, coeff);
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::pauli_string::PauliString;
53
54    #[test]
55    fn max_fanout_is_one() {
56        let id = IdentityChannel::new();
57        assert_eq!(<IdentityChannel as Channel<1>>::max_fanout(&id), 1);
58        assert_eq!(<IdentityChannel as Channel<2>>::max_fanout(&id), 1);
59    }
60
61    #[test]
62    fn support_is_empty() {
63        let id = IdentityChannel::new();
64        assert_eq!(<IdentityChannel as Channel<1>>::support(&id), &[] as &[u32]);
65        assert_eq!(<IdentityChannel as Channel<2>>::support(&id), &[] as &[u32]);
66    }
67
68    #[test]
69    fn apply_emits_input_unchanged_w1() {
70        let id = IdentityChannel::new();
71        // Build XYZ on qubits 0,1,2 by composition.
72        let mut input = PauliString::<1>::x(0);
73        let _ = input.mul_assign(&PauliString::<1>::y(1));
74        let _ = input.mul_assign(&PauliString::<1>::z(2));
75
76        let mut x = vec![[0u64; 1]; 1];
77        let mut z = vec![[0u64; 1]; 1];
78        let mut c = vec![Complex64::new(0.0, 0.0); 1];
79        let mut len = 0usize;
80        let coeff = Complex64::new(2.0, 3.0);
81        {
82            let mut out = OutputBuffer::<1> {
83                x: &mut x,
84                z: &mut z,
85                coeff: &mut c,
86                len: &mut len,
87            };
88            id.apply(&input.x, &input.z, coeff, &mut out);
89            assert_eq!(*out.len, 1);
90        }
91        assert_eq!(x[0], input.x);
92        assert_eq!(z[0], input.z);
93        assert_eq!(c[0], coeff);
94    }
95
96    #[test]
97    fn apply_emits_input_unchanged_w2() {
98        let id = IdentityChannel::new();
99        // X on qubit 70 lands in word 1 — exercises the multi-word case.
100        let input = PauliString::<2>::x(70);
101
102        let mut x = vec![[0u64; 2]; 1];
103        let mut z = vec![[0u64; 2]; 1];
104        let mut c = vec![Complex64::new(0.0, 0.0); 1];
105        let mut len = 0usize;
106        let coeff = Complex64::new(-1.5, 0.25);
107        {
108            let mut out = OutputBuffer::<2> {
109                x: &mut x,
110                z: &mut z,
111                coeff: &mut c,
112                len: &mut len,
113            };
114            id.apply(&input.x, &input.z, coeff, &mut out);
115            assert_eq!(*out.len, 1);
116        }
117        assert_eq!(x[0], input.x);
118        assert_eq!(z[0], input.z);
119        assert_eq!(c[0], coeff);
120    }
121}