Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MetaBackend object for loading and listing backends #1229

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
00c5b91
feat: added client backends for remote qibo and qiskit circuits execu…
BrunoLiegiBastonLiegi Jan 16, 2024
341bee4
feat: added the client backends to the __init__
BrunoLiegiBastonLiegi Jan 16, 2024
0da453e
fix: removed the client backends from qibo
BrunoLiegiBastonLiegi Jan 16, 2024
2a69891
feat: added support for OPENQASM 3 gate command
BrunoLiegiBastonLiegi Jan 22, 2024
6de6ddb
fix: removed imports from pylint checks
BrunoLiegiBastonLiegi Jan 22, 2024
c31b4bd
fix: fixed tests
BrunoLiegiBastonLiegi Jan 23, 2024
10a1aa0
fix: removed print
BrunoLiegiBastonLiegi Jan 23, 2024
3616643
test: added test for gate command
BrunoLiegiBastonLiegi Jan 23, 2024
deba408
fix: disabled coverage for the backends
BrunoLiegiBastonLiegi Jan 23, 2024
6af9ae6
refactor: changed name of the client backends
BrunoLiegiBastonLiegi Jan 25, 2024
932ffc7
fix: reverted to qasm 2.0
BrunoLiegiBastonLiegi Jan 25, 2024
4728589
fix: small update
BrunoLiegiBastonLiegi Jan 26, 2024
e094135
test: removed string replace
BrunoLiegiBastonLiegi Jan 31, 2024
a27fd97
Update src/qibo/models/circuit.py
BrunoLiegiBastonLiegi Jan 31, 2024
c694727
Update src/qibo/models/circuit.py
BrunoLiegiBastonLiegi Jan 31, 2024
8f059ea
fix: removed print
BrunoLiegiBastonLiegi Jan 31, 2024
ed790c2
refactor: renamed qibo-cloud backend
BrunoLiegiBastonLiegi Feb 1, 2024
1101112
Merge branch 'master' into client_backends
renatomello Feb 2, 2024
7895cd2
Merge branch 'master' into client_backends
renatomello Feb 3, 2024
f2ccea5
Merge branch 'master' into client_backends
renatomello Feb 7, 2024
caf24dd
Merge branch 'master' into client_backends
renatomello Feb 7, 2024
7e36891
Merge branch 'master' into client_backends
renatomello Feb 7, 2024
714f730
Merge branch 'master' into client_backends
renatomello Feb 7, 2024
c8f3da1
refactor: added **kwargs
BrunoLiegiBastonLiegi Feb 7, 2024
673060c
Merge branch 'master' into client_backends
renatomello Feb 8, 2024
9f96628
Merge branch 'client_backends' of github.com:qiboteam/qibo into clien…
renatomello Feb 8, 2024
363787f
fix: fix to set_backend
BrunoLiegiBastonLiegi Feb 9, 2024
a926f78
fix: pylint fix
BrunoLiegiBastonLiegi Feb 9, 2024
6ca949a
feat: added reference to qibo-cloud-backends docs in qibo api-ref + a…
BrunoLiegiBastonLiegi Feb 23, 2024
1a9bb5f
fix: removed lock
BrunoLiegiBastonLiegi Feb 23, 2024
483b10e
build: regenerated lock
BrunoLiegiBastonLiegi Feb 23, 2024
e969eff
feat: implemented MetaBackend
BrunoLiegiBastonLiegi Feb 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2414,3 +2414,9 @@ Alternatively, a Clifford circuit can also be executed starting from the :class:
.. autoclass:: qibo.backends.clifford.CliffordBackend
:members:
:member-order: bysource


Cloud Backends
^^^^^^^^^^^^^^

Additional backends, that support the remote execution of quantum circuits through cloud service providers, are provided by the optional qibo plugin `qibo-cloud-backends <https://github.com/qiboteam/qibo-cloud-backends>`_. For more information please refer to the `official documentation <https://qibo.science/qibo-cloud-backends/stable/>`_.
354 changes: 180 additions & 174 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ cupy-cuda12x = "^12.0.0"
cuquantum-python-cu12 = "^23.3.0"
qibojit = { git = "https://github.com/qiboteam/qibojit.git" }

[tool.poetry.group.cloud]
optional = true

[tool.poetry.group.cloud.dependencies]
qibo-cloud-backends = ">=0.0.1"

[tool.pylint.reports]
output-format = "colorized"

Expand Down
99 changes: 57 additions & 42 deletions src/qibo/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from importlib import import_module

from qibo.backends.abstract import Backend
from qibo.backends.clifford import CliffordBackend
Expand All @@ -7,43 +8,57 @@
from qibo.backends.tensorflow import TensorflowBackend
from qibo.config import log, raise_error


def construct_backend(backend, platform=None):
if backend == "qibojit":
from qibojit.backends import CupyBackend, CuQuantumBackend, NumbaBackend

