Skip to main content

tnc/io/qasm/
qasm_importer.rs

1use crate::builders::circuit_builder::Circuit;
2use crate::io::qasm::{
3    ast::Visitor, circuit_creator::CircuitCreator, expression_folder::ExpressionFolder,
4    gate_inliner::GateInliner, include_resolver::expand_includes, parser::parse,
5};
6
7/// Creates a [`Circuit`] from OpenQASM2 code.
8///
9/// All gates are inlined up to the known gates defined in [`crate::gates`]. All
10/// qubits are initialized to zero. Note that not all QASM instructions are
11/// supported, such as `measure` or `if`.
12pub fn import_qasm<S>(code: S) -> Circuit
13where
14    S: Into<String>,
15{
16    // Expand all includes
17    let mut full_code = code.into();
18    expand_includes(&mut full_code);
19
20    // Parse to AST
21    let mut program = parse(&full_code);
22
23    // Simplify expressions (not strictly needed)
24    let mut expression_folder = ExpressionFolder;
25    expression_folder.visit_program(&mut program);
26
27    // Inline gate calls
28    let mut inliner = GateInliner::default();
29    inliner.inline_program(&mut program);
30
31    // Simplify expressions after inline (needed)
32    let mut expression_folder = ExpressionFolder;
33    expression_folder.visit_program(&mut program);
34
35    // Create the circuit
36    let circuit_creator = CircuitCreator;
37    circuit_creator.create_circuit(&program)
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    use std::f64::consts::FRAC_1_SQRT_2;
45
46    use approx::assert_abs_diff_eq;
47    use num_complex::Complex64;
48
49    use crate::{
50        builders::circuit_builder::Permutor,
51        contractionpath::ContractionPath,
52        tensornetwork::{
53            contraction::contract_tensor_network,
54            tensor::{CompositeTensor, EdgeIndex, LeafTensor, TensorIndex},
55            tensordata::TensorData,
56        },
57    };
58
59    /// Returns whether the edge connects the two tensors.
60    fn edge_connects(
61        edge_id: EdgeIndex,
62        t1_id: TensorIndex,
63        t2_id: TensorIndex,
64        tn: &CompositeTensor,
65    ) -> bool {
66        let t1 = tn.tensor(t1_id).as_leaf().unwrap();
67        let t2 = tn.tensor(t2_id).as_leaf().unwrap();
68        let overlap = t1 & t2;
69        overlap.legs().contains(&edge_id)
70    }
71
72    /// Returns whether the edge is an open edge of the tensor.
73    fn is_open_edge_of(edge_id: EdgeIndex, t1_id: TensorIndex, tn: &CompositeTensor) -> bool {
74        // Check if the edge is a leg of the tensor
75        let t1 = tn.tensor(t1_id).as_leaf().unwrap();
76        if !t1.legs().contains(&edge_id) {
77            return false;
78        }
79
80        // Check if the edge is not connected to any other tensor
81        for (tensor_id, tensor) in tn.tensors().iter().enumerate() {
82            let tensor = tensor.as_leaf().unwrap();
83            if tensor_id != t1_id && tensor.legs().contains(&edge_id) {
84                return false;
85            }
86        }
87        true
88    }
89
90    struct IdTensor<'a> {
91        id: usize,
92        tensor: &'a LeafTensor,
93    }
94
95    fn get_quantum_tensors(
96        tn: &CompositeTensor,
97    ) -> (Vec<IdTensor<'_>>, Vec<IdTensor<'_>>, Vec<IdTensor<'_>>) {
98        let mut kets = Vec::new();
99        let mut single_qubit_gates = Vec::new();
100        let mut two_qubit_gates = Vec::new();
101        for (tid, tensor) in tn.tensors().iter().enumerate() {
102            let leaf = tensor.as_leaf().unwrap();
103            let id: usize = tid;
104            let legs = leaf.legs().len();
105            match legs {
106                1 => kets.push(IdTensor { id, tensor: leaf }),
107                2 => single_qubit_gates.push(IdTensor { id, tensor: leaf }),
108                4 => two_qubit_gates.push(IdTensor { id, tensor: leaf }),
109                _ => panic!("Tensor with unexpected leg count {legs} in quantum tensor network"),
110            }
111        }
112        (kets, single_qubit_gates, two_qubit_gates)
113    }
114
115    #[test]
116    fn bell_tensornetwork_construction() {
117        let code = "OPENQASM 2.0;
118        include \"qelib1.inc\";
119        qreg q[2];
120        h q[0];
121        cx q[0], q[1];
122        ";
123        let circuit = import_qasm(code);
124        let (tn, _) = circuit.into_statevector_network();
125
126        let (kets, single_qubit_gates, two_qubit_gates) = get_quantum_tensors(&tn);
127        let [k0, k1] = kets.as_slice() else { panic!() };
128        let [h] = single_qubit_gates.as_slice() else {
129            panic!()
130        };
131        let [cx] = two_qubit_gates.as_slice() else {
132            panic!()
133        };
134
135        // Find out which tensor is the first/top qubit (the one connected to the H gate tensor)
136        // and which is the second/bottom qubit
137        let first_qubit_id = h.tensor.legs()[1];
138        let (first_qubit, second_qubit) = if first_qubit_id == k0.id {
139            (k0, k1)
140        } else if first_qubit_id == k1.id {
141            (k1, k0)
142        } else {
143            panic!("H gate tensor not connected to any ket tensor");
144        };
145
146        // Check edges
147        let fq_to_h_id = first_qubit.tensor.legs()[0];
148        assert_eq!(h.tensor.legs()[1], fq_to_h_id);
149        assert!(edge_connects(fq_to_h_id, first_qubit_id, h.id, &tn));
150
151        let sq_to_cx_t_id = second_qubit.tensor.legs()[0];
152        assert_eq!(cx.tensor.legs()[3], sq_to_cx_t_id);
153        assert!(edge_connects(sq_to_cx_t_id, second_qubit.id, cx.id, &tn));
154
155        let h_to_cx_c_id = h.tensor.legs()[0];
156        assert_eq!(cx.tensor.legs()[2], h_to_cx_c_id);
157        assert!(edge_connects(h_to_cx_c_id, h.id, cx.id, &tn));
158
159        let cx_c_to_open_id = cx.tensor.legs()[0];
160        assert!(is_open_edge_of(cx_c_to_open_id, cx.id, &tn));
161
162        let cx_t_to_open_id = cx.tensor.legs()[1];
163        assert!(is_open_edge_of(cx_t_to_open_id, cx.id, &tn));
164    }
165
166    /// Contracts the tensor network with an arbitrary contraction order, then
167    /// returns the correctly permuted tensor data.
168    fn contract_tn(tn: CompositeTensor, perm: &Permutor) -> TensorData {
169        let opt_path =
170            ContractionPath::simple((1..tn.tensors().len()).map(|tid| (0, tid)).collect());
171        let leaf = contract_tensor_network(tn, &opt_path);
172        let leaf = perm.apply(leaf);
173        leaf.into_data()
174    }
175
176    #[test]
177    fn bell_contract() {
178        let code = "OPENQASM 2.0;
179        include \"qelib1.inc\";
180        qreg q[2];
181        h q[0];
182        cx q[0], q[1];
183        ";
184        let circuit = import_qasm(code);
185        let (tn, perm) = circuit.into_statevector_network();
186        let resulting_state = contract_tn(tn, &perm);
187
188        let expected = TensorData::new_from_data(
189            &[2, 2],
190            vec![
191                Complex64::new(FRAC_1_SQRT_2, 0.),
192                Complex64::ZERO,
193                Complex64::ZERO,
194                Complex64::new(FRAC_1_SQRT_2, 0.),
195            ],
196        );
197        assert_abs_diff_eq!(&resulting_state, &expected);
198    }
199
200    #[test]
201    fn custom_swap() {
202        let code = "OPENQASM 2.0;
203        include \"qelib1.inc\";
204        qreg q[2];
205        gate myswap a, b {
206            cx a, b;
207            cx b, a;
208            cx a, b;
209        }
210        x q[0];
211        myswap q[1], q[0];
212        ";
213        let circuit = import_qasm(code);
214        let (tn, perm) = circuit.into_statevector_network();
215        let resulting_state = contract_tn(tn, &perm);
216
217        let expected = TensorData::new_from_data(
218            &[2, 2],
219            vec![
220                Complex64::ZERO,
221                Complex64::ONE,
222                Complex64::ZERO,
223                Complex64::ZERO,
224            ],
225        );
226        assert_abs_diff_eq!(&resulting_state, &expected);
227    }
228
229    fn odd_test_circuit() -> Circuit {
230        // Test with odd numbers to check the order of the statevector is correct
231        let code = "OPENQASM 2.0;
232        include \"qelib1.inc\";
233        qreg q[3];
234        rx(0.5) q[0];
235        rx(0.2) q[1];
236        rx(0.3) q[2];
237        cx q[0], q[1];
238        cx q[1], q[2];";
239        import_qasm(code)
240    }
241
242    #[test]
243    fn statevector_order() {
244        let circuit = odd_test_circuit();
245        let (tn, perm) = circuit.into_statevector_network();
246        let resulting_state = contract_tn(tn, &perm);
247
248        let expected = TensorData::new_from_data(
249            &[2, 2, 2],
250            vec![
251                Complex64::new(0.953246407214305, 0.0),
252                Complex64::new(0.0, -0.14406910361762032),
253                Complex64::new(-0.014455126269118733, 0.0),
254                Complex64::new(0.0, -0.09564366568448116),
255                Complex64::new(-0.024421837348497916, 0.0),
256                Complex64::new(0.0, 0.0036909997130494475),
257                Complex64::new(-0.03678688170631573, 0.0),
258                Complex64::new(0.0, -0.24340376901515096),
259            ],
260        );
261        assert_abs_diff_eq!(&resulting_state, &expected);
262    }
263
264    #[test]
265    fn statevector_order_two_fixed_qubits() {
266        let circuit = odd_test_circuit();
267        // 1*0 should get a vec with amplitudes |100> and |110>
268        let (tn, perm) = circuit.into_amplitude_network("1*0");
269        let resulting_state = contract_tn(tn, &perm);
270
271        let expected = TensorData::new_from_data(
272            &[2],
273            vec![
274                Complex64::new(-0.024421837348497916, 0.0),
275                Complex64::new(-0.03678688170631573, 0.0),
276            ],
277        );
278        assert_abs_diff_eq!(&resulting_state, &expected);
279    }
280
281    #[test]
282    fn statevector_order_one_fixed_qubit() {
283        let circuit = odd_test_circuit();
284        // *1* should get a vec with amplitudes |010>, |011>, |110>, |111>
285        let (tn, perm) = circuit.into_amplitude_network("*1*");
286        let resulting_state = contract_tn(tn, &perm);
287
288        let expected = TensorData::new_from_data(
289            &[2, 2],
290            vec![
291                Complex64::new(-0.014455126269118733, 0.0),
292                Complex64::new(0.0, -0.09564366568448116),
293                Complex64::new(-0.03678688170631573, 0.0),
294                Complex64::new(0.0, -0.24340376901515096),
295            ],
296        );
297        assert_abs_diff_eq!(&resulting_state, &expected);
298    }
299
300    #[test]
301    fn gate_order() {
302        // Ensures that the cx gate legs are in the correct order
303        let code = "OPENQASM 2.0;
304        include \"qelib1.inc\";
305        qreg q[2];
306        creg c[1];
307        u2(0,0) q[0];
308        u2(-pi,-pi) q[1];
309        cx q[0],q[1];
310        u2(-pi,-pi) q[0];";
311
312        let circuit = import_qasm(code);
313        let (tn, perm) = circuit.into_statevector_network();
314        let resulting_state = contract_tn(tn, &perm);
315
316        let expected = TensorData::new_from_data(
317            &[2, 2],
318            vec![
319                Complex64::ZERO,
320                Complex64::ZERO,
321                Complex64::new(-FRAC_1_SQRT_2, 0.0),
322                Complex64::new(FRAC_1_SQRT_2, 0.0),
323            ],
324        );
325        assert_abs_diff_eq!(&resulting_state, &expected);
326    }
327}