stabilizer_ch_form_rust/circuit/
clifford_circuit.rs

1use crate::circuit::CliffordGate;
2use crate::circuit::parser;
3use crate::circuit::random_clifford;
4use crate::error::Result;
5use std::fmt;
6
7/// A struct representing a Clifford circuit composed of Clifford gates.
8/// [`CliffordCircuit`] only stores the sequence of gates and does not calculate
9/// the resulting stabilizer state.
10///
11/// ## Example usage:
12///
13/// ```rust
14/// use stabilizer_ch_form_rust::circuit::CliffordCircuit;
15/// use stabilizer_ch_form_rust::circuit::CliffordGate::{ H, CX };
16///
17/// let mut circuit = CliffordCircuit::new(2);
18/// circuit.apply_h(0);
19/// circuit.apply_cx(0, 1);
20///
21/// assert_eq!(circuit.gates[0], H(0));
22/// assert_eq!(circuit.gates[1], CX(0, 1));
23///
24/// // `CliffordCircuit` is intended to be converted to `StabilizerCHForm` for simulation
25/// use stabilizer_ch_form_rust::StabilizerCHForm;
26/// let ch_form = StabilizerCHForm::from_clifford_circuit(&circuit).unwrap();
27/// ```
28#[derive(Debug, Clone)]
29pub struct CliffordCircuit {
30    pub num_qubits: usize,
31    pub gates: Vec<CliffordGate>,
32}
33
34impl CliffordCircuit {
35    /// Creates a new Clifford circuit with the specified number of qubits.
36    /// ## Arguments
37    /// * `num_qubits` - The number of qubits in the circuit.
38    pub fn new(num_qubits: usize) -> Self {
39        CliffordCircuit {
40            num_qubits,
41            gates: Vec::new(),
42        }
43    }
44
45    /// creates a new Clifford circuit by taking the tensor product of this circuit
46    /// and another.
47    /// Gates from `self` are applied to the first `self.num_qubits` qubits,
48    /// and gates from `other` are applied to the next `other.num_qubits` qubits.
49    ///
50    /// ## Arguments
51    /// * `other` - The other Clifford circuit to tensor with.
52    ///
53    /// ## Returns
54    /// A new `CliffordCircuit` representing the tensor product.
55    pub fn tensor(&self, other: &CliffordCircuit) -> Self {
56        let mut new_circuit = CliffordCircuit::new(self.num_qubits + other.num_qubits);
57        // Add gates from the first circuit
58        for gate in &self.gates {
59            new_circuit.gates.push(gate.clone());
60        }
61        // Add gates from the second circuit, shifting qubit indices
62        for gate in &other.gates {
63            new_circuit.gates.push(gate.shifted(self.num_qubits));
64        }
65        new_circuit
66    }
67
68    /// Appends the gates from another [`CliffordCircuit`] to this one.
69    ///
70    /// ## Arguments
71    /// * `other` - The other Clifford circuit whose gates are to be appended.
72    pub fn append(&mut self, other: &CliffordCircuit) {
73        for gate in &other.gates {
74            self.gates.push(gate.clone());
75        }
76    }
77
78    /// Adds a Clifford gate to the circuit.
79    /// ## Arguments
80    /// * `gate` - The Clifford gate to add.
81    pub fn add_gate(&mut self, gate: CliffordGate) {
82        self.gates.push(gate);
83    }
84
85    /// Adds multiple Clifford gates to the circuit.
86    /// ## Arguments
87    /// * `gates` - A vector of Clifford gates to add.
88    pub fn add_gates(&mut self, gates: Vec<CliffordGate>) {
89        for gate in gates {
90            self.add_gate(gate);
91        }
92    }
93
94    /// Applies a Hadamard gate to the specified qubit.
95    /// ## Arguments
96    /// * `qarg` - The index of the qubit to apply the gate to.
97    pub fn apply_h(&mut self, qarg: usize) {
98        self.add_gate(CliffordGate::H(qarg));
99    }
100
101    /// Applies a Pauli-X gate to the specified qubit.
102    /// ## Arguments
103    /// * `qarg` - The index of the qubit to apply the gate to.
104    pub fn apply_x(&mut self, qarg: usize) {
105        self.add_gate(CliffordGate::X(qarg));
106    }
107
108    /// Applies a Pauli-Y gate to the specified qubit.
109    /// ## Arguments
110    /// * `qarg` - The index of the qubit to apply the gate to.
111    pub fn apply_y(&mut self, qarg: usize) {
112        self.add_gate(CliffordGate::Y(qarg));
113    }
114
115    /// Applies a Pauli-Z gate to the specified qubit.
116    /// ## Arguments
117    /// * `qarg` - The index of the qubit to apply the gate to.
118    pub fn apply_z(&mut self, qarg: usize) {
119        self.add_gate(CliffordGate::Z(qarg));
120    }
121
122    /// Applies a Phase (S) gate to the specified qubit.
123    /// ## Arguments
124    /// * `qarg` - The index of the qubit to apply the gate to.
125    pub fn apply_s(&mut self, qarg: usize) {
126        self.add_gate(CliffordGate::S(qarg));
127    }
128
129    /// Applies a conjugate Phase (Sdg) gate to the specified qubit.
130    /// ## Arguments
131    /// * `qarg` - The index of the qubit to apply the gate to.
132    pub fn apply_sdg(&mut self, qarg: usize) {
133        self.add_gate(CliffordGate::Sdg(qarg));
134    }
135
136    /// Applies a square root of X (SqrtX) gate to the specified qubit.
137    /// ## Arguments
138    /// * `qarg` - The index of the qubit to apply the gate to.
139    pub fn apply_sqrt_x(&mut self, qarg: usize) {
140        self.add_gate(CliffordGate::SqrtX(qarg));
141    }
142
143    /// Applies a conjugate square root of X (SqrtXdg) gate to the specified qubit.
144    /// ## Arguments
145    /// * `qarg` - The index of the qubit to apply the gate to.
146    pub fn apply_sqrt_xdg(&mut self, qarg: usize) {
147        self.add_gate(CliffordGate::SqrtXdg(qarg));
148    }
149
150    /// Applies a controlled-X (CX) gate between the specified control and target qubits.
151    /// ## Arguments
152    /// * `control` - The index of the control qubit.
153    /// * `target` - The index of the target qubit.
154    pub fn apply_cx(&mut self, control: usize, target: usize) {
155        self.add_gate(CliffordGate::CX(control, target));
156    }
157
158    /// Applies a controlled-Z (CZ) gate between the specified qubits.
159    /// ## Arguments
160    /// * `qarg1` - The index of the first qubit.
161    /// * `qarg2` - The index of the second qubit.
162    pub fn apply_cz(&mut self, qarg1: usize, qarg2: usize) {
163        self.add_gate(CliffordGate::CZ(qarg1, qarg2));
164    }
165
166    /// Applies a SWAP gate between the specified qubits.
167    /// ## Arguments
168    /// * `qarg1` - The index of the first qubit.
169    /// * `qarg2` - The index of the second qubit.
170    pub fn apply_swap(&mut self, qarg1: usize, qarg2: usize) {
171        self.add_gate(CliffordGate::Swap(qarg1, qarg2));
172    }
173
174    /// Parses an OpenQASM 2.0 file into a [`CliffordCircuit`].
175    ///
176    /// ## Arguments
177    /// * `path` - A path to the QASM file.
178    ///
179    /// ## Returns
180    /// A [`Result`] containing the parsed [`CliffordCircuit`] or an [`Error`](crate::error::Error).
181    pub fn from_qasm_file(path: &str) -> Result<Self> {
182        parser::from_qasm_file(path)
183    }
184
185    /// Parses an OpenQASM 2.0 string into a [`CliffordCircuit`].
186    ///
187    /// ## Arguments
188    /// * `qasm_str` - A string slice containing the OpenQASM 2.0 circuit description.
189    ///
190    /// ## Returns
191    /// A [`Result`] containing the parsed [`CliffordCircuit`] or an [`Error`](crate::error::Error).
192    pub fn from_qasm_str(qasm_str: &str) -> Result<Self> {
193        parser::from_qasm_str(qasm_str)
194    }
195
196    /// Converts the circuit to an OpenQASM 2.0 string.
197    ///
198    /// ## Arguments
199    /// * `reg_name` - The name of the quantum register (e.g., "q").
200    ///
201    /// ## Returns
202    /// A [`String`] containing the OpenQASM 2.0 representation of the circuit.
203    pub fn to_qasm_str(&self, reg_name: &str) -> String {
204        parser::to_qasm_str(self, reg_name)
205    }
206
207    /// Writes the circuit to an OpenQASM 2.0 file.
208    ///
209    /// ## Arguments
210    /// * `path` - The path to the output file.
211    /// * `reg_name` - The name of the quantum register (e.g., "q").
212    ///
213    /// ## Returns
214    /// A [`Result`] indicating success or failure.
215    pub fn to_qasm_file(&self, path: &str, reg_name: &str) -> Result<()> {
216        parser::to_qasm_file(self, path, reg_name)
217    }
218
219    /// Generates a uniformly random n-qubit Clifford circuit.
220    ///
221    /// This function implements the O(n^2) algorithm described in the paper to sample a Clifford
222    /// operator uniformly at random from the n-qubit Clifford group.
223    /// The resulting circuit is structured according to the canonical form U = F1 * H * S * F2.
224    /// See the reference for details.
225    ///
226    /// ## Arguments
227    /// * `n` - The number of qubits. Must be greater than 0.
228    /// * `seed` - An optional seed for the random number generator to ensure reproducibility.
229    ///   If `None`, a seed will be generated from system entropy.
230    ///
231    /// ## Returns
232    /// A [`CliffordCircuit`] object representing the random Clifford operator.
233    ///
234    /// ## Reference
235    /// - S. Bravyi and D. Maslov, "Hadamard-free circuits expose the structure of the Clifford
236    ///   group," IEEE Trans. Inf. Theory 67, 5800 (2021).
237    ///   <https://doi.org/10.1109/TIT.2021.3081415>
238    pub fn random_clifford(num_qubits: usize, seed: Option<[u8; 32]>) -> Self {
239        random_clifford::random_clifford(num_qubits, seed)
240    }
241}
242
243impl fmt::Display for CliffordCircuit {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        write!(f, "CliffordCircuit(num_qubits={}) [", self.num_qubits)?;
246
247        for (i, gate) in self.gates.iter().enumerate() {
248            if i > 0 {
249                write!(f, ", ")?;
250            }
251            write!(f, "{}", gate)?;
252        }
253
254        write!(f, "]")
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn test_append_circuit() {
264        let mut circuit1 = CliffordCircuit::new(2);
265        circuit1.apply_h(0);
266        let mut circuit2 = CliffordCircuit::new(2);
267        circuit2.apply_cx(0, 1);
268
269        circuit1.append(&circuit2);
270
271        assert_eq!(circuit1.gates.len(), 2);
272        assert_eq!(circuit1.gates[0], CliffordGate::H(0));
273        assert_eq!(circuit1.gates[1], CliffordGate::CX(0, 1));
274    }
275
276    #[test]
277    fn test_tensor_circuit() {
278        let mut circuit1 = CliffordCircuit::new(2);
279        circuit1.apply_h(0);
280        let mut circuit2 = CliffordCircuit::new(3);
281        circuit2.apply_cx(0, 1);
282
283        let tensor_circuit = circuit1.tensor(&circuit2);
284
285        assert_eq!(tensor_circuit.num_qubits, 5);
286        assert_eq!(tensor_circuit.gates.len(), 2);
287        assert_eq!(tensor_circuit.gates[0], CliffordGate::H(0));
288        assert_eq!(tensor_circuit.gates[1], CliffordGate::CX(2, 3));
289    }
290
291    #[test]
292    fn test_clifford_circuit_display() {
293        let mut circuit = CliffordCircuit::new(2);
294        circuit.apply_h(0);
295        circuit.apply_cx(0, 1);
296        let display_str = format!("{}", circuit);
297        assert_eq!(
298            display_str,
299            "CliffordCircuit(num_qubits=2) [H(0), CX(0, 1)]"
300        );
301    }
302}