Skip to content

Commit 6316889

Browse files
Merge pull request #1272 from qiboteam/qulacs
`QulacsBackend`
2 parents dd3c885 + 6033640 commit 6316889

17 files changed

+326
-115
lines changed

doc/source/api-reference/qibo.rst

+10
Original file line numberDiff line numberDiff line change
@@ -2440,6 +2440,16 @@ The default backend order is qibojit (if available), tensorflow (if available),
24402440
numpy. The default backend can be changed using the ``QIBO_BACKEND`` environment
24412441
variable.
24422442

2443+
Qibo optionally provides an interface to `qulacs <https://github.com/qulacs/qulacs>`_ through the :class:`qibo.backends.qulacs.QulacsBackend`. To use ``qulacs`` for simulating a quantum circuit you can globally set the backend as in the other cases
2444+
2445+
.. testcode:: python
2446+
2447+
import qibo
2448+
qibo.set_backend("qulacs")
2449+
2450+
.. note::
2451+
GPU simulation through ``qulacs`` is not supported yet.
2452+
24432453
.. autoclass:: qibo.backends.abstract.Backend
24442454
:members:
24452455
:member-order: bysource
16.7 KB
Loading

doc/source/getting-started/backends.rst

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ if the corresponding packages are installed, following the hierarchy below:
4040
* :ref:`installing-pytorch`: a pure PyTorch implementation for quantum simulation which provides access to gradient descent optimization and the possibility to implement classical and quantum architectures together. This backend is not optimized for memory and speed, use :ref:`installing-qibojit` instead.
4141
* :ref:`clifford <Clifford>`: a specialized backend for the simulation of quantum circuits with Clifford gates. This backend uses :ref:`installing-qibojit` and/or :ref:`installing-numpy`.
4242
* `qibotn <https://qibo.science/qibotn/stable/>`_: an interface to Tensor Networks simulation algorithms designed for GPUs and multi-node CPUs. This backend makes possible scaling quantum circuit simulation to a larger number of qubits.
43+
* `qulacs <https://github.com/qulacs/qulacs>`_: an interface to the `qulacs` library for quantum simulation. GPU support is not available yet.
4344

4445
The default backend that is used is the first available from the above list.
4546
The user can switch to a different using the ``qibo.set_backend`` method

doc/source/qibo_ecosystem.png

11.5 KB
Loading

poetry.lock

+139-101
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ numpy = "^1.26.4"
3535
networkx = "^3.2.1"
3636
tensorflow = { version = "^2.16.1", markers = "sys_platform == 'linux' or sys_platform == 'darwin'", optional = true }
3737
torch = { version = "^2.1.1", optional = true }
38+
qulacs = { version = "^0.6.4", optional = true, markers="(sys_platform == 'darwin' and python_version > '3.9') or sys_platform != 'darwin'"}
3839

3940
[tool.poetry.group.dev]
4041
optional = true
@@ -55,6 +56,7 @@ sphinx-markdown-tables = "^0.0.17"
5556
sphinx-copybutton = "^0.5.2"
5657
nbsphinx = "^0.8.12"
5758
ipython = "^8.10.0"
59+
qulacs = { version = "^0.6.4", markers="(sys_platform == 'darwin' and python_version > '3.9') or sys_platform != 'darwin'"}
5860
seaborn = "^0.13.2"
5961
ipykernel = "^6.29.4"
6062

@@ -74,6 +76,7 @@ torch = "^2.1.1"
7476
qibojit = { git = "https://github.com/qiboteam/qibojit.git" }
7577
qibotn = { git = "https://github.com/qiboteam/qibotn.git" }
7678
stim = "^1.12.0"
79+
qulacs = { version = "^0.6.4", markers="(sys_platform == 'darwin' and python_version > '3.9') or sys_platform != 'darwin'" }
7780

7881
[tool.poe.tasks]
7982
test = "pytest"

src/qibo/backends/__init__.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from qibo.backends.tensorflow import TensorflowBackend
1212
from qibo.config import log, raise_error
1313

14-
QIBO_NATIVE_BACKENDS = ("numpy", "tensorflow", "pytorch")
14+
QIBO_NATIVE_BACKENDS = ("numpy", "tensorflow", "pytorch", "qulacs")
1515
QIBO_NON_NATIVE_BACKENDS = ("qibojit", "qibolab", "qibo-cloud-backends", "qibotn")
1616

1717

