src/qutip_qip/qasm.py
"""Importation and exportation of QASM circuits"""
import re
import os
from itertools import chain
from copy import deepcopy
import warnings
from math import pi
import numpy as np
from .circuit import QubitCircuit
from .operations import (
controlled_gate,
qasmu_gate,
rz,
snot,
gate_sequence_product,
)
__all__ = ["read_qasm", "save_qasm", "print_qasm", "circuit_to_qasm_str"]
class QasmGate:
"""
Class which stores the gate definitions as specified in the QASM file.
"""
def __init__(self, name, gate_args, gate_regs):
self.name = name
self.gate_args = gate_args
self.gate_regs = gate_regs
self.gates_inside = []
def _get_qiskit_gates():
"""
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.
"""
def u2(args):
return qasmu_gate([np.pi / 2, args[0], args[1]])
def id():
return qasmu_gate([0, 0, 0])
def sdg():
return rz(-1 * np.pi / 2)
def tdg():
return rz(-1 * np.pi / 4)
def cu3(args):
return controlled_gate(qasmu_gate(args))
def ch():
return controlled_gate(snot())
return {"ch": ch, "tdg": tdg, "id": id, "u2": u2, "sdg": sdg, "cu3": cu3}
def _tokenize_line(command):
"""
Tokenize (break into several parts a string of) a single line of QASM code.
Parameters
----------
command : str
One line of QASM code to be broken into "tokens".
Returns
-------
tokens : list of str
The tokens (parts) corresponding to the qasm line taken as input.
"""
# for gates without arguments
if "(" not in command:
tokens = list(chain(*[a.split() for a in command.split(",")]))
tokens = [token.strip() for token in tokens]
# for classically controlled gates
elif re.match(r"\s*if\s*\(", command):
groups = re.match(r"\s*if\s*\((.*)\)\s*(.*)\s+\((.*)\)(.*)", command)
# for classically controlled gates with arguments
if groups:
tokens = ["if", "(", groups.group(1), ")"]
tokens_gate = _tokenize_line(
"{} ({}) {}".format(
groups.group(2), groups.group(3), groups.group(4)
)
)
tokens += tokens_gate
# for classically controlled gates without arguments
else:
groups = re.match(r"\s*if\s*\((.*)\)(.*)", command)
tokens = ["if", "(", groups.group(1), ")"]
tokens_gate = _tokenize_line(groups.group(2))
tokens += tokens_gate
tokens = [token.strip() for token in tokens]
# for gates with arguments
else:
groups = re.match(r"(^.*?)\((.*)\)(.*)", command)
if not groups:
raise SyntaxError("QASM: Incorrect bracket formatting")
tokens = groups.group(1).split()
tokens.append("(")
tokens += groups.group(2).split(",")
tokens.append(")")
tokens += groups.group(3).split(",")
tokens = [token.strip() for token in tokens]
return tokens
def _tokenize(token_cmds):
"""
Tokenize QASM code for processing, i.e. break it into several parts.
Parameters
----------
token_cmds : list of str
Lines of QASM code.
Returns
-------
tokens : list of (list of str)
List of tokens corresponding to each QASM line taken as input.
"""
processed_commands = []
for line in token_cmds:
# carry out some pre-processing for convenience
for c in "[]()":
line = line.replace(c, " " + c + " ")
for c in "{}":
line = line.replace(c, " ; " + c + " ; ")
line_commands = line.split(";")
line_commands = list(filter(lambda x: x != "", line_commands))
for command in line_commands:
tokens = _tokenize_line(command)
processed_commands.append(tokens)
return list(filter(lambda x: x != [], processed_commands))
def _gate_processor(command):
"""
Process tokens for a gate call statement separating them into args and regs.
Processes tokens from a "gate call" (e.g. rx(pi) q[0]) and returns the
tokens for the arguments and registers separately.
"""
gate_args = []
gate_regs = []
tokens = command[1:]
reg_start = 0
# extract arguments
if "(" in tokens and ")" in tokens:
bopen = tokens.index("(")
bclose = tokens.index(")")
gate_args = tokens[bopen + 1 : bclose]
reg_start = bclose + 1
# extract registers
gate_regs = tokens[reg_start:]
return gate_args, gate_regs
class QasmProcessor:
"""
Class which holds variables used in processing QASM code.
"""
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 = {} # 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 != "external_only":
self.qiskitgates = set(
[
"u3",
"u2",
"u1",
"cx",
"id",
"x",
"y",
"z",
"h",
"s",
"sdg",
"t",
"tdg",
"rx",
"ry",
"rz",
"cz",
"cy",
"ch",
"ccx",
"crz",
"cu1",
"cu3",
]
)
self.predefined_gates = self.predefined_gates.union(
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(
"U", ["alpha", "beta", "gamma"], ["q"]
)
self.commands = commands
def _process_includes(self):
"""
QASM allows for code to be specified in additional files with the
".inc" extension, especially to specify gate definitions in terms of
the built-in gates. Process into tokens all the
additional files and insert it into previously processed list.
"""
prev_index = 0
expanded_commands = []
for curr_index, command in enumerate(self.commands):
if command[0] != "include":
continue
filename = command[1].strip('"')
if self.mode == "predefined_only":
warnings.warn(
"Ignoring external gate definition"
" in the predefined_only mode."
)
continue
if os.path.exists(filename):
with open(filename, "r") as f:
qasm_lines = [
line.strip() for line in f.read().splitlines()
]
qasm_lines = list(
filter(lambda x: x[:2] != "//" and x != "", qasm_lines)
)
expanded_commands = (
expanded_commands
+ command[prev_index:curr_index]
+ _tokenize(qasm_lines)
)
prev_index = curr_index + 1
else:
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
def _initialize_pass(self):
"""
Passes through the tokenized commands, create QasmGate objects for
each user-defined gate, process register declarations.
"""
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 = []
for num, command in enumerate(self.commands):
if gate_defn_mode:
if command[0] == "{":
gate_defn_mode = False
open_bracket_mode = True
gate_elems = []
continue
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] == "}":
if not curr_gate.gates_inside:
raise NotImplementedError(
"QASM: opaque gate {} are \
not allowed, please define \
or omit \
them".format(
curr_gate.name
)
)
open_bracket_mode = False
self.gate_names.add(curr_gate.name)
self.qasm_gates[curr_gate.name] = curr_gate
continue
elif command[0] in self.gate_names:
name = command[0]
gate_args, gate_regs = _gate_processor(command)
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:
qubit_name = groups.group(1)
num_regs = int(groups.group(2))
self.qubit_regs[qubit_name] = list(
range(self.num_qubits, self.num_qubits + num_regs)
)
self.num_qubits += num_regs
else:
raise SyntaxError("QASM: incorrect bracket formatting")
elif command[0] == "creg":
groups = re.match(r"(.*)\[(.*)\]", "".join(command[1:]))
if groups:
cbit_name = groups.group(1)
num_regs = int(groups.group(2))
self.cbit_regs[cbit_name] = list(
range(self.num_cbits, self.num_cbits + num_regs)
)
self.num_cbits += num_regs
else:
raise SyntaxError("QASM: incorrect bracket formatting")
elif command[0] == "reset":
raise NotImplementedError(
("QASM: reset functionality " "is not supported.")
)
elif command[0] in ["barrier", "include"]:
continue
else:
unprocessed.append(num)
continue
if open_bracket_mode:
raise SyntaxError("QASM: incorrect bracket formatting")
self.commands = [self.commands[i] for i in unprocessed]
def _custom_gate(self, qc_temp, gate_call):
"""
Recursively process a custom-defined gate with specified arguments
to produce a dummy circuit with all the gates in the custom-defined
gate.
Parameters
----------
qc_temp: :class:`.QubitCircuit`
temporary circuit to process custom gate
gate_call: list of str
tokens corresponding to gate signature/call
"""
gate_name, args, regs = gate_call
gate = self.qasm_gates[gate_name]
args_map = {}
regs_map = {}
# maps variables to supplied arguments, registers
for i, arg in enumerate(gate.gate_args):
args_map[arg] = eval(str(args[i]))
for i, reg in enumerate(gate.gate_regs):
regs_map[reg] = regs[i]
# process all the constituent gates with supplied arguments, registers
for call in gate.gates_inside:
# create function call for the constituent gate
name, com_args, com_regs = call
for arg, real_arg in args_map.items():
com_args = [
command.replace(arg.strip(), str(real_arg))
for command in com_args
]
for reg, real_reg in regs_map.items():
com_regs = [
command.replace(reg.strip(), str(real_reg))
for command in com_regs
]
com_args = [eval(arg) for arg in com_args]
if name in self.predefined_gates:
qc_temp.user_gates = _get_qiskit_gates()
com_regs = [int(reg) for reg in com_regs]
self._add_predefined_gates(qc_temp, name, com_regs, com_args)
else:
self._custom_gate(qc_temp, [name, com_args, com_regs])
def _regs_processor(self, regs, reg_type):
"""
Process register tokens: map them to the :class:`.QubitCircuit` indices
of the respective registers.
Parameters
----------
regs : list of str
Token list corresponding to qubit/cbit register invocations.
reg_type : str
reg_type can be "measure" or "gate" to specify type of required
processing.
Returns
-------
regs list : list of (list of regs)
list of register sets to which circuit operations
are applied.
"""
# turns messy tokens into appropriate form
# ['q', 'p', '[', '0', ']'] -> ['q', 'p[0]']
regs = [reg.replace(" ", "") for reg in regs]
if "[" in regs:
prev_token = ""
new_regs = []
open_bracket_mode = False
for token in regs:
if token == "[":
open_bracket_mode = True
elif open_bracket_mode:
if token == "]":
open_bracket_mode = False
reg_name = new_regs.pop()
new_regs.append(reg_name + "[" + reg_num + "]")
elif token.isdigit():
reg_num = token
else:
raise SyntaxError("QASM: incorrect bracket formatting")
else:
new_regs.append(token)
if open_bracket_mode:
raise SyntaxError("QASM: incorrect bracket formatting")
regs = new_regs
if reg_type == "measure":
# processes register tokens of the form q[i] -> c[i]
groups = re.match(r"(.*)\[(.*)\]->(.*)\[(.*)\]", "".join(regs))
if groups:
qubit_name = groups.group(1)
qubit_ind = int(groups.group(2))
qubit_lst = self.qubit_regs[qubit_name]
if qubit_ind < len(qubit_lst):
qubit = qubit_lst[0] + qubit_ind
else:
raise ValueError("QASM: qubit index out of bounds")
cbit_name = groups.group(3)
cbit_ind = int(groups.group(4))
cbit_lst = self.cbit_regs[cbit_name]
if cbit_ind < len(cbit_lst):
cbit = cbit_lst[0] + cbit_ind
else:
raise ValueError("QASM: cbit index out of bounds")
return [(qubit, cbit)]
# processes register tokens of the form q -> c
else:
qubit_name = regs[0]
cbit_name = regs[2]
qubits = self.qubit_regs[qubit_name]
cbits = self.cbit_regs[cbit_name]
if len(qubits) == len(cbits):
return zip(qubits, cbits)
else:
raise ValueError(
"QASM: qubit and cbit \
register sizes are different"
)
else:
# processes gate tokens to create sets of registers to
# which the gates are applied.
new_regs = []
expand = 0
for reg in regs:
if "[" in reg:
groups = re.match(r"(.*)\[(.*)\]", "".join(reg))
qubit_name = groups.group(1)
qubit_ind = int(groups.group(2))
qubit_lst = self.qubit_regs[qubit_name]
if qubit_ind < len(qubit_lst):
qubit = qubit_lst[0] + qubit_ind
else:
qubit_name = reg
qubit = self.qubit_regs[qubit_name]
expand = len(qubit)
new_regs.append(qubit)
if expand:
return zip(
*list(
map(
lambda x: (
x if isinstance(x, list) else [x] * expand
),
new_regs,
)
)
)
else:
return [new_regs]
def _add_qiskit_gates(
self,
qc,
name,
regs,
args=None,
classical_controls=None,
classical_control_value=None,
):
"""
Add any gates that are pre-defined in qiskit-style exported
qasm file with included "qelib1.inc".
Parameters
----------
qc : :class:`.QubitCircuit`
the circuit to which the gate is added.
name : str
name of gate to be added.
regs : list of int
list of qubit register indices to add gates to.
args : float, optional
value of args supplied to the gate.
classical_controls : list of int, optional
indices of classical bits to control gate on.
classical_control_value : int, optional
value of classical bits to control on, the classical controls are
interpreted as an integer with lowest bit being the first one.
If not specified, then the value is interpreted to be
2 ** len(classical_controls) - 1
(i.e. all classical controls are 1).
"""
gate_name_map_1q = {
"x": "X",
"y": "Y",
"z": "Z",
"h": "SNOT",
"t": "T",
"s": "S",
"sdg": "sdg",
"tdg": "tdg",
"rx": "RX",
"ry": "RY",
"rz": "RZ",
}
if len(args) == 0:
args = None
elif len(args) == 1:
args = args[0]
if name == "u3":
qc.add_gate(
"QASMU",
targets=regs[0],
arg_value=args,
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "u2":
qc.add_gate(
"u2",
targets=regs[0],
arg_value=args,
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "u1":
qc.add_gate(
"RZ",
targets=regs[0],
arg_value=args,
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "cz":
qc.add_gate(
"CZ",
targets=regs[1],
controls=regs[0],
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "cy":
qc.add_gate(
"CY",
targets=regs[1],
controls=regs[0],
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "ch":
qc.add_gate(
"ch",
targets=regs,
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "ccx":
qc.add_gate(
"TOFFOLI",
targets=regs[2],
controls=regs[:2],
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "crz":
qc.add_gate(
"CRZ",
targets=regs[1],
controls=regs[0],
arg_value=args,
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "cu1":
qc.add_gate(
"CPHASE",
targets=regs[1],
controls=regs[0],
arg_value=args,
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "cu3":
qc.add_gate(
"cu3",
targets=[regs[0], regs[1]],
arg_value=args,
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "cx":
qc.add_gate(
"CNOT",
targets=int(regs[1]),
controls=int(regs[0]),
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name in gate_name_map_1q:
if args == []:
args = None
qc.add_gate(
gate_name_map_1q[name],
targets=regs[0],
arg_value=args,
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
def _add_predefined_gates(
self,
qc,
name,
com_regs,
com_args,
classical_controls=None,
classical_control_value=None,
):
"""
Add any gates that are pre-defined and/or inbuilt
in our circuit.
Parameters
----------
qc : :class:`.QubitCircuit`
the circuit to which the gate is added.
name : str
name of gate to be added.
regs : list of int
list of qubit register indices to add gates to.
args : float, optional
value of args supplied to the gate.
classical_controls : list of int, optional
indices of classical bits to control gate on.
classical_control_value : int, optional
value of classical bits to control on, the classical controls are
interpreted as an integer with lowest bit being the first one.
If not specified, then the value is interpreted to be
2 ** len(classical_controls) - 1
(i.e. all classical controls are 1).
"""
if name == "CX":
qc.add_gate(
"CNOT",
targets=int(com_regs[1]),
controls=int(com_regs[0]),
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name == "U":
qc.add_gate(
"QASMU",
targets=int(com_regs[0]),
arg_value=[float(arg) for arg in com_args],
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
elif name in self.qiskitgates:
self._add_qiskit_gates(
qc,
name,
com_regs,
com_args,
classical_controls,
classical_control_value,
)
def _gate_add(
self,
qc,
command,
custom_gates,
classical_controls=None,
classical_control_value=None,
):
"""
Add gates to :class:`.QubitCircuit` from processed tokens,
define custom gates if necessary.
Parameters
----------
qc : :class:`.QubitCircuit`
circuit object to which gate is added
command: list of str
list of tokens corresponding to gate application
custom_gates: {gate name : gate function or unitary}
dictionary of user gates defined for qutip
classical_controls : int or list of int, optional
indices of classical bits to control gate on.
classical_control_value : int, optional
value of classical bits to control on, the classical controls are
interpreted as an integer with lowest bit being the first one.
If not specified, then the value is interpreted to be
2 ** len(classical_controls) - 1
(i.e. all classical controls are 1).
"""
args, regs = _gate_processor(command)
reg_set = self._regs_processor(regs, "gate")
if args:
gate_name = "{}({})".format(command[0], ",".join(args))
else:
gate_name = "{}".format(command[0])
# creates custom-gate (if required) using gate defn and provided args
if (
command[0] not in self.predefined_gates
and command[0] not in custom_gates
):
n = len(reg_set[0])
qc_temp = QubitCircuit(n)
self._custom_gate(
qc_temp, [command[0], args, [str(i) for i in range(n)]]
)
unitary_mat = qc_temp.compute_unitary()
custom_gates[gate_name] = unitary_mat
qc.user_gates = custom_gates
# adds gate to the QubitCircuit
for regs in reg_set:
regs = [int(i) for i in regs]
if command[0] in self.predefined_gates:
args = [eval(arg) for arg in args]
self._add_predefined_gates(
qc,
command[0],
regs,
args,
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
else:
if not isinstance(regs, list):
regs = [regs]
qc.add_gate(
gate_name,
targets=regs,
classical_controls=classical_controls,
classical_control_value=classical_control_value,
)
def _final_pass(self, qc):
"""
Take a blank circuit, add all the gates and measurements specified
by QASM.
"""
custom_gates = {}
if self.mode != "external_only":
custom_gates = _get_qiskit_gates()
for command in self.commands:
if command[0] in self.gate_names:
# adds gates to the QubitCircuit
self._gate_add(qc, command, custom_gates)
elif command[0] == "measure":
# adds measurement to the QubitCircuit
reg_set = self._regs_processor(command[1:], "measure")
for regs in reg_set:
qc.add_measurement(
"M", targets=[regs[0]], classical_store=regs[1]
)
elif command[0] == "if":
warnings.warn(
(
"Information about individual registers"
" is not preserved in QubitCircuit"
)
)
# adds classically controlled gates to the QubitCircuit
cbit_reg, classical_control_value = command[2].split("==")
cbit_inds = self.cbit_regs[cbit_reg]
classical_control_value = int(classical_control_value)
self._gate_add(
qc,
command[4:],
custom_gates,
cbit_inds,
classical_control_value,
)
else:
err = "QASM: {} is not a valid QASM command.".format(
command[0]
)
raise SyntaxError(err)
def read_qasm(qasm_input, mode="default", version="2.0", strmode=False):
"""
Read OpenQASM intermediate representation
(https://github.com/Qiskit/openqasm) and return
a :class:`.QubitCircuit` and state inputs as specified in the
QASM file.
Parameters
----------
qasm_input : str
File location or String Input for QASM file to be imported. In case of
string input, the parameter strmode must be True.
mode : str
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
if specified as True, indicates that qasm_input is in string format
rather than from file.
Returns
-------
qc : :class:`.QubitCircuit`
Returns a :class:`.QubitCircuit` object specified in the QASM file.
"""
if strmode:
qasm_lines = qasm_input.splitlines()
else:
f = open(qasm_input, "r")
qasm_lines = f.read().splitlines()
f.close()
# split input into lines and ignore comments
qasm_lines = [line.strip() for line in qasm_lines]
qasm_lines = list(filter(lambda x: x[:2] != "//" and x != "", qasm_lines))
# QASMBench Benchmark Suite has lines that have comments after instructions.
# Not sure if QASM standard allows this.
for i in range(len(qasm_lines)):
qasm_line = qasm_lines[i]
loc_comment = qasm_line.find("//")
if loc_comment >= 0:
qasm_line = qasm_line[0:loc_comment]
qasm_lines[i] = qasm_line
if version != "2.0":
raise NotImplementedError(
"QASM: Only OpenQASM 2.0 \
is currently supported."
)
if qasm_lines.pop(0) != "OPENQASM 2.0;":
raise SyntaxError("QASM: File does not contain QASM 2.0 header")
qasm_obj = QasmProcessor(qasm_lines, mode=mode, version=version)
qasm_obj.commands = _tokenize(qasm_obj.commands)
qasm_obj._process_includes()
qasm_obj._initialize_pass()
qc = QubitCircuit(qasm_obj.num_qubits, num_cbits=qasm_obj.num_cbits)
qasm_obj._final_pass(qc)
return qc
_GATE_NAME_TO_QASM_NAME = {
"QASMU": "U",
"RX": "rx",
"RY": "ry",
"RZ": "rz",
"SNOT": "h",
"X": "x",
"Y": "y",
"Z": "z",
"S": "s",
"T": "t",
"CRZ": "crz",
"CNOT": "cx",
"TOFFOLI": "ccx",
}
class QasmOutput:
"""
Class for QASM export.
Parameters
----------
version: str, optional
OpenQASM version, currently must be "2.0" necessarily.
"""
def __init__(self, version="2.0"):
self.version = version
self.lines = []
self.gate_name_map = deepcopy(_GATE_NAME_TO_QASM_NAME)
def output(self, line="", n=0):
"""
Pipe QASM output string to QasmOutput's lines variable.
Parameters
----------
line: str, optional
string to be appended to QASM output.
n: int, optional
number of blank lines to be appended to QASM output.
"""
if line:
self.lines.append(line)
self.lines = self.lines + [""] * n
def _flush(self):
"""
Resets QasmOutput variables.
"""
self.lines = []
self.gate_name_map = deepcopy(_GATE_NAME_TO_QASM_NAME)
def _qasm_str(self, q_name, q_controls, q_targets, q_args):
"""
Returns QASM string for gate definition or gate application given
name, registers, arguments.
"""
if not q_controls:
q_controls = []
q_regs = q_controls + q_targets
if isinstance(q_targets[0], int):
q_regs = ",".join(["q[{}]".format(reg) for reg in q_regs])
else:
q_regs = ",".join(q_regs)
if q_args:
if isinstance(q_args, list):
q_args = ",".join([str(arg) for arg in q_args])
return "{}({}) {};".format(q_name, q_args, q_regs)
else:
return "{} {};".format(q_name, q_regs)
def _qasm_defn_from_resolved(self, curr_gate, gates_lst):
"""
Resolve QASM definition of QuTiP gate in terms of component gates.
Parameters
----------
curr_gate: :class:`~.operations.Gate`
QuTiP gate which needs to be resolved into component gates.
gates_lst: list of :class:`~.operations.Gate`
list of gate that constitute QASM definition of self.
"""
forbidden_gates = ["GLOBALPHASE", "PHASEGATE"]
reg_map = ["a", "b", "c"]
q_controls = None
if curr_gate.controls:
q_controls = [reg_map[i] for i in curr_gate.controls]
q_targets = [reg_map[i] for i in curr_gate.targets]
arg_name = None
if curr_gate.arg_value:
arg_name = "theta"
self.output(
"gate {} {{".format(
self._qasm_str(
curr_gate.name.lower(), q_controls, q_targets, arg_name
)[:-1]
)
)
for gate in gates_lst:
if gate.name in self.gate_name_map:
gate.targets = [reg_map[i] for i in gate.targets]
if gate.controls:
gate.controls = [reg_map[i] for i in gate.controls]
self.output(
self._qasm_str(
self.gate_name_map[gate.name],
gate.controls,
gate.targets,
gate.arg_value,
)
)
elif gate.name in forbidden_gates:
continue
else:
raise ValueError(
(
"The given resolved gate {} cannot be defined"
" in QASM format"
).format(curr_gate.name)
)
self.output("}")
def _qasm_defn_resolve(self, gate):
"""
Resolve QASM definition of QuTiP gate if possible.
Parameters
----------
gate: :class:`~.operations.Gate`
QuTiP gate which needs to be resolved into component gates.
"""
qc = QubitCircuit(3)
gates_lst = []
if gate.name == "CSIGN":
qc._gate_CSIGN(gate, gates_lst)
else:
err_msg = "No definition specified for {} gate".format(gate.name)
raise NotImplementedError(err_msg)
self._qasm_defn_from_resolved(gate, gates_lst)
self.gate_name_map[gate.name] = gate.name.lower()
def _qasm_defns(self, gate):
"""
Define QASM gates for QuTiP gates that do not have QASM counterparts.
Parameters
----------
gate: :class:`~.operations.Gate`
QuTiP gate which needs to be defined in QASM format.
"""
if gate.name == "CRY":
gate_def = "gate cry(theta) a,b { cu3(theta,0,0) a,b; }"
elif gate.name == "CRX":
gate_def = "gate crx(theta) a,b { cu3(theta,-pi/2,pi/2) a,b; }"
elif gate.name == "SQRTNOT":
gate_def = "gate sqrtnot a {h a; u1(-pi/2) a; h a; }"
elif gate.name == "CS":
gate_def = "gate cs a,b { cu1(pi/2) a,b; }"
elif gate.name == "CT":
gate_def = "gate ct a,b { cu1(pi/4) a,b; }"
elif gate.name == "SWAP":
gate_def = "gate swap a,b { cx a,b; cx b,a; cx a,b; }"
else:
self._qasm_defn_resolve(gate)
return
self.output("// QuTiP definition for gate {}".format(gate.name))
self.output(gate_def)
self.gate_name_map[gate.name] = gate.name.lower()
def qasm_name(self, gate_name):
"""
Return QASM gate name for corresponding QuTiP gate.
Parameters
----------
gate_name: str
QuTiP gate name.
"""
if gate_name in self.gate_name_map:
return self.gate_name_map[gate_name]
else:
return None
def is_defined(self, gate_name):
"""
Check if QASM gate definition exists for QuTiP gate.
Parameters
----------
gate_name: str
QuTiP gate name.
"""
return gate_name in self.gate_name_map
def _qasm_output(self, qc):
"""
QASM output handler.
Parameters
----------
qc : :class:`.QubitCircuit`
circuit object to produce QASM output for.
"""
self._flush()
self.output("// QASM 2.0 file generated by QuTiP", 1)
if self.version == "2.0":
self.output("OPENQASM 2.0;")
else:
raise NotImplementedError(
"QASM: Only OpenQASM 2.0 \
is currently supported."
)
self.output('include "qelib1.inc";', 1)
qc._to_qasm(self)
return self.lines
def print_qasm(qc):
"""
Print QASM output of circuit object.
Parameters
----------
qc : :class:`.QubitCircuit`
circuit object to produce QASM output for.
"""
qasm_out = QasmOutput("2.0")
lines = qasm_out._qasm_output(qc)
for line in lines:
print(line)
def circuit_to_qasm_str(qc):
"""
Return QASM output of circuit object as string
Parameters
----------
qc : :class:`.QubitCircuit`
circuit object to produce QASM output for.
Returns
-------
output: str
string corresponding to QASM output.
"""
qasm_out = QasmOutput("2.0")
lines = qasm_out._qasm_output(qc)
output = ""
for line in lines:
output += line + "\n"
return output
def save_qasm(qc, file_loc):
"""
Save QASM output of circuit object to file.
Parameters
----------
qc : :class:`.QubitCircuit`
circuit object to produce QASM output for.
"""
qasm_out = QasmOutput("2.0")
lines = qasm_out._qasm_output(qc)
with open(file_loc, "w") as f:
for line in lines:
f.write("{}\n".format(line))