Source code for tequila_code.circuit.qasm

"""
Export QCircuits as qasm code

OPENQASM version 2.0 specification from:
A. W. Cross, L. S. Bishop, J. A. Smolin, and J. M. Gambetta, e-print arXiv:1707.03429v2 [quant-ph] (2017).
https://arxiv.org/pdf/1707.03429v2.pdf
"""
from tequila import TequilaException
from tequila.circuit import QCircuit
from tequila.circuit.compiler import CircuitCompiler
import tequila.circuit.gates as gates
from numpy import pi
from typing import Dict
import typing


[docs] def export_open_qasm(circuit: QCircuit, variables=None, version: str = "2.0", filename: str = None, zx_calculus: bool = False) -> str: """ Allow export to different versions of OpenQASM Args: circuit: to be exported to OpenQASM variables: optional dictionary with values for variables version: of the OpenQASM specification, optional filename: optional file name to save the generated OpenQASM code zx_calculus: indicate if y-gates must be transformed to xz equivalents Returns: str: OpenQASM string """ if version == "2.0": result = convert_to_open_qasm_2(circuit=circuit, variables=variables, zx_calculus=zx_calculus) else: return "Unsupported OpenQASM version : " + version # TODO: export to version 3 if filename is not None: with open(filename, "w") as file: file.write(result) file.close() return result
[docs] def import_open_qasm(qasm_code: str, version: str = "2.0", rigorous: bool = True) -> QCircuit: """ Allow import from different versions of OpenQASM Args: qasm_code: string with the OpenQASM code version: of the OpenQASM specification, optional rigorous: indicates whether the QASM code should be read rigorously Returns: QCircuit: equivalent to the OpenQASM code received """ if version == "2.0": result = parse_from_open_qasm_2(qasm_code=qasm_code, rigorous=rigorous) else: return "Unsupported OpenQASM version : " + version # TODO: export to version 3 return result
[docs] def import_open_qasm_from_file(filename: str, version: str = "2.0", rigorous: bool = True) -> QCircuit: """ Allow import from different versions of OpenQASM from a file Args: filename: string with the file name with the OpenQASM code variables: optional dictionary with values for variables version: of the OpenQASM specification, optional rigorous: indicates whether the QASM code should be read rigorously Returns: QCircuit: equivalent to the OpenQASM code received """ with open(filename, "r") as file: qasm_code = file.read() file.close() return import_open_qasm(qasm_code, version=version, rigorous=rigorous)
[docs] def convert_to_open_qasm_2(circuit: QCircuit, variables=None, zx_calculus: bool = False) -> str: """ Allow export to OpenQASM version 2.0 Args: circuit: to be exported to OpenQASM variables: optional dictionary with values for variables zx_calculus: indicate if y-gates must be transformed to xz equivalents Returns: str: OpenQASM string """ if variables is None and not (len(circuit.extract_variables()) == 0): raise TequilaException( "You called export_open_qasm for a parametrized type but forgot to pass down the variables: {}".format( circuit.extract_variables())) compiler = CircuitCompiler(multitarget=True, multicontrol=False, trotterized=True, generalized_rotation=True, exponential_pauli=True, controlled_exponential_pauli=True, hadamard_power=True, controlled_power=True, power=True, toffoli=True, controlled_phase=True, phase=True, phase_to_z=True, controlled_rotation=True, swap=True, cc_max=True, gradient_mode=False, ry_gate=zx_calculus, y_gate=zx_calculus, ch_gate=zx_calculus) compiled = compiler(circuit, variables=None) result = "OPENQASM 2.0;\ninclude \"qelib1.inc\";\n" qubits_names: Dict[int, str] = {} for q in compiled.qubits: name = "q[" + str(q) + "]" qubits_names[q] = name result += "qreg q[" + str(compiled.n_qubits) + "];\n" result += "creg c[" + str(compiled.n_qubits) + "];\n" for g in compiled.gates: control_str = '' if g.is_controlled(): if len(g.control) > 2: raise TequilaException( "Multi-controls beyond 2 not yet supported for OpenQASM 2.0. Gate was:\n{}".format(g)) controls = list(map(lambda c: qubits_names[c], g.control)) control_str = ','.join(controls) + ',' gate_name = name_and_params(g, variables) for t in g.target: result += gate_name result += control_str result += qubits_names[t] result += ";\n" return result
[docs] def name_and_params(g, variables): """ Determines the quantum gate name and its parameters if applicable Args: g: gate to get its name variables: dictionary with values for variables Returns: str: name (and parameter) to the gate specified """ res = "" for c in range(len(g.control)): res += "c" res += g.name.lower() if hasattr(g, "parameter") and g.parameter is not None: res += "(" + str(g.parameter(variables)) + ")" res += " " return res
[docs] def parse_from_open_qasm_2(qasm_code: str, rigorous: bool = True) -> QCircuit: lines = qasm_code.splitlines() clean_code = [] # ignore comments for line in lines: if line.find("//") != -1: clean_line = line[0:line.find("//")].strip() else: clean_line = line.strip() if clean_line: clean_code.append(clean_line) if clean_code[0].startswith("OPENQASM"): clean_code.pop(0) elif rigorous: raise TequilaException("File must start with the 'OPENQASM' directive") if clean_code[0].startswith('include "qelib1.inc";'): clean_code.pop(0) elif rigorous: raise TequilaException("File must import standard library (qelib1.inc)") code_circuit = "\n".join(clean_code) # separate the custom command definitions from the normal commands custom_gates_map: Dict[str, QCircuit] = {} while True: i = code_circuit.find("gate ") if i == -1: break j = code_circuit.find("}", i) custom_name, custom_circuit = parse_custom_gate(code_circuit[i:j + 1], custom_gates_map=custom_gates_map) custom_gates_map[custom_name] = custom_circuit code_circuit = code_circuit[:i] + code_circuit[j + 1:] # parse regular commands commands = [s.strip() for s in code_circuit.split(";") if s.strip()] qregisters: Dict[str, int] = {} circuit = QCircuit() for c in commands: partial_circuit = parse_command(command=c, custom_gates_map=custom_gates_map, qregisters=qregisters) if partial_circuit is not None: circuit += partial_circuit return circuit
[docs] def parse_custom_gate(gate_custom: str, custom_gates_map: Dict[str, QCircuit]) -> (str, QCircuit): """ Parse custom gates code Args: gate_custom: code with custom gates """ gate_custom = gate_custom[5:] spec, body = gate_custom.split("{", 1) if "(" in spec: i = spec.find("(") j = spec.find(")") if spec[i + 1:j].strip(): raise TequilaException("Parameters for custom gates not supported: {}".format(spec)) spec = spec[:i] + spec[j + 1:] spec = spec.strip() if " " in spec: name, qargs = spec.split(" ", 1) name = name.strip() qargs = qargs.strip() else: raise TequilaException("Custom gate specification doesn't have any arguments: {}".format(spec)) custom_qregisters: Dict[str, int] = {} for qarg in qargs.split(','): custom_qregisters[qarg] = len(custom_qregisters) body = body[:-1].strip() commands = [s.strip() for s in body.split(";") if s.strip()] custom_circuit = QCircuit() for c in commands: partial_circuit = parse_command(command=c, custom_gates_map=custom_gates_map, qregisters=custom_qregisters) if partial_circuit is not None: custom_circuit += partial_circuit return name, custom_circuit
[docs] def parse_command(command: str, custom_gates_map: Dict[str, QCircuit], qregisters: Dict[str, int]) -> QCircuit: """ Parse qasm code command Args: command: open qasm code to be parsed custom_gates_map: map with custom gates """ name, rest = command.split(" ", 1) if name in ("barrier", "creg", "measure", "id"): return None if name in ("opaque", "if"): raise TequilaException("Unsupported operation {}".format(command)) args = [s.strip() for s in rest.split(",") if s.strip()] if name == "qreg": regname, sizep = args[0].split("[", 1) size = int(sizep[:-1]) for i in range(size): qregisters[regname + "[" + str(i) + "]"] = len(qregisters) return None for arg in args: if not (arg in qregisters or arg in [key.split("[",1)[0] for key in qregisters.keys()]): raise TequilaException("Invalid register {}".format(arg)) if name in custom_gates_map: custom_circuit = custom_gates_map[name] qregisters_values = [] for a in args: qregisters_values.append(get_qregister(a, qregisters)) return apply_custom_gate(custom_circuit=custom_circuit, qregisters_values=qregisters_values) if name in ("x", "y", "z", "h", "cx", "cy", "cz", "ch"): target = get_qregister(args[0], qregisters) control = None if name[0].lower() == 'c': control = get_qregister(args[0], qregisters) target = get_qregister(args[1], qregisters) name = name[1] G = getattr(gates, name.upper()) return G(control=control, target=target) if name in ("ccx", "ccy", "ccz"): G = getattr(gates, name[2].upper()) control = [get_qregister(args[0], qregisters), get_qregister(args[1], qregisters)] target = get_qregister(args[2], qregisters) return G(control=control, target=target) if name.startswith("rx(") or name.startswith("ry(") or name.startswith("rz(") or \ name.startswith("crx(") or name.startswith("cry(") or name.startswith("crz("): angle = get_angle(name)[0] i = name.find('(') name = name[0:i] name = name.upper() name = [x for x in name] name[-1] = name[-1].lower() name = "".join(name) G = getattr(gates, name) return G(angle=angle,control=get_qregister(args[0], qregisters) if name[0] == 'C' else None,target=get_qregister(args[1 if name[0] == 'C' else 0], qregisters)) if name.startswith("U("): angles = get_angle(name) return gates.U(theta=angles[0], phi=angles[1], lambd=angles[2], control=None, target=get_qregister(args[0], qregisters)) if name.startswith("u1("): angles = get_angle(name) return gates.u1(lambd=angles[0], control=None, target=get_qregister(args[0], qregisters)) if name.startswith("u2("): angles = get_angle(name) return gates.u2(phi=angles[0], lambd=angles[1], control=None, target=get_qregister(args[0], qregisters)) if name.startswith("u3("): angles = get_angle(name) return gates.u3(theta=angles[0], phi=angles[1], lambd=angles[2], control=None, target=get_qregister(args[0], qregisters)) if name.startswith("cu1("): angles = get_angle(name) return gates.u1(lambd=angles[0], control=get_qregister(args[0], qregisters), target=get_qregister(args[1], qregisters)) if name.startswith("cu2("): angles = get_angle(name) return gates.u2(phi=angles[0], lambd=angles[1], control=get_qregister(args[0], qregisters), target=get_qregister(args[1], qregisters)) if name.startswith("cu3("): angles = get_angle(name) return gates.u3(theta=angles[0], phi=angles[1], lambd=angles[2], control=get_qregister(args[0], qregisters), target=get_qregister(args[1], qregisters)) if name in ("s", "t", "sdg", "tdg"): g = gates.Phase(angle=pi / (2 if name.startswith("s") else 4), control=None, target=get_qregister(args[0], qregisters)) if name.find("dg") != -1: g = g.dagger() return g
[docs] def apply_custom_gate(custom_circuit: QCircuit, qregisters_values: list) -> QCircuit: applied_custom_circuit = QCircuit() for gate in custom_circuit.gates: g = gate.copy() g._target = tuple([qregisters_values[i] for i in gate._target]) g._control = tuple([qregisters_values[i] for i in gate._control]) if gate.is_controlled() else gate._control applied_custom_circuit += g return applied_custom_circuit
[docs] def get_qregister(qreg: str, qregisters: Dict[str, int]) -> typing.Union[list, int]: if qreg == qreg.split("[", 1)[0]: qreg_tequila = [qregisters[key] for key in qregisters.keys() if qreg == key.split("[", 1)[0]] else: qreg_tequila = qregisters[qreg] return qreg_tequila
[docs] def get_angle(name: str) -> list: i = name.find('(') j = name.find(')') if j == -1: raise TequilaException("Invalid specification {}".format(name)) angles_str = name[i+1:j].split(',') angles = [] for angle in angles_str: try: phase = float(angle) except ValueError: if angle.find('pi') == -1: raise TequilaException("Invalid specification {}".format(name)) angle = angle.replace('pi', '') try: sign = 1 div = 1 if angle.find('-') != -1: angle = angle.replace('-', '') sign = -1 if angle.find('/') != -1: div = float(angle[angle.index('/')+1:]) angle = angle[:angle.index('/')] if angle.find('*') != -1: angle = angle.replace('*', '') phase = sign * float(angle) * pi / div elif len(angle) == 0: phase = sign * pi / div else: phase = sign * float(angle) / div except ValueError: raise TequilaException("Invalid specification {}".format(name)) angles.append(phase) return angles