Skip to content

Commit

Permalink
Fix parsing of OpenQASM code with spaces between phase parameters.
Browse files Browse the repository at this point in the history
Add round-trip tests from pyzx to qiskit back to pyzx using both OpenQASM 2 and 3.

Fixes zxcalc#225.
  • Loading branch information
dlyongemallo committed May 9, 2024
1 parent 2caab4b commit 0e843bd
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 18 deletions.
5 changes: 3 additions & 2 deletions pyzx/circuit/qasmparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,13 @@ def extract_command_parts(self, c: str) -> Tuple[str,List[Fraction],List[str]]:
c = re.sub(r"^bit\[(\d+)] (\w+)$", r"creg \2[\1]", c)
c = re.sub(r"^qubit\[(\d+)] (\w+)$", r"qreg \2[\1]", c)
c = re.sub(r"^(\w+)\[(\d+)] = measure (\w+)\[(\d+)]$", r"measure \3[\4] -> \1[\2]", c)
name, rest = c.split(" ",1)
right_bracket = c.find(")")
name, rest = c.split(" ", 1) if right_bracket == -1\
else [c[:right_bracket+1], c[right_bracket+1:]]
args = [s.strip() for s in rest.split(",") if s.strip()]
left_bracket = name.find('(')
phases = []
if left_bracket != -1:
right_bracket = name.find(')')
if right_bracket == -1:
raise TypeError(f"Mismatched bracket: {name}.")
vals = name[left_bracket+1:right_bracket].split(',')
Expand Down
56 changes: 40 additions & 16 deletions tests/test_qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
try:
from qiskit import quantum_info, transpile
from qiskit.circuit import QuantumCircuit
from qiskit.qasm2 import dumps
from qiskit.qasm3 import loads
from qiskit.qasm2 import dumps as dumps2
from qiskit.qasm3 import loads as loads3, dumps as dumps3
except ImportError:
QuantumCircuit = None

Expand Down Expand Up @@ -232,10 +232,10 @@ def compare_gate_matrix_with_qiskit(gates, num_qubits: int, num_angles: int, qas
qasm = setup + \
", ".join(
[f"q[{i}]" for i in range(num_qubits)]) + ";\n"
c = Circuit.from_qasm(qasm)
pyzx_matrix = c.to_matrix()
pyzx_circuit = Circuit.from_qasm(qasm)
pyzx_matrix = pyzx_circuit.to_matrix()

for g in c.gates:
for g in pyzx_circuit.gates:
for b in g.to_basic_gates():
self.assertListEqual(b.to_basic_gates(), [b],
f"\n{gate}.to_basic_gates() contains non-basic gate")
Expand All @@ -244,16 +244,28 @@ def compare_gate_matrix_with_qiskit(gates, num_qubits: int, num_angles: int, qas
qiskit_qasm = setup + \
", ".join([f"q[{i}]" for i in reversed(
range(num_qubits))]) + ";\n"
qc = QuantumCircuit.from_qasm_str(
qiskit_qasm) if qasm_version == 2 else loads(qiskit_qasm)
qc = QuantumCircuit.from_qasm_str(qiskit_qasm) if qasm_version == 2 else loads3(qiskit_qasm)
qiskit_matrix = quantum_info.Operator(qc).data

# Check that pyzx and qiskit produce the same tensor from the same qasm, modulo qubit endianness.
self.assertTrue(compare_tensors(pyzx_matrix, qiskit_matrix, False),
f"Gate: {gate}\nqasm:\n{qasm}\npyzx_matrix:\n{pyzx_matrix}\nqiskit_matrix:\n{qiskit_matrix}")

s = c.to_qasm(qasm_version)
round_trip = Circuit.from_qasm(s)
self.assertEqual(c.qubits, round_trip.qubits)
self.assertListEqual(c.gates, round_trip.gates)
# Check internal round-trip (pyzx to qasm to pyzx) results in the same circuit.
qasm_from_pyzx = pyzx_circuit.to_qasm(qasm_version)
pyzx_round_trip = Circuit.from_qasm(qasm_from_pyzx)
self.assertEqual(pyzx_circuit.qubits, pyzx_round_trip.qubits)
self.assertListEqual(pyzx_circuit.gates, pyzx_round_trip.gates)

# Check external round-trip (pyzx to qasm to qiskit to qasm to pyzx) results in the same circuit.
# Note that the endianness is reversed when going out and again when coming back in, so the overall
# result is no change.
qiskit_from_qasm = (QuantumCircuit.from_qasm_str(qasm_from_pyzx) if qasm_version == 2
else loads3(qasm_from_pyzx))
pyzx_from_qiskit = Circuit.from_qasm(dumps2(qiskit_from_qasm) if qasm_version == 2
else dumps3(qiskit_from_qasm))
self.assertEqual(pyzx_circuit.qubits, pyzx_from_qiskit.qubits)
self.assertListEqual(pyzx_circuit.gates, pyzx_from_qiskit.gates)

# Test standard gates common to both qelib1.inc (OpenQASM 2) and stdgates.inc (OpenQASM 3).
compare_gate_matrix_with_qiskit(
Expand Down Expand Up @@ -305,16 +317,28 @@ def test_qiskit_transpile_pyzx_optimization_round_trip(self):
qc1 = transpile(qc)
t1 = quantum_info.Operator(qc1).data

c = Circuit.from_qasm(dumps(qc1))
g = c.to_graph()
full_reduce(g)
qasm = extract_circuit(g).to_basic_gates().to_qasm()
# Test round-trip for OpenQASM 2.
c2 = Circuit.from_qasm(dumps2(qc1))
g2 = c2.to_graph()
full_reduce(g2)
qasm2 = extract_circuit(g2).to_basic_gates().to_qasm(2)

qc2 = QuantumCircuit().from_qasm_str(qasm)
qc2 = QuantumCircuit().from_qasm_str(qasm2)
t2 = quantum_info.Operator(qc2).data

self.assertTrue(compare_tensors(t1, t2))

# Test round-trip for OpenQASM 3.
c3 = Circuit.from_qasm(dumps3(qc1))
g3 = c3.to_graph()
full_reduce(g3)
qasm3 = extract_circuit(g3).to_basic_gates().to_qasm(3)

qc3 = loads3(qasm3)
t3 = quantum_info.Operator(qc3).data

self.assertTrue(compare_tensors(t1, t3))


if __name__ == '__main__':
unittest.main()

0 comments on commit 0e843bd

Please sign in to comment.