Skip to content

Commit

Permalink
Merge pull request #226 from dlyongemallo/fix_qasm3_space_between_phases
Browse files Browse the repository at this point in the history
Fix two OpenQASM issues: handle spaces between phases and add `U` gate.
  • Loading branch information
jvdwetering authored May 10, 2024
2 parents 2caab4b + 4f98629 commit 42343c1
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 22 deletions.
1 change: 1 addition & 0 deletions pyzx/circuit/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,7 @@ def to_graph(self, g, q_mapper, c_mapper):
"u2": U2,
"u3": U3,
"u": U3,
"U": U3, # needed for OpenQASM 3 in Qiskit 1.0 and up
"cu3": CU3,
"cu": CU,
"cx": CNOT,
Expand Down
13 changes: 7 additions & 6 deletions pyzx/circuit/qasmparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from typing import List, Dict, Tuple, Optional

from . import Circuit
from .gates import Gate, qasm_gate_table, U2, U3
from .gates import Gate, qasm_gate_table
from ..utils import settings


Expand Down 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 Expand Up @@ -192,10 +193,10 @@ def parse_command(self, c: str, registers: Dict[str,Tuple[int,int]]) -> List[Gat
gates.append(g)
elif name == 'u2':
if len(phases) != 2: raise TypeError("Invalid specification {}".format(c))
gates.append(U2(argset[0],phases[0],phases[1]))
elif name in ('u3', 'u'):
gates.append(qasm_gate_table[name](argset[0], phases[0], phases[1])) # type: ignore
elif name in ('u3', 'u', 'U'):
if len(phases) != 3: raise TypeError("Invalid specification {}".format(c))
gates.append(U3(argset[0],phases[0],phases[1],phases[2]))
gates.append(qasm_gate_table[name](argset[0], phases[0], phases[1], phases[2])) # type: ignore
elif name in ('cx', 'CX', 'cy', 'cz', 'ch', 'csx', 'swap'):
if len(phases) != 0: raise TypeError("Invalid specification {}".format(c))
g = qasm_gate_table[name](control=argset[0],target=argset[1]) # type: ignore
Expand Down
59 changes: 43 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 All @@ -278,6 +290,9 @@ def compare_gate_matrix_with_qiskit(gates, num_qubits: int, num_angles: int, qas
compare_gate_matrix_with_qiskit(['cu3'], 2, 3, [2])
compare_gate_matrix_with_qiskit(['u'], 1, 3, [2])

# Test native OpenQASM 3 gate.
compare_gate_matrix_with_qiskit(['U'], 1, 3, [3])

@unittest.skipUnless(QuantumCircuit, "qiskit needs to be installed for this test")
def test_qiskit_transpile_pyzx_optimization_round_trip(self):
"""Regression test for issue #102.
Expand Down Expand Up @@ -305,16 +320,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 42343c1

Please sign in to comment.