Skip to content

Commit 45eae7f

Browse files
Merge pull request #1440 from qiboteam/qibo_global
Internal global backend `_Global`
2 parents 958b11b + 65bdfee commit 45eae7f

39 files changed

+465
-248
lines changed

doc/source/api-reference/qibo.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -2667,7 +2667,7 @@ As for the other backends, the Clifford backend can be set with
26672667
import qibo
26682668
qibo.set_backend("clifford", platform="numpy")
26692669

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

26722672
.. testcode:: python
26732673

doc/source/code-examples/advancedexamples.rst

+3-5
Original file line numberDiff line numberDiff line change
@@ -1297,9 +1297,8 @@ As observable we can simply take :math:`Z_0 Z_1 Z_2` :
12971297

12981298
from qibo.symbols import Z
12991299
from qibo.hamiltonians import SymbolicHamiltonian
1300-
from qibo.backends import GlobalBackend
13011300

1302-
backend = GlobalBackend()
1301+
backend = qibo.get_backend()
13031302

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

21042103
# Define connectivity as nx.Graph
21052104
def star_connectivity():
2106-
Q = [i for i in range(5)]
21072105
chip = nx.Graph()
2108-
chip.add_nodes_from(Q)
2109-
graph_list = [(Q[i], Q[2]) for i in range(5) if i != 2]
2106+
chip.add_nodes_from(list(range(5)))
2107+
graph_list = [(i, 2) for i in range(5) if i != 2]
21102108
chip.add_edges_from(graph_list)
21112109
return chip
21122110

