stabilizer_ch_form_rust/form/
discard.rs

1use crate::{
2    StabilizerCHForm,
3    error::{Error, Result},
4};
5use ndarray::{Array1, Array2};
6
7impl StabilizerCHForm {
8    /// Discards the specified qubit from the state.
9    ///
10    /// NOTE: This function assumes that the qubit `qarg` has already been
11    /// projected onto the |0> state. You need to project the qubit onto |0> before
12    /// calling this function. If this is not the case, the behavior is undefined.
13    ///
14    ///
15    /// # Arguments
16    /// * `qarg` - The index of the qubit to discard.
17    ///
18    /// ## Errors
19    /// Returns an [`Error`] if the qubit index is out of bounds. Note that
20    /// this function does not check if the qubit is properly projected onto |0>.
21    pub fn discard(&mut self, qarg: usize) -> Result<()> {
22        if qarg >= self.n {
23            return Err(Error::QubitIndexOutOfBounds(qarg, self.n));
24        }
25
26        // Ensure s[qarg], v[qarg] are false
27        // and also G[qarg, :] and G[:, qarg] are zero except for the diagonal.
28        // Also ensure M[qarg, :] and M[:, qarg] are zero.
29        self.set_s_v_to_false(qarg)?;
30        self.transform_g(qarg)?;
31        self.transform_m(qarg)?;
32
33        // Update self with the new (n-1)-qubit state
34        self.n -= 1;
35        self.mat_g = self.remove_row_col_from_matrix(&self.mat_g, qarg);
36        self.mat_f = self.remove_row_col_from_matrix(&self.mat_f, qarg);
37        self.mat_m = self.remove_row_col_from_matrix(&self.mat_m, qarg);
38
39        self.gamma = self.remove_element_from_vector(&self.gamma, qarg);
40        self.vec_v = self.remove_element_from_vector(&self.vec_v, qarg);
41        self.vec_s = self.remove_element_from_vector(&self.vec_s, qarg);
42
43        Ok(())
44    }
45
46    /// Returns a new StabilizerCHForm with the specified qubit discarded.
47    ///
48    /// NOTE: This function assumes that the qubit `qarg` has already been
49    /// projected onto the |0> state. You need to project the qubit onto |0> before
50    /// calling this function. If this is not the case, the behavior is undefined.
51    ///
52    /// ## Arguments
53    /// * `qarg` - The index of the qubit to discard.
54    ///
55    /// ## Returns
56    /// A [`Result`] containing the new `StabilizerCHForm` with the specified qubit discarded.
57    pub fn discarded(&self, qarg: usize) -> Result<StabilizerCHForm> {
58        let mut self_clone = self.clone();
59        self_clone.discard(qarg)?;
60        Ok(self_clone)
61    }
62
63    /// Creates a new matrix by removing a specified row and column from the input matrix.
64    fn remove_row_col_from_matrix<T: Clone>(&self, matrix: &Array2<T>, index: usize) -> Array2<T> {
65        let mut new_matrix = Array2::from_elem((self.n, self.n), matrix[[0, 0]].clone());
66        let mut new_i = 0;
67        for i in 0..=self.n {
68            if i == index {
69                continue;
70            }
71            let mut new_j = 0;
72            for j in 0..=self.n {
73                if j == index {
74                    continue;
75                }
76                new_matrix[[new_i, new_j]] = matrix[[i, j]].clone();
77                new_j += 1;
78            }
79            new_i += 1;
80        }
81        new_matrix
82    }
83
84    /// Creates a new vector by removing a specified element from the input vector.
85    fn remove_element_from_vector<T: Clone>(&self, vector: &Array1<T>, index: usize) -> Array1<T> {
86        let mut new_vector = Array1::from_elem(self.n, vector[0].clone());
87        let mut new_i = 0;
88        for i in 0..=self.n {
89            if i == index {
90                continue;
91            }
92            new_vector[new_i] = vector[i].clone();
93            new_i += 1;
94        }
95        new_vector
96    }
97
98    fn set_s_v_to_false(&mut self, qarg: usize) -> Result<()> {
99        if !self.vec_v[qarg] && !self.vec_s[qarg] {
100            return Ok(());
101        }
102
103        // Find a 'clean' qubit that satisfies v[i] = s[i] = 0
104        let mut clean_index = (0..self.n).find(|&i| i != qarg && !self.vec_v[i] && !self.vec_s[i]);
105        if clean_index.is_none() {
106            // Find two qubits to create a clean one. qarg can be one of them.
107            let mut candidates = (0..self.n).filter(|&i| !self.vec_v[i] && self.vec_s[i]);
108            if let (Some(f), Some(s)) = (candidates.next(), candidates.next()) {
109                self.right_multiply_cx(f, s)?;
110                self.vec_s[s] = false;
111                clean_index = Some(s);
112            }
113        }
114
115        // If the qubit state is valid (i.e. can be discarded), there must be a clean qubit.
116        let target = clean_index.ok_or(Error::CannotDiscardQubit(qarg))?;
117
118        // If qarg itself became clean during the process, no SWAP is needed.
119        if target == qarg {
120            return Ok(());
121        }
122
123        // Move the clean qubit to qarg position
124        self.right_multiply_cx(target, qarg)?;
125        self.right_multiply_cx(qarg, target)?;
126        self.right_multiply_cx(target, qarg)?;
127
128        self.vec_v.swap(qarg, target);
129        self.vec_s.swap(qarg, target);
130
131        Ok(())
132    }
133
134    /// Transforms G so that G[qarg, :] and G[:, qarg] are zero except for the diagonal.
135    fn transform_g(&mut self, qarg: usize) -> Result<()> {
136        if !self.mat_g[[qarg, qarg]] {
137            if let Some(pivot) = (0..self.n).find(|&i| i != qarg && self.mat_g[[qarg, i]]) {
138                self.right_multiply_cx(qarg, pivot)?;
139            } else {
140                unreachable!("Cannot zero G row due to G matrix state.");
141            }
142        }
143
144        // Make G[i, qarg] = false for i != qarg.
145        for i in 0..self.n {
146            if i != qarg && self.mat_g[[i, qarg]] {
147                self.left_multiply_cx(qarg, i)?;
148            }
149        }
150
151        // Make G[qarg, i] = false for i != qarg.
152        for i in 0..self.n {
153            if i != qarg && self.mat_g[[qarg, i]] {
154                if self.vec_v[i] {
155                    unreachable!("Cannot zero G column due to v vector state.");
156                }
157                self.right_multiply_cx(i, qarg)?;
158            }
159        }
160        Ok(())
161    }
162
163    /// Transforms M so that M[qarg, :] and M[:, qarg] are zero.
164    fn transform_m(&mut self, qarg: usize) -> Result<()> {
165        for i in 0..self.n {
166            if i != qarg && self.mat_m[[i, qarg]] {
167                self.left_multiply_cx(qarg, i)?;
168            }
169        }
170        if self.mat_m[[qarg, qarg]] {
171            self.left_multiply_sdg(qarg)?;
172        }
173
174        for i in 0..self.n {
175            if i != qarg && self.mat_m[[qarg, i]] {
176                self.right_multiply_cz(qarg, i)?;
177            }
178        }
179
180        Ok(())
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use num_complex::Complex64;
187
188    use crate::circuit::CliffordCircuit;
189    use crate::test_utils::{assert_eq_complex_array1, tensor_statevectors};
190
191    use super::*;
192
193    #[test]
194    fn test_discard() {
195        let mut trials = 0;
196        let mut successes = 0;
197        let num_qubits = 5;
198        while successes < 10 && trials < 1000 {
199            trials += 1;
200            let mut seed = [0u8; 32];
201            let trial_bytes = (trials as u64).to_le_bytes();
202            seed[0..8].copy_from_slice(&trial_bytes);
203            let random_circuit = CliffordCircuit::random_clifford(num_qubits, Some(seed));
204            let mut ch_form = StabilizerCHForm::from_clifford_circuit(&random_circuit).unwrap();
205
206            let qubit_to_discard = num_qubits - 1;
207            // Project the qubit onto |0>]
208            if ch_form.project(qubit_to_discard, false).is_err() {
209                continue; // Cannot discard this qubit, try again
210            }
211            let expected_original_sv = ch_form.to_statevector().unwrap();
212            // |0>
213            let zero_state = ndarray::array![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
214
215            match ch_form.discard(qubit_to_discard) {
216                Ok(()) => {
217                    let sv_after_discard = ch_form.to_statevector().unwrap();
218                    let expected_after_discard =
219                        tensor_statevectors(&sv_after_discard, &zero_state);
220                    assert_eq_complex_array1(&expected_original_sv, &expected_after_discard);
221                    successes += 1;
222                }
223                Err(_) => {
224                    // Discard is always expected to succeed after projection
225                    panic!("Discard failed after successful projection");
226                }
227            }
228        }
229    }
230}