bloq_graph/verify/
qasm.rs1use 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
13pub 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 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}