src/qibo/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
get_device,
2020
get_precision,
2121
get_threads,
22+
get_transpiler,
23+
get_transpiler_name,
2224
list_available_backends,
2325
matrices,
2426
set_backend,
2527
set_device,
2628
set_precision,
2729
set_threads,
30+
set_transpiler,
2831
)
2932
from qibo.config import (
3033
get_batch_size,

src/qibo/backends/__init__.py

+124-29
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
from importlib import import_module
33

4+
import networkx as nx
45
import numpy as np
56

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

6768

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

71-
_instance = None
7274
_dtypes = {"double": "complex128", "single": "complex64"}
7375
_default_order = [
7476
{"backend": "qibojit", "platform": "cupy"},
@@ -78,39 +80,88 @@ class GlobalBackend(NumpyBackend):
7880
{"backend": "pytorch"},
7981
]
8082

81-
def __new__(cls):
82-
if cls._instance is not None:
83-
return cls._instance
83+
@classmethod
84+
def backend(cls):
85+
"""Get the current backend. If no backend is set, it will create one."""
86+
if cls._backend is not None:
87+
return cls._backend
88+
cls._backend = cls._create_backend()
89+
log.info(f"Using {cls._backend} backend on {cls._backend.device}")
90+
return cls._backend
8491

85-
backend = os.environ.get("QIBO_BACKEND")
86-
if backend: # pragma: no cover
92+
@classmethod
93+
def _create_backend(cls):
94+
backend_env = os.environ.get("QIBO_BACKEND")
95+
if backend_env: # pragma: no cover
8796
# Create backend specified by user
8897
platform = os.environ.get("QIBO_PLATFORM")
89-
cls._instance = construct_backend(backend, platform=platform)
98+
backend = construct_backend(backend_env, platform=platform)
9099
else:
91100
# Create backend according to default order
92101
for kwargs in cls._default_order:
93102
try:
94-
cls._instance = construct_backend(**kwargs)
103+
backend = construct_backend(**kwargs)
95104
break
96105
except (ImportError, MissingBackend):
97106
pass
98107

99-
if cls._instance is None: # pragma: no cover
108+
if backend is None: # pragma: no cover
100109
raise_error(RuntimeError, "No backends available.")
110+
return backend
111+
112+
@classmethod
113+
def set_backend(cls, backend, **kwargs):
114+
cls._backend = construct_backend(backend, **kwargs)
115+
log.info(f"Using {cls._backend} backend on {cls._backend.device}")
101116

102-
log.info(f"Using {cls._instance} backend on {cls._instance.device}")
103-
return cls._instance
117+
@classmethod
118+
def transpiler(cls):
119+
"""Get the current transpiler. If no transpiler is set, it will create one."""
120+
if cls._transpiler is not None:
121+
return cls._transpiler
122+
123+
cls._transpiler = cls._default_transpiler()
124+
return cls._transpiler
125+
126+
@classmethod
127+
def set_transpiler(cls, transpiler):
128+
cls._transpiler = transpiler
129+
# TODO: check if transpiler is valid on the backend
104130

105131
@classmethod
106-
def set_backend(cls, backend, **kwargs): # pragma: no cover
132+
def _default_transpiler(cls):
133+
from qibo.transpiler.optimizer import Preprocessing
134+
from qibo.transpiler.pipeline import Passes
135+
from qibo.transpiler.placer import Trivial
136+
from qibo.transpiler.router import Sabre
137+
from qibo.transpiler.unroller import NativeGates, Unroller
138+
139+
qubits = cls._backend.qubits
140+
natives = cls._backend.natives
141+
connectivity_edges = cls._backend.connectivity
107142
if (
108-
cls._instance is None
109-
or cls._instance.name != backend
110-
or cls._instance.platform != kwargs.get("platform")
143+
qubits is not None
144+
and natives is not None
145+
and connectivity_edges is not None
111146
):
112-
cls._instance = construct_backend(backend, **kwargs)
113-
log.info(f"Using {cls._instance} backend on {cls._instance.device}")
147+
# only for q{i} naming
148+
node_mapping = {q: i for i, q in enumerate(qubits)}
149+
edges = [
150+
(node_mapping[e[0]], node_mapping[e[1]]) for e in connectivity_edges
151+
]
152+
connectivity = nx.Graph(edges)
153+
154+
return Passes(
155+
connectivity=connectivity,
156+
passes=[
157+
Preprocessing(connectivity),
158+
Trivial(connectivity),
159+
Sabre(connectivity),
160+
Unroller(NativeGates[natives]),
161+
],
162+
)
163+
164+
return Passes(passes=[])
114165

115166

116167
class QiboMatrices:
@@ -147,53 +198,97 @@ def create(self, dtype):
147198

148199

149200
def get_backend():
150-
return str(GlobalBackend())
201+
"""Get the current backend."""
202+
return _Global.backend()
151203

152204

153205
def set_backend(backend, **kwargs):
154-
GlobalBackend.set_backend(backend, **kwargs)
206+
"""Set the current backend.
207+
208+
Args:
209+
backend (str): Name of the backend to use.
210+
kwargs (dict): Additional arguments for the backend.
211+
"""
212+
_Global.set_backend(backend, **kwargs)
213+
214+
215+
def get_transpiler():
216+
"""Get the current transpiler."""
217+
return _Global.transpiler()
218+
219+
220+
def get_transpiler_name():
221+
"""Get the name of the current transpiler as a string."""
222+
return str(_Global.transpiler())
223+
224+
225+
def set_transpiler(transpiler):
226+
"""Set the current transpiler.
227+
228+
Args:
229+
transpiler (Passes): The transpiler to use.
230+
"""
231+
_Global.set_transpiler(transpiler)
155232

156233

157234
def get_precision():
158-
return GlobalBackend().precision
235+
"""Get the precision of the backend."""
236+
return get_backend().precision
159237

160238

161239
def set_precision(precision):
162-
GlobalBackend().set_precision(precision)
163-
matrices.create(GlobalBackend().dtype)
240+
"""Set the precision of the backend.
241+
242+
Args:
243+
precision (str): Precision to use.
244+
"""
245+
get_backend().set_precision(precision)
246+
matrices.create(get_backend().dtype)
164247

165248

166249
def get_device():
167-
return GlobalBackend().device
250+
"""Get the device of the backend."""
251+
return get_backend().device
168252

169253

170254
def set_device(device):
255+
"""Set the device of the backend.
256+
257+
Args:
258+
device (str): Device to use.
259+
"""
171260
parts = device[1:].split(":")
172261
if device[0] != "/" or len(parts) < 2 or len(parts) > 3:
173262
raise_error(
174263
ValueError,
175264
"Device name should follow the pattern: /{device type}:{device number}.",
176265
)
177-
backend = GlobalBackend()
266+
backend = get_backend()
178267
backend.set_device(device)
179268
log.info(f"Using {backend} backend on {backend.device}")
180269

181270

182271
def get_threads():
183-
return GlobalBackend().nthreads
272+
"""Get the number of threads used by the backend."""
273+
return get_backend().nthreads
184274

185275

186276
def set_threads(nthreads):
277+
"""Set the number of threads used by the backend.
278+
279+
Args:
280+
nthreads (int): Number of threads to use.
281+
"""
187282
if not isinstance(nthreads, int):
188283
raise_error(TypeError, "Number of threads must be integer.")
189284
if nthreads < 1:
190285
raise_error(ValueError, "Number of threads must be positive.")
191-
GlobalBackend().set_threads(nthreads)
286+
get_backend().set_threads(nthreads)
192287

193288

194289
def _check_backend(backend):
195290
if backend is None:
196-
return GlobalBackend()
291+
return get_backend()
197292

198293
return backend
199294

src/qibo/backends/abstract.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import abc
2-
from typing import Union
2+
from typing import Optional, Union
33

44
from qibo.config import raise_error
55

@@ -29,6 +29,26 @@ def __repr__(self):
2929
else:
3030
return f"{self.name} ({self.platform})"
3131

32+
@property
33+
@abc.abstractmethod
34+
def qubits(self) -> Optional[list[Union[int, str]]]: # pragma: no cover
35+
"""Return the qubit names of the backend. If :class:`SimulationBackend`, return None."""
36+
raise_error(NotImplementedError)
37+
38+
@property
39+
@abc.abstractmethod
40+
def connectivity(
41+
self,
42+
) -> Optional[list[tuple[Union[int, str], Union[int, str]]]]: # pragma: no cover
43+
"""Return the available qubit pairs of the backend. If :class:`SimulationBackend`, return None."""
44+
raise_error(NotImplementedError)
45+
46+
@property
47+
@abc.abstractmethod
48+
def natives(self) -> Optional[list[str]]: # pragma: no cover
49+
"""Return the native gates of the backend. If :class:`SimulationBackend`, return None."""
50+
raise_error(NotImplementedError)
51+
3252
@abc.abstractmethod
3353
def set_precision(self, precision): # pragma: no cover
3454
"""Set complex number precision.

src/qibo/backends/numpy.py

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ def __init__(self):
3434
np.complex128,
3535
)
3636

37+
@property
38+
def qubits(self):
39+
return None
40+
41+
@property
42+
def connectivity(self):
43+
return None
44+
45+
@property
46+
def natives(self):
47+
return None
48+
3749
def set_precision(self, precision):
3850
if precision != self.precision:
3951
if precision == "single":

src/qibo/gates/abstract.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ def matrix(self, backend=None):
399399
Args:
400400
backend (:class:`qibo.backends.abstract.Backend`, optional): backend
401401
to be used in the execution. If ``None``, it uses
402-
:class:`qibo.backends.GlobalBackend`. Defaults to ``None``.
402+
the current backend. Defaults to ``None``.
403403
404404
Returns:
405405
ndarray: Matrix representation of gate.

src/qibo/gates/channels.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def to_choi(self, nqubits: Optional[int] = None, order: str = "row", backend=Non
6565
vectorization is done block-wise. Defaut is ``"row"``.
6666
backend (:class:`qibo.backends.abstract.Backend`, optional):
6767
backend to be used in the execution. If ``None``,
68-
it uses :class:`qibo.backends.GlobalBackend`.
68+
it uses the current backend.
6969
Defaults to ``None``.
7070
7171
Returns:
@@ -121,7 +121,7 @@ def to_liouville(self, nqubits: int = None, order: str = "row", backend=None):
121121
it raises ``NotImplementedError``. Defaut is ``"row"``.
122122
backend (:class:`qibo.backends.abstract.Backend`, optional):
123123
backend to be used in the execution. If ``None``,
124-
it uses :class:`qibo.backends.GlobalBackend`.
124+
it uses the current backend.
125125
Defaults to ``None``.
126126
127127
Returns:
@@ -160,7 +160,7 @@ def to_pauli_liouville(
160160
Pauli elements in the basis. Default is "IXYZ".
161161
backend (:class:`qibo.backends.abstract.Backend`, optional): backend
162162
to be used in the execution. If ``None``, it uses
163-
:class:`qibo.backends.GlobalBackend`.
163+
the current backend.
164164
Defaults to ``None``.
165165
166166
Returns:

src/qibo/gates/special.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def matrix(self, backend=None):
9999
"""Returns matrix representation of special gate.
100100
101101
Args:
102-
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``.
102+
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used in the execution. If ``None``, it uses the current backend. Defaults to ``None``.
103103
104104
Returns:
105105
ndarray: Matrix representation of special gate.

0 commit comments

Comments
 (0)