Skip to content

Commit

Permalink
Merge pull request #1440 from qiboteam/qibo_global
Browse files Browse the repository at this point in the history
Internal global backend `_Global`
  • Loading branch information
MatteoRobbiati authored Oct 23, 2024
2 parents 958b11b + 65bdfee commit 45eae7f
Show file tree
Hide file tree
Showing 39 changed files with 465 additions and 248 deletions.
2 changes: 1 addition & 1 deletion doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2667,7 +2667,7 @@ As for the other backends, the Clifford backend can be set with
import qibo
qibo.set_backend("clifford", platform="numpy")

by specifying the engine used for calculation, if not provided the current :class:`qibo.backends.GlobalBackend` is used
by specifying the engine used for calculation, if not provided the current backend is used

.. testcode:: python

Expand Down
8 changes: 3 additions & 5 deletions doc/source/code-examples/advancedexamples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1297,9 +1297,8 @@ As observable we can simply take :math:`Z_0 Z_1 Z_2` :

from qibo.symbols import Z
from qibo.hamiltonians import SymbolicHamiltonian
from qibo.backends import GlobalBackend

backend = GlobalBackend()
backend = qibo.get_backend()

# Define the observable
obs = np.prod([Z(i) for i in range(nqubits)])
Expand Down Expand Up @@ -2103,10 +2102,9 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile

# Define connectivity as nx.Graph
def star_connectivity():
Q = [i for i in range(5)]
chip = nx.Graph()
chip.add_nodes_from(Q)
graph_list = [(Q[i], Q[2]) for i in range(5) if i != 2]
chip.add_nodes_from(list(range(5)))
graph_list = [(i, 2) for i in range(5) if i != 2]
chip.add_edges_from(graph_list)
return chip

