Skip to content

Commit

Permalink
Update the parsing mode for read_qasm
Browse files Browse the repository at this point in the history
Remove the ambiguous mode "qiskit" and add "default", "predefine_only" and "external_only"
  • Loading branch information
BoxiLi committed Dec 9, 2023
1 parent 0a7cf78 commit 02d9147
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 21 deletions.
60 changes: 41 additions & 19 deletions src/qutip_qip/qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ def __init__(self, name, gate_args, gate_regs):

def _get_qiskit_gates():
"""
Create and return a dictionary containing custom gates needed
for "qiskit" mode. These include a subset of gates usually defined
in the file "qelib1.inc".
Create and return a dictionary containing a few commonly used qiskit gates
that are not predefined in qutip-qip.
Returns a dictionary mapping gate names to QuTiP gates.
"""
Expand Down Expand Up @@ -179,17 +178,22 @@ class QasmProcessor:
Class which holds variables used in processing QASM code.
"""

def __init__(self, commands, mode="qiskit", version="2.0"):
def __init__(self, commands, mode="default", version="2.0"):
self.qubit_regs = {}
self.cbit_regs = {}
self.num_qubits = 0
self.num_cbits = 0
self.qasm_gates = {}
self.qasm_gates = {} # Custom defined QASM gates
if mode not in ["default", "external_only", "predefined_only"]:
warnings.warn(
"Unknown parsing mode, using the default mode instead."
)
mode = "default"
self.mode = mode
self.version = version
self.predefined_gates = set(["CX", "U"])

if self.mode == "qiskit":
if self.mode != "external_only":
self.qiskitgates = set(
[
"u3",
Expand Down Expand Up @@ -221,6 +225,8 @@ def __init__(self, commands, mode="qiskit", version="2.0"):
self.qiskitgates
)

# A set of available gates, including both predefined gate and
# custom defined gates from `qelib1.inc` (added later).
self.gate_names = deepcopy(self.predefined_gates)
for gate in self.predefined_gates:
self.qasm_gates[gate] = QasmGate(
Expand All @@ -245,7 +251,11 @@ def _process_includes(self):

filename = command[1].strip('"')

if self.mode == "qiskit" and filename == "qelib1.inc":
if self.mode == "predefined_only":
warnings.warn(
"Ignoring external gate definition"
" in the predefined_only mode."
)
continue

if os.path.exists(filename):
Expand All @@ -264,8 +274,14 @@ def _process_includes(self):
)
prev_index = curr_index + 1
else:
raise ValueError(command[1] + ": such a file does not exist")
if self.mode == "default":
warnings.warn(command[1] + "not found, ignored.")
else:
raise ValueError(
command[1] + ": such a file does not exist"
)

# Insert the custom gate configurations to the list of commands
expanded_commands += self.commands[prev_index:]
self.commands = expanded_commands

Expand All @@ -275,8 +291,10 @@ def _initialize_pass(self):
each user-defined gate, process register declarations.
"""

gate_defn_mode = False
open_bracket_mode = False
gate_defn_mode = False # If in the middle of defining a custom gate
open_bracket_mode = (
False # If in the middle of defining a decomposition
)

unprocessed = []

Expand All @@ -290,6 +308,7 @@ def _initialize_pass(self):
else:
raise SyntaxError("QASM: incorrect bracket formatting")
elif open_bracket_mode:
# Define the decomposition of custom QASM gate
if command[0] == "{":
raise SyntaxError("QASM: incorrect bracket formatting")
elif command[0] == "}":
Expand All @@ -312,11 +331,11 @@ def _initialize_pass(self):
gate_added = self.qasm_gates[name]
curr_gate.gates_inside.append([name, gate_args, gate_regs])
elif command[0] == "gate":
# Custom definition of gates.
gate_name = command[1]
gate_args, gate_regs = _gate_processor(command[1:])
curr_gate = QasmGate(gate_name, gate_args, gate_regs)
gate_defn_mode = True

