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}