Expand Down
3 changes: 3 additions & 0 deletions src/qibo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
get_device,
get_precision,
get_threads,
get_transpiler,
get_transpiler_name,
list_available_backends,
matrices,
set_backend,
set_device,
set_precision,
set_threads,
set_transpiler,
)
from qibo.config import (
get_batch_size,
Expand Down
153 changes: 124 additions & 29 deletions src/qibo/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from importlib import import_module

import networkx as nx
import numpy as np

from qibo.backends.abstract import Backend
Expand Down Expand Up @@ -65,10 +66,11 @@ def list_available(self) -> dict:
return available_backends


class GlobalBackend(NumpyBackend):
"""The global backend will be used as default by ``circuit.execute()``."""
class _Global:
_backend = None
_transpiler = None
# TODO: resolve circular import with qibo.transpiler.pipeline.Passes

_instance = None
_dtypes = {"double": "complex128", "single": "complex64"}
_default_order = [
{"backend": "qibojit", "platform": "cupy"},
Expand All @@ -78,39 +80,88 @@ class GlobalBackend(NumpyBackend):
{"backend": "pytorch"},
]

def __new__(cls):
if cls._instance is not None:
return cls._instance
@classmethod
def backend(cls):
"""Get the current backend. If no backend is set, it will create one."""
if cls._backend is not None:
return cls._backend
cls._backend = cls._create_backend()
log.info(f"Using {cls._backend} backend on {cls._backend.device}")
return cls._backend

backend = os.environ.get("QIBO_BACKEND")
if backend: # pragma: no cover
@classmethod
def _create_backend(cls):
backend_env = os.environ.get("QIBO_BACKEND")
if backend_env: # pragma: no cover
# Create backend specified by user
platform = os.environ.get("QIBO_PLATFORM")
cls._instance = construct_backend(backend, platform=platform)
backend = construct_backend(backend_env, platform=platform)
else:
# Create backend according to default order
for kwargs in cls._default_order:
try:
cls._instance = construct_backend(**kwargs)
backend = construct_backend(**kwargs)
break
except (ImportError, MissingBackend):
pass

if cls._instance is None: # pragma: no cover
if backend is None: # pragma: no cover
raise_error(RuntimeError, "No backends available.")
return backend

@classmethod
def set_backend(cls, backend, **kwargs):
cls._backend = construct_backend(backend, **kwargs)
log.info(f"Using {cls._backend} backend on {cls._backend.device}")

log.info(f"Using {cls._instance} backend on {cls._instance.device}")
return cls._instance
@classmethod
def transpiler(cls):
"""Get the current transpiler. If no transpiler is set, it will create one."""
if cls._transpiler is not None:
return cls._transpiler

cls._transpiler = cls._default_transpiler()
return cls._transpiler

@classmethod
def set_transpiler(cls, transpiler):
cls._transpiler = transpiler
# TODO: check if transpiler is valid on the backend

@classmethod
def set_backend(cls, backend, **kwargs): # pragma: no cover
def _default_transpiler(cls):
from qibo.transpiler.optimizer import Preprocessing
from qibo.transpiler.pipeline import Passes
from qibo.transpiler.placer import Trivial
from qibo.transpiler.router import Sabre
from qibo.transpiler.unroller import NativeGates, Unroller

qubits = cls._backend.qubits
natives = cls._backend.natives
connectivity_edges = cls._backend.connectivity
if (
cls._instance is None
or cls._instance.name != backend
or cls._instance.platform != kwargs.get("platform")
qubits is not None
and natives is not None
and connectivity_edges is not None
):
cls._instance = construct_backend(backend, **kwargs)
log.info(f"Using {cls._instance} backend on {cls._instance.device}")
# only for q{i} naming
node_mapping = {q: i for i, q in enumerate(qubits)}
edges = [
(node_mapping[e[0]], node_mapping[e[1]]) for e in connectivity_edges
]
connectivity = nx.Graph(edges)

return Passes(
connectivity=connectivity,
passes=[
Preprocessing(connectivity),
Trivial(connectivity),
Sabre(connectivity),
Unroller(NativeGates[natives]),
],
)

return Passes(passes=[])


class QiboMatrices:
Expand Down Expand Up @@ -147,53 +198,97 @@ def create(self, dtype):


def get_backend():
return str(GlobalBackend())
"""Get the current backend."""
return _Global.backend()


def set_backend(backend, **kwargs):
GlobalBackend.set_backend(backend, **kwargs)
"""Set the current backend.
Args:
backend (str): Name of the backend to use.
kwargs (dict): Additional arguments for the backend.
"""
_Global.set_backend(backend, **kwargs)


def get_transpiler():
"""Get the current transpiler."""
return _Global.transpiler()


def get_transpiler_name():
"""Get the name of the current transpiler as a string."""
return str(_Global.transpiler())


def set_transpiler(transpiler):
"""Set the current transpiler.
Args:
transpiler (Passes): The transpiler to use.
"""
_Global.set_transpiler(transpiler)


def get_precision():
return GlobalBackend().precision
"""Get the precision of the backend."""
return get_backend().precision


def set_precision(precision):
GlobalBackend().set_precision(precision)
matrices.create(GlobalBackend().dtype)
"""Set the precision of the backend.
Args:
precision (str): Precision to use.
"""
get_backend().set_precision(precision)
matrices.create(get_backend().dtype)


def get_device():
return GlobalBackend().device
"""Get the device of the backend."""
return get_backend().device


def set_device(device):
"""Set the device of the backend.
Args:
device (str): Device to use.
"""
parts = device[1:].split(":")
if device[0] != "/" or len(parts) < 2 or len(parts) > 3:
raise_error(
ValueError,
"Device name should follow the pattern: /{device type}:{device number}.",
)
backend = GlobalBackend()
backend = get_backend()
backend.set_device(device)
log.info(f"Using {backend} backend on {backend.device}")


def get_threads():
return GlobalBackend().nthreads
"""Get the number of threads used by the backend."""
return get_backend().nthreads


def set_threads(nthreads):
"""Set the number of threads used by the backend.
Args:
nthreads (int): Number of threads to use.
"""
if not isinstance(nthreads, int):
raise_error(TypeError, "Number of threads must be integer.")
if nthreads < 1:
raise_error(ValueError, "Number of threads must be positive.")
GlobalBackend().set_threads(nthreads)
get_backend().set_threads(nthreads)


def _check_backend(backend):
if backend is None:
return GlobalBackend()
return get_backend()

return backend

Expand Down
22 changes: 21 additions & 1 deletion src/qibo/backends/abstract.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import abc
from typing import Union
from typing import Optional, Union

from qibo.config import raise_error

Expand Down Expand Up @@ -29,6 +29,26 @@ def __repr__(self):
else:
return f"{self.name} ({self.platform})"

@property
@abc.abstractmethod
def qubits(self) -> Optional[list[Union[int, str]]]: # pragma: no cover
"""Return the qubit names of the backend. If :class:`SimulationBackend`, return None."""
raise_error(NotImplementedError)

@property
@abc.abstractmethod
def connectivity(
self,
) -> Optional[list[tuple[Union[int, str], Union[int, str]]]]: # pragma: no cover
"""Return the available qubit pairs of the backend. If :class:`SimulationBackend`, return None."""
raise_error(NotImplementedError)

@property
@abc.abstractmethod
def natives(self) -> Optional[list[str]]: # pragma: no cover
"""Return the native gates of the backend. If :class:`SimulationBackend`, return None."""
raise_error(NotImplementedError)

@abc.abstractmethod
def set_precision(self, precision): # pragma: no cover
"""Set complex number precision.
Expand Down
12 changes: 12 additions & 0 deletions src/qibo/backends/numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ def __init__(self):
np.complex128,
)

@property
def qubits(self):
return None

@property
def connectivity(self):
return None

@property
def natives(self):
return None

def set_precision(self, precision):
if precision != self.precision:
if precision == "single":
Expand Down
2 changes: 1 addition & 1 deletion src/qibo/gates/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ def matrix(self, backend=None):
Args:
backend (:class:`qibo.backends.abstract.Backend`, optional): backend
to be used in the execution. If ``None``, it uses
:class:`qibo.backends.GlobalBackend`. Defaults to ``None``.
the current backend. Defaults to ``None``.
Returns:
ndarray: Matrix representation of gate.
Expand Down
6 changes: 3 additions & 3 deletions src/qibo/gates/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def to_choi(self, nqubits: Optional[int] = None, order: str = "row", backend=Non
vectorization is done block-wise. Defaut is ``"row"``.
backend (:class:`qibo.backends.abstract.Backend`, optional):
backend to be used in the execution. If ``None``,
it uses :class:`qibo.backends.GlobalBackend`.
it uses the current backend.
Defaults to ``None``.
Returns:
Expand Down Expand Up @@ -121,7 +121,7 @@ def to_liouville(self, nqubits: int = None, order: str = "row", backend=None):
it raises ``NotImplementedError``. Defaut is ``"row"``.
backend (:class:`qibo.backends.abstract.Backend`, optional):
backend to be used in the execution. If ``None``,
it uses :class:`qibo.backends.GlobalBackend`.
it uses the current backend.
Defaults to ``None``.
Returns:
Expand Down Expand Up @@ -160,7 +160,7 @@ def to_pauli_liouville(
Pauli elements in the basis. Default is "IXYZ".
backend (:class:`qibo.backends.abstract.Backend`, optional): backend
to be used in the execution. If ``None``, it uses
:class:`qibo.backends.GlobalBackend`.
the current backend.
Defaults to ``None``.
Returns:
Expand Down
2 changes: 1 addition & 1 deletion src/qibo/gates/special.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def matrix(self, backend=None):
"""Returns matrix representation of special gate.
Args:
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used in the execution. If ``None``, it uses :class:`qibo.backends.GlobalBackend`. Defaults to ``None``.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used in the execution. If ``None``, it uses the current backend. Defaults to ``None``.
Returns:
ndarray: Matrix representation of special gate.
Expand Down
Loading

0 comments on commit 45eae7f

Please sign in to comment.