elif command[0] == "qreg":
groups = re.match(r"(.*)\[(.*)\]", "".join(command[1:]))
if groups:
Expand Down Expand Up @@ -647,7 +666,7 @@ def _add_qiskit_gates(
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
if name == "cx":
elif name == "cx":
qc.add_gate(
"CNOT",
targets=int(regs[1]),
Expand Down Expand Up @@ -715,7 +734,7 @@ def _add_predefined_gates(
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name in self.qiskitgates and self.mode == "qiskit":
elif name in self.qiskitgates and self.mode != "external_only":
self._add_qiskit_gates(
qc,
name,
Expand Down Expand Up @@ -808,7 +827,7 @@ def _final_pass(self, qc):
"""

custom_gates = {}
if self.mode == "qiskit":
if self.mode != "external_only":
custom_gates = _get_qiskit_gates()

for command in self.commands:
Expand Down Expand Up @@ -847,7 +866,7 @@ def _final_pass(self, qc):
raise SyntaxError(err)


def read_qasm(qasm_input, mode="qiskit", version="2.0", strmode=False):
def read_qasm(qasm_input, mode="default", version="2.0", strmode=False):
"""
Read OpenQASM intermediate representation
(https://github.com/Qiskit/openqasm) and return
Expand All @@ -860,10 +879,13 @@ def read_qasm(qasm_input, mode="qiskit", version="2.0", strmode=False):
File location or String Input for QASM file to be imported. In case of
string input, the parameter strmode must be True.
mode : str
QASM mode to be read in. When mode is "qiskit",
the "qelib1.inc" include is automatically included,
without checking externally. Otherwise, each include is
processed.
Parsing mode for the qasm file.
- "default": For predefined gates in qutip-qip, use the predefined
version, otherwise use the custom gate defined in qelib1.inc.
The predefined gate can usually be further processed (e.g. decomposed)
within qutip-qip.
- "predefined_only": Use only the predefined gates in qutip-qip.
- "external_only": Use only the gate defined in qelib1.inc, except for CX and QASMU gate.
version : str
QASM version of the QASM file. Only version 2.0 is currently supported.
strmode : bool
Expand Down
72 changes: 70 additions & 2 deletions tests/test_qasm.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import pytest
import numpy as np
from pathlib import Path
import warnings

import qutip
from qutip_qip.qasm import read_qasm, circuit_to_qasm_str
from qutip_qip.circuit import QubitCircuit
from qutip import tensor, rand_ket, basis, rand_dm, identity
from qutip_qip.operations import cnot, ry, Measurement
from qutip_qip.operations import cnot, ry, Measurement, swap


@pytest.mark.parametrize(["filename", "error", "error_message"], [
Expand Down Expand Up @@ -127,7 +129,10 @@ def test_export_import():
qc.add_gate("T", targets=[0])
# qc.add_gate("CSIGN", targets=[0], controls=[1])

read_qc = read_qasm(circuit_to_qasm_str(qc), strmode=True)
# The generated code by default has a inclusion statement of
# qelib1.inc, which will trigger a warning when read.
with warnings.catch_warnings():
read_qc = read_qasm(circuit_to_qasm_str(qc), strmode=True)

props = qc.propagators()
read_props = read_qc.propagators()
Expand All @@ -145,3 +150,66 @@ def test_read_qasm():
qc = read_qasm(filepath)
qc2 = read_qasm(filepath2)
assert True


def test_parsing_mode(tmp_path):
mode = "qiskit"
qasm_input_string = (
'OPENQASM 2.0;\n\ncreg c[2];'
'\nqreg q[2];cx q[0],q[1];\n'
)
with pytest.warns(UserWarning) as record_warning:
read_qasm(
qasm_input_string,
mode=mode,
strmode=True,
)
assert "Unknown parsing mode" in record_warning[0].message.args[0]

mode = "predefined_only"
qasm_input_string = (
'OPENQASM 2.0;\ninclude "qelib1.inc"\n\ncreg c[2];'
'\nqreg q[2];swap q[0],q[1];\n'
)
with pytest.raises(SyntaxError):
with pytest.warns(UserWarning) as record_warning:
circuit = read_qasm(
qasm_input_string,
mode=mode,
strmode=True,
)
assert (
"Ignoring external gate definition in the predefined_only mode."
in record_warning[0].message.args[0]
)

mode = "external_only"
file_path = tmp_path / "custom_swap.inc"
file_path.write_text(
"gate cx c,t { CX c,t; }\n"
"gate swap a,b { cx a,b; cx b,a; cx a,b; }\n"
)
qasm_input_string = (
'OPENQASM 2.0;\ninclude "'
+ str(file_path)
+ '"\ncreg c[2];'
'\nqreg q[2];swap q[0],q[1];\n'
)
circuit = read_qasm(
qasm_input_string,
mode=mode,
strmode=True,
)
propagator = circuit.compute_unitary()

fidelity = qutip.average_gate_fidelity(propagator, swap())
pytest.approx(fidelity, 1.0)

circuit = read_qasm(
qasm_input_string,
strmode=True,
)
propagator = circuit.compute_unitary()

fidelity = qutip.average_gate_fidelity(propagator, swap())
pytest.approx(fidelity, 1.0)

0 comments on commit 02d9147

Please sign in to comment.