rustiq_core/structures/
tableau.rs

1use super::pauli_like::PauliLike;
2use super::pauli_set::PauliSet;
3use super::{CliffordCircuit, IsometryTableau};
4use rand::Rng;
5
6fn compute_phase_product_pauli(pset0: &PauliSet, vec: &[bool]) -> bool {
7    let mut phase = false;
8    for (j, item) in vec.iter().enumerate().take(2 * pset0.n) {
9        phase ^= pset0.get_phase(j) & item;
10    }
11    let mut ifact: u8 = 0;
12    for i in 0..pset0.n {
13        if vec[i] & vec[i + pset0.n] {
14            ifact += 1;
15        }
16    }
17    ifact %= 4;
18    for j in 0..pset0.n {
19        let mut x: bool = false;
20        let mut z: bool = false;
21        for (i, item) in vec.iter().enumerate().take(2 * pset0.n) {
22            if *item {
23                let x1: bool = pset0.get_entry(j, i);
24                let z1: bool = pset0.get_entry(j + pset0.n, i);
25                let entry = (x1, z1, x, z);
26                if LOOKUP_0.contains(&entry) {
27                    ifact += 1;
28                }
29                if LOOKUP_1.contains(&entry) {
30                    ifact += 3;
31                }
32                x ^= x1;
33                z ^= z1;
34                ifact %= 4;
35            }
36        }
37    }
38    (((ifact % 4) >> 1) != 0) ^ phase
39}
40
41#[derive(Debug, Clone, PartialEq)]
42pub struct Tableau {
43    pub logicals: PauliSet,
44}
45
46impl Tableau {
47    /// Allocates a new Tableau representing the identity operator over `n` qubits
48    pub fn new(n: usize) -> Self {
49        let mut logicals = PauliSet::new(n);
50        for i in 0..2 * n {
51            // fn insert_vec_bool(&mut self, axis: &Vec<bool>, phase: bool) -> usize {
52            let mut vecbool = vec![false; 2 * n];
53            vecbool[i] = true;
54            logicals.insert_vec_bool(&vecbool, false);
55        }
56        Tableau { logicals }
57    }
58    /// Build the Tableau corresponding to a Clifford circuit
59    pub fn from_circuit(circuit: &CliffordCircuit) -> Self {
60        let mut tab = Self::new(circuit.nqbits);
61        tab.conjugate_with_circuit(circuit);
62        tab
63    }
64    /// Generates a random Tableau (no garantuees, just here for testing)
65    pub fn random(n: usize) -> Self {
66        let mut rng = rand::thread_rng();
67        let mut iso = Self::new(n);
68        for _ in 0..(n) * (n) {
69            let i = rng.gen::<usize>() % (n);
70            loop {
71                let j = rng.gen::<usize>() % (n);
72                if i == j {
73                    continue;
74                }
75                iso.cnot(i, j);
76                break;
77            }
78            for _ in 0..(n) {
79                let gate_i = rng.gen::<u8>() % 3;
80                if gate_i == 1 {
81                    let q = rng.gen::<usize>() % (n);
82                    iso.h(q);
83                }
84                if gate_i == 2 {
85                    let q = rng.gen::<usize>() % (n);
86                    iso.s(q);
87                }
88            }
89        }
90        iso
91    }
92    /// Build a Tableau from a PauliSet
93    pub fn from_operators(logicals: &Vec<(bool, String)>) -> Self {
94        if logicals.is_empty() {
95            return Self::new(0);
96        }
97        let nqbits = logicals[0].1.len();
98        let mut pset = PauliSet::new(nqbits);
99        for (phase, string) in logicals {
100            pset.insert(string, *phase);
101        }
102        Self { logicals: pset }
103    }
104    /// Returns the inverse Tableau
105    pub fn adjoint(&self) -> Self {
106        let mut new_logicals = PauliSet::new(self.logicals.n);
107        for i in 0..self.logicals.n {
108            let (_, string) = self.logicals.get_inverse_x(i);
109            new_logicals.insert(&string, self.logicals.get_phase(i));
110        }
111        for i in 0..self.logicals.n {
112            let (_, string) = self.logicals.get_inverse_z(i);
113            new_logicals.insert(&string, self.logicals.get_phase(i + self.logicals.n));
114        }
115        let prod = self.clone()
116            * Tableau {
117                logicals: new_logicals.clone(),
118            };
119        for i in 0..2 * self.logicals.n {
120            new_logicals.set_phase(i, new_logicals.get_phase(i) ^ prod.logicals.get_phase(i));
121        }
122        Self {
123            logicals: new_logicals,
124        }
125    }
126
127    pub fn get_inverse_z(&self, qbit: usize) -> (bool, String) {
128        let (_, string) = self.logicals.get_inverse_z(qbit);
129        let mut as_vec_bool = vec![false; 2 * self.logicals.n];
130        for qbit in 0..self.logicals.n {
131            match string.chars().nth(qbit).unwrap() {
132                'X' => {
133                    as_vec_bool[qbit] = true;
134                }
135                'Y' => {
136                    as_vec_bool[qbit] = true;
137                    as_vec_bool[qbit + self.logicals.n] = true;
138                }
139                'Z' => {
140                    as_vec_bool[qbit + self.logicals.n] = true;
141                }
142                _ => {}
143            }
144        }
145        let phase = compute_phase_product_pauli(&self.logicals, &as_vec_bool);
146        (phase, string)
147    }
148    pub fn get_inverse_x(&self, qbit: usize) -> (bool, String) {
149        let (_, string) = self.logicals.get_inverse_x(qbit);
150        let mut as_vec_bool = vec![false; 2 * self.logicals.n];
151        for qbit in 0..self.logicals.n {
152            match string.chars().nth(qbit).unwrap() {
153                'X' => {
154                    as_vec_bool[qbit] = true;
155                }
156                'Y' => {
157                    as_vec_bool[qbit] = true;
158                    as_vec_bool[qbit + self.logicals.n] = true;
159                }
160                'Z' => {
161                    as_vec_bool[qbit + self.logicals.n] = true;
162                }
163                _ => {}
164            }
165        }
166        let phase = compute_phase_product_pauli(&self.logicals, &as_vec_bool);
167        (phase, string)
168    }
169    /// Lifts the Taleau into an IsometryTableau (k = 0)
170    pub fn to_isometry(self) -> IsometryTableau {
171        IsometryTableau {
172            n: self.logicals.n,
173            k: 0,
174            stabilizers: PauliSet::new(self.logicals.n),
175            logicals: self.logicals,
176        }
177    }
178}
179
180impl PauliLike for Tableau {
181    fn h(&mut self, i: usize) {
182        self.logicals.h(i);
183    }
184
185    fn s(&mut self, i: usize) {
186        self.logicals.s(i);
187    }
188
189    fn sd(&mut self, i: usize) {
190        self.logicals.sd(i);
191    }
192
193    fn sqrt_x(&mut self, i: usize) {
194        self.logicals.sqrt_x(i);
195    }
196
197    fn sqrt_xd(&mut self, i: usize) {
198        self.logicals.sqrt_xd(i);
199    }
200
201    fn cnot(&mut self, i: usize, j: usize) {
202        self.logicals.cnot(i, j);
203    }
204}
205
206const LOOKUP_0: [(bool, bool, bool, bool); 3] = [
207    (false, true, true, true),
208    (true, false, false, true),
209    (true, true, true, false),
210];
211
212const LOOKUP_1: [(bool, bool, bool, bool); 3] = [
213    (false, true, true, false),
214    (true, false, true, true),
215    (true, true, false, true),
216];
217
218impl std::ops::Mul<Tableau> for Tableau {
219    type Output = Tableau;
220    fn mul(self, rhs: Tableau) -> Self::Output {
221        assert_eq!(self.logicals.n, rhs.logicals.n);
222        let mut new_tableau = Tableau::new(self.logicals.n);
223        for i in 0..2 * self.logicals.n {
224            let (mut phase, col) = rhs.logicals.get_as_vec_bool(i);
225            for (j, item) in col.iter().enumerate().take(2 * self.logicals.n) {
226                phase ^= self.logicals.get_phase(j) & item;
227            }
228            new_tableau.logicals.set_phase(i, phase);
229        }
230        let mut ifacts = rhs.logicals.get_i_factors();
231        for (k, item) in ifacts.iter_mut().enumerate().take(2 * self.logicals.n) {
232            for j in 0..self.logicals.n {
233                let mut x: bool = false;
234                let mut z: bool = false;
235                for i in 0..2 * self.logicals.n {
236                    if rhs.logicals.get_entry(i, k) {
237                        let x1: bool = self.logicals.get_entry(j, i);
238                        let z1: bool = self.logicals.get_entry(j + self.logicals.n, i);
239                        let entry = (x1, z1, x, z);
240                        if LOOKUP_0.contains(&entry) {
241                            *item += 1;
242                        }
243                        if LOOKUP_1.contains(&entry) {
244                            *item += 3;
245                        }
246                        x ^= x1;
247                        z ^= z1;
248                        *item %= 4;
249                    }
250                }
251            }
252            *item %= 4;
253        }
254        let p: Vec<bool> = ifacts.into_iter().map(|v| 0 != ((v % 4) >> 1)).collect();
255        for (i, ph) in p.iter().enumerate() {
256            new_tableau
257                .logicals
258                .set_phase(i, new_tableau.logicals.get_phase(i) ^ ph);
259        }
260
261        for i in 0..2 * self.logicals.n {
262            for j in 0..2 * self.logicals.n {
263                let (_, col) = rhs.logicals.get_as_vec_bool(j);
264                new_tableau
265                    .logicals
266                    .set_raw_entry(i, j, self.logicals.and_row_acc(i, &col));
267            }
268        }
269        new_tableau
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::Tableau;
276
277    #[test]
278    fn test_mul_adjoint() {
279        let t1 = Tableau::random(5);
280        let t2 = t1.adjoint();
281        let t3 = t1 * t2;
282        let t4 = Tableau::new(5);
283        assert_eq!(t3, t4);
284    }
285}