Skip to content

Commit

Permalink
Merge pull request #224 from BoxiLi/qasm
Browse files Browse the repository at this point in the history
Update the parsing mode for read_qasm
  • Loading branch information
BoxiLi committed Apr 20, 2024
1 parent 9fa3d64 commit eede9af
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 32 deletions.
1 change: 0 additions & 1 deletion doc/source/w-state.qasm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Name of Experiment: W-state v1

OPENQASM 2.0;
include "qelib1.inc";


qreg q[3];
Expand Down
60 changes: 41 additions & 19 deletions src/qutip_qip/qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,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 @@ -180,17 +179,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 @@ -222,6 +226,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 @@ -246,7 +252,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 @@ -265,8 +275,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 @@ -276,8 +292,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 @@ -291,6 +309,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 @@ -313,11 +332,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 @@ -648,7 +667,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 @@ -716,7 +735,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:
self._add_qiskit_gates(
qc,
name,
Expand Down Expand Up @@ -809,7 +828,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 @@ -848,7 +867,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 @@ -861,10 +880,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
1 change: 0 additions & 1 deletion tests/qasm_files/bracket_error.qasm
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
OPENQASM 2.0;
include "qelib1.inc";

qreg q[2];
creg c[4];
Expand Down
1 change: 0 additions & 1 deletion tests/qasm_files/command_error.qasm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Implementation of Deutsch algorithm with two qubits for f(x)=x
OPENQASM 2.0;
include "qelib1.inc";

qreg q[5];
creg c[5];
Expand Down
2 changes: 0 additions & 2 deletions tests/qasm_files/qasm_error.qasm
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
include "qelib1.inc";

qreg q[2];
creg c[4];
gate cu1fixed (a) c,t {
Expand Down
2 changes: 1 addition & 1 deletion tests/qasm_files/qft.qasm
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
OPENQASM 2.0;
include "qelib1.inc";

qreg q[4];
x q[0];
x q[2];
Expand Down
1 change: 0 additions & 1 deletion tests/qasm_files/teleportation.qasm
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
OPENQASM 2.0;
include "qelib1.inc";

qreg q[3];
creg c0[1];
Expand Down
1 change: 0 additions & 1 deletion tests/qasm_files/test_add.qasm
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
OPENQASM 2.0;
include "qelib1.inc";

qreg q[2];
creg c[2];
Expand Down
1 change: 0 additions & 1 deletion tests/qasm_files/test_custom_gates.qasm
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
OPENQASM 2.0;
include "qelib1.inc";

qreg q[2];

Expand Down
1 change: 0 additions & 1 deletion tests/qasm_files/w-state.qasm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Name of Experiment: W-state v1

OPENQASM 2.0;
include "qelib1.inc";


qreg q[4];
Expand Down
1 change: 0 additions & 1 deletion tests/qasm_files/w-state_with_comments.qasm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Name of Experiment: W-state v1

OPENQASM 2.0;
include "qelib1.inc";


qreg q[4]; // This comments also should be handled.
Expand Down
73 changes: 71 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,11 @@ 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():
warnings.simplefilter("ignore")
read_qc = read_qasm(circuit_to_qasm_str(qc), strmode=True)

props = qc.propagators()
read_props = read_qc.propagators()
Expand All @@ -145,3 +151,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 eede9af

Please sign in to comment.