@@ -39,6 +39,10 @@ def load(backend: str, **kwargs) -> Backend:
3939
engine = kwargs.pop("platform", None)
4040
kwargs["engine"] = engine
4141
return CliffordBackend(**kwargs)
42+
elif backend == "qulacs":
43+
from qibo.backends.qulacs import QulacsBackend
44+
45+
return QulacsBackend()
4246
else:
4347
raise_error(
4448
ValueError,

src/qibo/backends/qulacs.py

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import re
2+
3+
import numpy as np
4+
import qulacs # pylint: disable=import-error
5+
from qulacs import ( # pylint: disable=no-name-in-module, import-error
6+
QuantumCircuitSimulator,
7+
converter,
8+
)
9+
10+
from qibo import __version__
11+
from qibo.backends import NumpyBackend
12+
from qibo.config import raise_error
13+
from qibo.result import CircuitResult, QuantumState
14+
15+
16+
def circuit_to_qulacs(
17+
circuit: "qibo.Circuit",
18+
) -> "qulacs.QuantumCircuit": # pylint: disable=no-member
19+
"""
20+
Converts a qibo circuit in a qulacs circuit.
21+
22+
Args:
23+
circuit (:class:`qibo.models.circuit.Circuit`): Input circuit to convert.
24+
25+
Returns:
26+
qulacs.QuantumCircuit: The converted qulacs circuit.
27+
"""
28+
qasm_str = re.sub("^//.+\n", "", circuit.to_qasm())
29+
qasm_str = re.sub(r"creg\s.+;", "", qasm_str)
30+
qasm_str = re.sub(r"measure\s.+;", "", qasm_str)
31+
circ = converter.convert_QASM_to_qulacs_circuit(qasm_str.splitlines())
32+
return circ
33+
34+
35+
class QulacsBackend(NumpyBackend):
36+
37+
def __init__(self):
38+
super().__init__()
39+
40+
self.name = "qulacs"
41+
self.versions = {"qibo": __version__, "qulacs": qulacs.__version__}
42+
self.device = "CPU"
43+
44+
def execute_circuit(
45+
self,
46+
circuit: "qibo.Circuit",
47+
initial_state=None,
48+
nshots: int = 1000,
49+
):
50+
"""Execute a circuit with qulacs.
51+
52+
Args:
53+
circuit (:class:`qibo.models.circuit.Circuit`): Input circuit.
54+
nshots (int, optional): Number of shots to perform if ``circuit`` has measurements.
55+
Defaults to :math:`10^{3}`.
56+
57+
Returns:
58+
:class:`qibo.result.CircuitResult`: Object storing to the final results.
59+
"""
60+
if initial_state is not None:
61+
raise_error(
62+
NotImplementedError,
63+
"The use of an initial state is not supported yet by the `QulacsBackend`.",
64+
)
65+
circ = circuit_to_qulacs(circuit)
66+
state = (
67+
qulacs.DensityMatrix(circuit.nqubits) # pylint: disable=no-member
68+
if circuit.density_matrix
69+
else qulacs.QuantumState(circuit.nqubits) # pylint: disable=no-member
70+
)
71+
sim = QuantumCircuitSimulator(circ, state)
72+
sim.simulate()
73+
if circuit.density_matrix:
74+
dim = 2**circuit.nqubits
75+
state = (
76+
state.get_matrix()
77+
.reshape(2 * circuit.nqubits * (2,))
78+
.T.reshape(dim, dim)
79+
)
80+
else:
81+
state = state.get_vector().reshape(circuit.nqubits * (2,)).T.ravel()
82+
if len(circuit.measurements) > 0:
83+
return CircuitResult(
84+
state, circuit.measurements, backend=self, nshots=nshots
85+
)
86+
return QuantumState(state, backend=self)

src/qibo/models/iqae.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,9 @@ def execute(self, backend=None):
322322

323323
# Calling and executing the quantum circuit
324324
qc = self.construct_qae_circuit(k_i)
325-
samples = qc(nshots=n_shots_i).frequencies(binary=True)["1"]
325+
samples = backend.execute_circuit(qc, nshots=n_shots_i).frequencies(
326+
binary=True
327+
)["1"]
326328

327329
samples_history.append(samples)
328330
n_shots_history.append(n_shots_i)

tests/conftest.py

+9
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ def get_backend(backend_name):
4646
except (ModuleNotFoundError, ImportError):
4747
pass
4848

49+
try:
50+
get_backend("qulacs")
51+
QULACS_INSTALLED = True
52+
except ModuleNotFoundError:
53+
QULACS_INSTALLED = False
54+
4955

5056
def pytest_runtest_setup(item):
5157
ALL = {"darwin", "linux"}
@@ -54,6 +60,9 @@ def pytest_runtest_setup(item):
5460
if supported_platforms and plat not in supported_platforms: # pragma: no cover
5561
# case not covered by workflows
5662
pytest.skip(f"Cannot run test on platform {plat}.")
63+
elif not QULACS_INSTALLED and item.fspath.purebasename == "test_backends_qulacs":
64+
# case not covered by workflows
65+
pytest.skip(f"Cannot test `qulacs` on platform {plat}.")
5766

5867

5968
def pytest_configure(config):

tests/test_backends.py

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import platform
2+
import sys
23

34
import numpy as np
45
import pytest
@@ -113,10 +114,14 @@ def test_construct_backend(backend):
113114

114115
def test_list_available_backends():
115116
tensorflow = False if platform.system() == "Windows" else True
117+
qulacs = (
118+
False if platform.system() == "Darwin" and sys.version_info[1] == 9 else True
119+
)
116120
available_backends = {
117121
"numpy": True,
118122
"tensorflow": tensorflow,
119123
"pytorch": True,
124+
"qulacs": qulacs,
120125
"qibojit": {"numba": True, "cupy": False, "cuquantum": False},
121126
"qibolab": False,
122127
"qibo-cloud-backends": False,

tests/test_backends_qulacs.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import random
2+
3+
import numpy as np
4+
import pytest
5+
6+
from qibo import Circuit, gates
7+
from qibo.backends import GlobalBackend, MetaBackend, NumpyBackend, set_backend
8+
from qibo.quantum_info import random_clifford, random_density_matrix, random_statevector
9+
10+
numpy_bkd = NumpyBackend()
11+
12+
13+
@pytest.mark.parametrize("density_matrix", [True, False])
14+
@pytest.mark.parametrize("with_measurements", [True, False])
15+
def test_qulacs(density_matrix, with_measurements):
16+
c = random_clifford(3, backend=numpy_bkd, density_matrix=density_matrix)
17+
if with_measurements:
18+
measured_qubits = random.sample([0, 1, 2], 2)
19+
c.add(gates.M(*measured_qubits))
20+
qulacs_bkd = MetaBackend.load("qulacs")
21+
nshots = 1000
22+
qulacs_res = qulacs_bkd.execute_circuit(c, nshots=nshots)
23+
numpy_res = numpy_bkd.execute_circuit(c, nshots=nshots)
24+
numpy_bkd.assert_allclose(numpy_res.probabilities(), qulacs_res.probabilities())
25+
if with_measurements:
26+
numpy_freq = numpy_res.frequencies(binary=True)
27+
qulacs_freq = qulacs_res.frequencies(binary=True)
28+
numpy_freq = [numpy_freq.get(state, 0) / nshots for state in range(8)]
29+
qulacs_freq = [qulacs_freq.get(state, 0) / nshots for state in range(8)]
30+
numpy_bkd.assert_allclose(numpy_freq, qulacs_freq, atol=1e-1)
31+
32+
33+
def test_initial_state_error():
34+
c = Circuit(1)
35+
qulacs_bkd = MetaBackend.load("qulacs")
36+
initial_state = np.array([0.0, 1.0])
37+
with pytest.raises(NotImplementedError):
38+
qulacs_bkd.execute_circuit(c, initial_state=initial_state)
39+
40+
41+
def test_set_backend():
42+
set_backend("qulacs")
43+
assert GlobalBackend().name == "qulacs"

tests/test_measurements.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ def test_measurement_basis(backend, nqubits, outcome):
424424
c.add(gates.X(q) for q in range(nqubits))
425425
c.add(gates.H(q) for q in range(nqubits))
426426
c.add(gates.M(*range(nqubits), basis=gates.X))
427-
result = c(nshots=100)
427+
result = backend.execute_circuit(c, nshots=100)
428428
assert result.frequencies() == {nqubits * str(outcome): 100}
429429

430430

@@ -435,7 +435,7 @@ def test_measurement_basis_list(backend):
435435
c.add(gates.H(2))
436436
c.add(gates.X(3))
437437
c.add(gates.M(0, 1, 2, 3, basis=[gates.X, gates.Z, gates.X, gates.Z]))
438-
result = c(nshots=100)
438+
result = backend.execute_circuit(c, nshots=100)
439439
assert result.frequencies() == {"0011": 100}
440440
assert (
441441
c.draw()

tests/test_measurements_collapse.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def test_collapse_error(backend):
253253
c = models.Circuit(1)
254254
m = c.add(gates.M(0, collapse=True))
255255
with pytest.raises(Exception) as exc_info:
256-
c()
256+
backend.execute_circuit(c)
257257
assert (
258258
str(exc_info.value)
259259
== "The circuit contains only collapsing measurements (`collapse=True`) but `density_matrix=False`. Please set `density_matrix=True` to retrieve the final state after execution."

tests/test_models_circuit.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -47,26 +47,32 @@ def test_circuit_init():
4747
assert c.nqubits == 2
4848

4949

50-
def test_eigenstate():
50+
def test_eigenstate(backend):
5151
nqubits = 3
5252
c = Circuit(nqubits)
5353
c.add(gates.M(*list(range(nqubits))))
5454
c2 = initialize(nqubits, eigenstate="-")
55-
assert c(nshots=100, initial_state=c2).frequencies() == {"111": 100}
55+
assert backend.execute_circuit(c, nshots=100, initial_state=c2).frequencies() == {
56+
"111": 100
57+
}
5658
c2 = initialize(nqubits, eigenstate="+")
57-
assert c(nshots=100, initial_state=c2).frequencies() == {"000": 100}
59+
assert backend.execute_circuit(c, nshots=100, initial_state=c2).frequencies() == {
60+
"000": 100
61+
}
5862

5963
with pytest.raises(NotImplementedError):
6064
c2 = initialize(nqubits, eigenstate="x")
6165

6266

63-
def test_initialize():
67+
def test_initialize(backend):
6468
nqubits = 3
6569
for gate in [gates.X, gates.Y, gates.Z]:
6670
c = Circuit(nqubits)
6771
c.add(gates.M(*list(range(nqubits)), basis=gate))
6872
c2 = initialize(nqubits, basis=gate)
69-
assert c(nshots=100, initial_state=c2).frequencies() == {"000": 100}
73+
assert backend.execute_circuit(
74+
c, nshots=100, initial_state=c2
75+
).frequencies() == {"000": 100}
7076

7177

7278
@pytest.mark.parametrize("nqubits", [0, -10, 2.5])

tests/test_models_iqae.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from qibo.models.iqae import IQAE
88

99

10-
def test_iqae_init(backend=None):
10+
def test_iqae_init(backend):
1111
A = Circuit(3 + 1)
1212
Q = Circuit(3 + 1)
1313
alpha = 0.05
@@ -23,7 +23,7 @@ def test_iqae_init(backend=None):
2323
assert iqae.method == method
2424

2525

26-
def test_iqae_init_raising_errors(backend=None):
26+
def test_iqae_init_raising_errors(backend):
2727
A = Circuit(3 + 1)
2828
Q = Circuit(4 + 1)
2929
# incorrect values of:
@@ -50,7 +50,7 @@ def test_iqae_init_raising_errors(backend=None):
5050
results = iqae.execute(backend=backend)
5151

5252

53-
def test_iqae_execution(backend=None):
53+
def test_iqae_execution(backend):
5454
# Let's check if we get the correct result for the integral of Sin(x)^2 from 0 to 1
5555
nbit = 3
5656
A = A_circ(qx=list(range(nbit)), qx_measure=nbit, nbit=nbit, b_max=1, b_min=0)

tests/test_models_qcnn.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy as np
44

55
import qibo
6-
from qibo import gates
6+
from qibo import gates, set_backend
77
from qibo.models import Circuit
88
from qibo.models.qcnn import QuantumCNN
99

@@ -13,6 +13,7 @@
1313

1414
def test_classifier_circuit2():
1515
""" """
16+
set_backend("numpy")
1617
nqubits = 2
1718
nlayers = int(nqubits / 2)
1819
init_state = np.ones(2**nqubits) / np.sqrt(2**nqubits) #
@@ -77,6 +78,7 @@ def get_real_vector2():
7778

7879
def test_classifier_circuit4():
7980
""" """
81+
set_backend("numpy")
8082
nqubits = 4
8183
nlayers = int(nqubits / 2)
8284
init_state = np.ones(2**nqubits) / np.sqrt(2**nqubits) #
@@ -278,6 +280,8 @@ def test_1_qubit_classifier_circuit_error():
278280
def test_qcnn_training():
279281
import random
280282

283+
set_backend("numpy")
284+
281285
# generate 2 random states and labels for pytest
282286
data = np.zeros([2, 16])
283287
for i in range(2):

0 commit comments

Comments
 (0)