174 lines
7.2 KiB
Python
174 lines
7.2 KiB
Python
from abc import ABC, abstractmethod
|
|
import numpy as np
|
|
|
|
|
|
class AbstractBackend(ABC):
|
|
|
|
def __init__(self):
|
|
self.name = None
|
|
self.__version__ = None
|
|
|
|
@abstractmethod
|
|
def from_qasm(self, qasm):
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
def __call__(self, circuit):
|
|
raise NotImplementedError
|
|
|
|
def transpose_state(self, x):
|
|
"""Switch order of qubits in state vector to be compatible to Qibo."""
|
|
shape = tuple(x.shape)
|
|
nqubits = int(np.log2(shape[0]))
|
|
x = np.reshape(x, nqubits * (2,))
|
|
x = np.transpose(x, range(nqubits - 1, -1, -1))
|
|
return np.reshape(x, shape)
|
|
|
|
@abstractmethod
|
|
def get_precision(self):
|
|
raise NotImplementedError
|
|
|
|
def set_precision(self, precision):
|
|
raise NotImplementedError(f"Cannot set precision for {self.name} backend.")
|
|
|
|
@abstractmethod
|
|
def get_device(self):
|
|
raise NotImplementedError
|
|
|
|
|
|
class ParserBackend(AbstractBackend):
|
|
|
|
QASM_GATES = {"h": "H", "x": "X", "y": "Y", "z": "Z",
|
|
"rx": "RX", "ry": "RY", "rz": "RZ",
|
|
"u1": "U1", "u2": "U2", "u3": "U3",
|
|
"cx": "CNOT", "swap": "SWAP", "cz": "CZ",
|
|
"crx": "CRX", "cry": "CRY", "crz": "CRZ",
|
|
"cu1": "CU1", "cu3": "CU3", "rzz": "RZZ",
|
|
"ccx": "TOFFOLI", "id": "I"}
|
|
PARAMETRIZED_GATES = {"rx", "ry", "rz", "u1", "u2", "u3",
|
|
"crx", "cry", "crz", "cu1", "cu3", "rzz"}
|
|
|
|
def parse(self, qasm_code):
|
|
"""Extracts circuit information from QASM script.
|
|
|
|
Args:
|
|
qasm_code: String with the QASM code to parse.
|
|
|
|
Returns:
|
|
nqubits: The total number of qubits in the circuit.
|
|
gate_list: List that specifies the gates of the circuit.
|
|
Contains tuples of the form
|
|
(Qibo gate name, qubit IDs, optional additional parameter).
|
|
The additional parameter is the ``register_name`` for
|
|
measurement gates or ``theta`` for parametrized gates.
|
|
"""
|
|
import re
|
|
def read_args(args):
|
|
_args = iter(re.split(r"[\[\],]", args))
|
|
for name in _args:
|
|
if name:
|
|
index = next(_args)
|
|
if not index.isdigit():
|
|
raise ValueError("Invalid QASM qubit arguments: {}".format(args))
|
|
yield name, int(index)
|
|
|
|
# Remove comment lines
|
|
lines = "".join(line for line in qasm_code.split("\n")
|
|
if line and line[:2] != "//")
|
|
lines = (line for line in lines.split(";") if line)
|
|
|
|
if next(lines) != "OPENQASM 2.0":
|
|
raise ValueError("QASM code should start with 'OPENQASM 2.0'.")
|
|
|
|
qubits = {} # Dict[Tuple[str, int], int]: map from qubit tuple to qubit id
|
|
cregs_size = {} # Dict[str, int]: map from `creg` name to its size
|
|
registers = {} # Dict[str, List[int]]: map from register names to target qubit ids
|
|
gate_list = [] # List[Tuple[str, List[int]]]: List of (gate name, list of target qubit ids)
|
|
for line in lines:
|
|
command, args = line.split(None, 1)
|
|
# remove spaces
|
|
command = command.replace(" ", "")
|
|
args = args.replace(" ", "")
|
|
|
|
if command == "include":
|
|
pass
|
|
|
|
elif command == "qreg":
|
|
for name, nqubits in read_args(args):
|
|
for i in range(nqubits):
|
|
qubits[(name, i)] = len(qubits)
|
|
|
|
elif command == "creg":
|
|
for name, nqubits in read_args(args):
|
|
cregs_size[name] = nqubits
|
|
|
|
elif command == "measure":
|
|
args = args.split("->")
|
|
if len(args) != 2:
|
|
raise ValueError("Invalid QASM measurement: {}".format(line))
|
|
qubit = next(read_args(args[0]))
|
|
if qubit not in qubits:
|
|
raise ValueError("Qubit {} is not defined in QASM code."
|
|
"".format(qubit))
|
|
|
|
register, idx = next(read_args(args[1]))
|
|
if register not in cregs_size:
|
|
raise ValueError("Classical register name {} is not defined "
|
|
"in QASM code.".format(register))
|
|
if idx >= cregs_size[register]:
|
|
raise ValueError("Cannot access index {} of register {} "
|
|
"with {} qubits."
|
|
"".format(idx, register, cregs_size[register]))
|
|
if register in registers:
|
|
if idx in registers[register]:
|
|
raise KeyError("Key {} of register {} has already "
|
|
"been used.".format(idx, register))
|
|
registers[register][idx] = qubits[qubit]
|
|
else:
|
|
registers[register] = {idx: qubits[qubit]}
|
|
gate_list.append(("M", register))
|
|
|
|
else:
|
|
pieces = [x for x in re.split("[()]", command) if x]
|
|
if len(pieces) == 1:
|
|
gatename, params = pieces[0], None
|
|
if gatename not in self.QASM_GATES:
|
|
raise ValueError("QASM command {} is not recognized."
|
|
"".format(command))
|
|
if gatename in self.PARAMETRIZED_GATES:
|
|
raise ValueError("Missing parameters for QASM "
|
|
"gate {}.".format(gatename))
|
|
|
|
elif len(pieces) == 2:
|
|
gatename, params = pieces
|
|
if gatename not in self.PARAMETRIZED_GATES:
|
|
raise ValueError("Invalid QASM command {}."
|
|
"".format(command))
|
|
params = params.replace(" ", "").split(",")
|
|
try:
|
|
for i, p in enumerate(params):
|
|
if 'pi' in p:
|
|
import math
|
|
from operator import mul
|
|
from functools import reduce
|
|
s = p.replace('pi', str(math.pi)).split('*')
|
|
p = reduce(mul, [float(j) for j in s], 1)
|
|
params[i] = float(p)
|
|
except ValueError:
|
|
raise ValueError("Invalid value {} for gate parameters."
|
|
"".format(params))
|
|
|
|
else:
|
|
raise ValueError("QASM command {} is not recognized."
|
|
"".format(command))
|
|
|
|
# Add gate to gate list
|
|
qubit_list = []
|
|
for qubit in read_args(args):
|
|
if qubit not in qubits:
|
|
raise ValueError("Qubit {} is not defined in QASM "
|
|
"code.".format(qubit))
|
|
qubit_list.append(qubits[qubit])
|
|
gate_list.append((self.QASM_GATES[gatename], list(qubit_list), params))
|
|
return len(qubits), gate_list
|