if platform == "cupy": # pragma: no cover
return CupyBackend()
elif platform == "cuquantum": # pragma: no cover
return CuQuantumBackend()
elif platform == "numba":
return NumbaBackend()
else: # pragma: no cover
QIBO_NATIVE_BACKENDS = ("numpy", "tensorflow")
QIBO_NON_NATIVE_BACKENDS = ("qibojit", "qibolab", "qibocloud")


class MetaBackend:
"""Meta-backend class which takes care of loading the qibo backends."""

@staticmethod
def load(backend: str, **kwargs) -> Backend:
"""Loads the backend.

Args:
backend (str): Name of the backend to load.
kwargs (dict): Additional arguments for the non-native qibo backends.
Returns:
qibo.backends.abstract.Backend: The loaded backend.
"""

if backend == "numpy":
return NumpyBackend()
elif backend == "tensorflow":
return TensorflowBackend()
elif backend == "clifford":
return CliffordBackend(**kwargs)
elif backend in QIBO_NON_NATIVE_BACKENDS:
module = import_module(backend)
return getattr(module, "MetaBackend").load(**kwargs)
else:
raise_error(
ValueError,
f"Backend {backend} is not available. To check which backend is installed use `qibo.list_available_backends()`.",
)

def list_available(self) -> dict:
"""Lists all the available qibo backends."""
available_backends = {}
for backend in QIBO_NATIVE_BACKENDS:
try:
return CupyBackend()
except (ModuleNotFoundError, ImportError):
return NumbaBackend()

elif backend == "tensorflow":
return TensorflowBackend()

elif backend == "numpy":
return NumpyBackend()

elif backend == "qibolab": # pragma: no cover
from qibolab.backends import QibolabBackend # pylint: disable=E0401

return QibolabBackend(platform)
elif backend == "clifford":
if platform is not None: # pragma: no cover
if platform in ("cupy", "numba", "cuquantum"):
platform = construct_backend("qibojit", platform=platform)
else:
platform = construct_backend(platform)
return CliffordBackend(platform)

else: # pragma: no cover
raise_error(ValueError, f"Backend {backend} is not available.")
MetaBackend.load(backend)
available = True
except:
available = False
available_backends[backend] = available
for backend in QIBO_NON_NATIVE_BACKENDS:
try:
module = import_module(backend)
available = getattr(module, "MetaBackend")().list_available()
except:
available = False
available_backends.update({backend: available})
return available_backends


class GlobalBackend(NumpyBackend):
Expand All @@ -66,7 +81,7 @@ def __new__(cls):
if backend: # pragma: no cover
# Create backend specified by user
platform = os.environ.get("QIBO_PLATFORM")
cls._instance = construct_backend(backend, platform)
cls._instance = construct_backend(backend, platform=platform)
else:
# Create backend according to default order
for kwargs in cls._default_order:
Expand All @@ -83,13 +98,13 @@ def __new__(cls):
return cls._instance

@classmethod
def set_backend(cls, backend, platform=None): # pragma: no cover
def set_backend(cls, backend, **kwargs): # pragma: no cover
if (
cls._instance is None
or cls._instance.name != backend
or cls._instance.platform != platform
or cls._instance.platform != kwargs.get("platform")
):
cls._instance = construct_backend(backend, platform)
cls._instance = construct_backend(backend, **kwargs)
log.info(f"Using {cls._instance} backend on {cls._instance.device}")


Expand Down Expand Up @@ -129,8 +144,8 @@ def get_backend():
return str(GlobalBackend())


def set_backend(backend, platform=None):
GlobalBackend.set_backend(backend, platform)
def set_backend(backend, **kwargs):
GlobalBackend.set_backend(backend, **kwargs)


def get_precision():
Expand Down
99 changes: 83 additions & 16 deletions src/qibo/models/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1248,13 +1248,13 @@ def read_args(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)
lines = [line for line in qasm_code.splitlines() if line and line[:2] != "//"]

if next(lines) != "OPENQASM 2.0":
raise_error(ValueError, "QASM code should start with 'OPENQASM 2.0'.")
if not re.search(r"^OPENQASM [0-9]+\.[0-9]+", lines[0]):
raise_error(
ValueError,
"QASM code should start with 'OPENQASM X.X' with X.X indicating the version.",
)

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
Expand All @@ -1264,11 +1264,14 @@ def read_args(args):
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(" ", "")
composite_gates = {} # composite gates created with the "gate" command

def parse_line(line):
line = line.replace(r"/\s{2,}/g", " ").replace(r"^[\s]+", "")
command, *args = line.split(" ")
args = " ".join(args)
if args[-1] == ";":
args = args[:-1]

if command == "include":
pass
Expand All @@ -1283,7 +1286,7 @@ def read_args(args):
cregs_size[name] = nqubits

