Skip to main content

bloq_graph/verify/
qasm.rs

1//! Interpretation of the shared QASM instructions as pure ZX branch maps.
2
3use bloq_utils::qasm::{QasmGate, QasmInstruction, QasmProgram};
4use quizx::circuit::Circuit;
5use quizx::graph::{BasisElem, GraphLike};
6
7use crate::{Basis, PauliBasis};
8
9use super::QuizxGraph;
10
11pub use bloq_utils::qasm::QasmError;
12
13/// Parses the unitary part of an unconditional circuit.
14///
15/// Resets must precede gates and measurements on each wire, and measurements
16/// must be terminal. Both are removed from the returned circuit. Use
17/// [`super::LogicalVerifier`] to compare maps with classical branches.
18///
19/// # Errors
20///
21/// Returns a parse, boundary-order, or unsupported-conditional error.
22pub fn parse_qasm(source: &str) -> Result<Circuit, QasmError> {
23    let parsed = QasmCircuit::parse(source)?;
24    if parsed.program.instructions().iter().any(|instruction| {
25        matches!(instruction, QasmInstruction::Gate { condition, .. } if !condition.is_empty())
26    }) {
27        return Err(QasmError::Boundary(
28            "conditional operations are not supported by unitary circuit parsing".into(),
29        ));
30    }
31    Ok(parsed.unitary(&[]).to_basic_gates())
32}
33
34pub(super) struct QasmCircuit {
35    pub(super) program: QasmProgram,
36    pub(super) reset_inputs: Vec<bool>,
37    pub(super) measurements: Vec<Option<usize>>,
38}
39
40impl QasmCircuit {
41    pub(super) fn parse(source: &str) -> Result<Self, QasmError> {
42        let program = bloq_utils::qasm::parse_qasm(source)?;
43        let mut touched = vec![false; program.qubits().len()];
44        let mut reset_inputs = touched.clone();
45        let mut measurements = vec![None; touched.len()];
46        for instruction in program.instructions() {
47            match instruction {
48                QasmInstruction::Reset { qubit } => {
49                    if touched[*qubit] {
50                        return Err(QasmError::Boundary(format!(
51                            "reset must precede gates and measurements on {}",
52                            program.qubits()[*qubit]
53                        )));
54                    }
55                    reset_inputs[*qubit] = true;
56                }
57                QasmInstruction::Measure { qubit, bit } => {
58                    if measurements[*qubit].replace(*bit).is_some() {
59                        return Err(QasmError::Boundary(format!(
60                            "{} is measured more than once",
61                            program.qubits()[*qubit]
62                        )));
63                    }
64                    touched[*qubit] = true;
65                }
66                QasmInstruction::Gate { qubits, .. } => {
67                    for &qubit in qubits {
68                        if measurements[qubit].is_some() {
69                            return Err(QasmError::Boundary(format!(
70                                "gate follows measurement of {}",
71                                program.qubits()[qubit]
72                            )));
73                        }
74                        touched[qubit] = true;
75                    }
76                }
77            }
78        }
79        Ok(Self {
80            program,
81            reset_inputs,
82            measurements,
83        })
84    }
85
86    pub(super) fn input_count(&self) -> usize {
87        self.reset_inputs.iter().filter(|reset| !**reset).count()
88    }
89
90    pub(super) fn output_count(&self) -> usize {
91        self.measurements.iter().filter(|bit| bit.is_none()).count()
92    }
93
94    fn unitary(&self, values: &[bool]) -> Circuit {
95        let mut circuit = Circuit::new(self.program.qubits().len());
96        for instruction in self.program.instructions() {
97            let QasmInstruction::Gate {
98                gate,
99                qubits,
100                condition,
101            } = instruction
102            else {
103                continue;
104            };
105            if !condition
106                .iter()
107                .all(|&(bit, required)| values[bit] == required)
108            {
109                continue;
110            }
111            match *gate {
112                QasmGate::Identity => {}
113                QasmGate::H => circuit.add_gate("h", qubits.clone()),
114                QasmGate::Pauli(PauliBasis::X) => circuit.add_gate("x", qubits.clone()),
115                QasmGate::Pauli(PauliBasis::Z) => circuit.add_gate("z", qubits.clone()),
116                QasmGate::Pauli(PauliBasis::Y) => {
117                    // The omitted i is a global phase within a classical branch.
118                    circuit.add_gate("z", qubits.clone());
119                    circuit.add_gate("x", qubits.clone());
120                }
121                QasmGate::Phase { basis, quarters } => circuit.add_gate_with_phase(
122                    match basis {
123                        Basis::X => "rx",
124                        Basis::Z => "rz",
125                    },
126                    qubits.clone(),
127                    (quarters, 4),
128                ),
129                QasmGate::Cx => circuit.add_gate("cx", qubits.clone()),
130                QasmGate::Cz => circuit.add_gate("cz", qubits.clone()),
131                QasmGate::Xcx => circuit.add_gate("xcx", qubits.clone()),
132                QasmGate::Swap => circuit.add_gate("swap", qubits.clone()),
133                QasmGate::Ccx => circuit.add_gate("ccx", qubits.clone()),
134                QasmGate::Ccz => circuit.add_gate("ccz", qubits.clone()),
135            }
136        }
137        circuit
138    }
139
140    pub(super) fn instantiate(&self, values: &[bool]) -> QuizxGraph {
141        let mut graph: QuizxGraph = self.unitary(values).to_graph();
142        graph.plug_inputs(
143            &self
144                .reset_inputs
145                .iter()
146                .map(|reset| {
147                    if *reset {
148                        BasisElem::Z0
149                    } else {
150                        BasisElem::SKIP
151                    }
152                })
153                .collect::<Vec<_>>(),
154        );
155        graph.plug_outputs(
156            &self
157                .measurements
158                .iter()
159                .map(|bit| match bit {
160                    Some(bit) if values[*bit] => BasisElem::Z1,
161                    Some(_) => BasisElem::Z0,
162                    None => BasisElem::SKIP,
163                })
164                .collect::<Vec<_>>(),
165        );
166        graph
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use quizx::tensor::{CompareTensors, TensorF, ToTensor};
174
175    #[test]
176    fn unitary_parser_checks_boundary_order_and_rejects_conditionals() {
177        let parsed =
178            parse_qasm("OPENQASM 2.0; qreg a[1]; qreg b[2]; reset a; barrier a,b; h a; t b[0];")
179                .unwrap();
180        assert_eq!(parsed.num_qubits(), 3);
181        let measured = parse_qasm("OPENQASM 2.0; qreg q[1]; creg c[1]; measure q -> c;").unwrap();
182        assert!(measured.gates.is_empty());
183        for body in [
184            "measure q -> c; x q;",
185            "h q; reset q;",
186            "measure q -> c; if(c==1) x q;",
187            "if(c==0) x q;",
188        ] {
189            parse_qasm(&format!("OPENQASM 2.0; qreg q[1]; creg c[1]; {body}")).unwrap_err();
190        }
191    }
192
193    #[test]
194    fn ancilla_parity_and_conditional_pauli_keep_both_source_branches() {
195        let parsed = QasmCircuit::parse("OPENQASM 2.0; qreg q[2]; creg m[1]; reset q[1]; h q[1]; measure q[1] -> m; if(m==1) x q[0];").unwrap();
196        for bit in [false, true] {
197            let actual = parsed.instantiate(&[bit]);
198            let mut expected = Circuit::new(1);
199            if bit {
200                expected.add_gate("x", vec![0]);
201            }
202            assert!(<TensorF as CompareTensors>::scalar_eq(
203                &actual.to_tensorf(),
204                &expected.to_tensorf()
205            ));
206        }
207    }
208}