src/qutip_qip/qasm.py
"""Importation and exportation of QASM circuits""" import reimport osfrom itertools import chainfrom copy import deepcopyimport warnings from math import piimport numpy as np from .circuit import QubitCircuitfrom .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 Function `_initialize_pass` has a Cognitive Complexity of 32 (exceeds 20 allowed). Consider refactoring. 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 = TrueSimilar blocks of code found in 2 locations. Consider refactoring. 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") Similar blocks of code found in 2 locations. Consider refactoring. 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]) Function `_regs_processor` has a Cognitive Complexity of 50 (exceeds 20 allowed). Consider refactoring. 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))