elif command == "measure":
args = args.split("->")
args = args.replace(" ", "").split("->")
if len(args) != 2:
raise_error(ValueError, "Invalid QASM measurement:", line)
qubit = next(read_args(args[0]))
Expand Down Expand Up @@ -1316,6 +1319,39 @@ def read_args(args):
registers[register] = {idx: qubits[qubit]}
gate_list.append((gates.M, register, None))

elif command == "gate":
_name, _qubits, *_ = args.split(" ")
_separate_gates = (
re.search(r"\{(.*?)\}", args).group(0)[2:-2].split(";")
)
_params = re.search(r"\((.*?)\)", _name)
_qubits = _qubits.split(",")
if _params:
_name = _name[: _params.start()]
_params = _params.group()[1:-1].split(",")
composite_gates[_name] = []
for g in _separate_gates:
_g_name, _g_qubits = g.split(" ")
_g_params = re.search(r"\((.*?)\)", _g_name)
if _g_params and _params:
_g_name = _g_name[: _g_params.start()]
_g_params = [
f"{{{_params.index(p)}}}"
for p in _g_params.group()[1:-1].split(",")
]
else:
_g_params = None
_g_qubits = [
f"{{{_qubits.index(q)}}}" for q in _g_qubits.split(",")
]
pars = "" if not _g_params else f"({','.join(_g_params)})"
composite_gates[_name].append(
{
"gatename": _g_name,
"qubits": ",".join(_g_qubits),
"parameters": pars,
}
)
else:
pieces = [x for x in re.split("[()]", command) if x]

Expand All @@ -1331,10 +1367,25 @@ def read_args(args):
}[gatename]
)
except:
raise_error(
ValueError,
f"QASM command {command} is not recognized.",
)
if gatename in composite_gates:
_q = line.split(" ")[-1].replace(";", "").split(",")
_p = (
re.search(r"\((.*?)\)", line.split(" ")[0])
.group(0)
.split(",")
if len(pieces) == 2
else []
)
for g in composite_gates[gatename]:
parse_line(
f"{g['gatename']}{g['parameters'].format(*_p)} {g['qubits'].format(*_q)}"
)
return
else:
raise_error(
ValueError,
f"QASM command {command} is not recognized.",
)

if len(pieces) == 1:
params = None
Expand Down Expand Up @@ -1384,6 +1435,22 @@ def read_args(args):
qubit_list.append(qubits[qubit])
gate_list.append((gatetype, list(qubit_list), params))

for line in lines:
line = (
re.sub(r"^OPENQASM [0-9]+\.[0-9]+;*\s*", "", line)
.replace(r"/\s\s+/g", " ")
.replace(r"^[\s]+", "")
.replace("[\\s]+\n", "")
.replace("; ", ";")
.replace(", ", ",")
)
_lines = [line]
if len(re.findall(";", line)) > 1 and not line.split(" ")[0] == "gate":
_lines = line.split(";")[:-1]
for l in _lines:
if l != "":
parse_line(l)

# Create measurement gate qubit lists from registers
for i, (gatetype, register, _) in enumerate(gate_list):
if gatetype == gates.M:
Expand Down
25 changes: 24 additions & 1 deletion tests/test_models_circuit_qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,11 +529,34 @@ def test_from_qasm_invalid_parametrized_gates():
def test_from_qasm_empty_spaces():
target = """OPENQASM 2.0; qreg q[2];
creg a[2]; h q[0];x q[1]; cx q[0], q[1];
measure q[0] -> a[0];measure q[1]->a[1]"""
measure q[0] -> a[0];measure q[1]->a[1];"""
c = Circuit.from_qasm(target)
assert c.nqubits == 2
assert c.depth == 3
assert isinstance(c.queue[0], gates.H)
assert isinstance(c.queue[1], gates.X)
assert isinstance(c.queue[2], gates.CNOT)
assert c.measurement_tuples == {"a": (0, 1)}


def test_from_qasm_gate_command():
target = """OPENQASM 2.0;
include "qelib1.inc";
gate bob(theta,alpha) q0,q1 { h q1; cx q0,q1; rz(theta) q1; rx(alpha) q0; h q1; }
gate alice q0,q1 { bob(pi/4,pi) q0,q1; x q0; bob(-pi/4,pi/2) q0,q1; }
qreg q[3];
bob(-pi/2,pi) q[0],q[2];
alice q[1],q[0];"""
c = Circuit.from_qasm(target)
for i in range(2):
assert isinstance(c.queue[0 + 5 * i], gates.H)
assert isinstance(c.queue[1 + 5 * i], gates.CNOT)
assert isinstance(c.queue[2 + 5 * i], gates.RZ)
assert isinstance(c.queue[3 + 5 * i], gates.RX)
assert isinstance(c.queue[4 + 5 * i], gates.H)
assert isinstance(c.queue[10], gates.X)
assert isinstance(c.queue[11], gates.H)
assert isinstance(c.queue[12], gates.CNOT)
assert isinstance(c.queue[13], gates.RZ)
assert isinstance(c.queue[14], gates.RX)
assert isinstance(c.queue[15], gates.H)
Loading