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

QulacsBackend #1272

Merged
merged 49 commits into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
02325c6
feat: added qualcs backend
BrunoLiegiBastonLiegi Mar 18, 2024
20e03a9
feat: removed cache files
BrunoLiegiBastonLiegi Mar 18, 2024
41f1117
feat: added test
BrunoLiegiBastonLiegi Mar 19, 2024
d6a51cc
fix: removed cache file
BrunoLiegiBastonLiegi Mar 19, 2024
e8e3558
build: added qulacs as an optional dependency
BrunoLiegiBastonLiegi Mar 19, 2024
a16e40e
fix: removed the optimize option
BrunoLiegiBastonLiegi Mar 19, 2024
32a4ae9
build added test dependency
BrunoLiegiBastonLiegi Mar 19, 2024
eab30fc
feat: added qulacs to set_backend
BrunoLiegiBastonLiegi Mar 19, 2024
2bc412b
fix: disabled some pylint errors
BrunoLiegiBastonLiegi Mar 19, 2024
c5e1b39
fix: fixed order of qubits in the state
BrunoLiegiBastonLiegi Mar 19, 2024
4f0aacb
fix: improved coverage
BrunoLiegiBastonLiegi Mar 19, 2024
bb7417b
fix: removed the possibility to set an initial state
BrunoLiegiBastonLiegi Mar 19, 2024
9b9e1a7
doc: added a paragraph about qulacs in the docs
BrunoLiegiBastonLiegi Apr 30, 2024
8ffa7f7
feat: added some dosctrings
BrunoLiegiBastonLiegi Apr 30, 2024
1a642ef
build: updated lock
BrunoLiegiBastonLiegi Apr 30, 2024
f8abd22
Merge branch 'master' into qulacs
BrunoLiegiBastonLiegi Apr 30, 2024
d2a9a46
build: merge master
BrunoLiegiBastonLiegi Apr 30, 2024
02fb9f2
build: merge master + lock update
BrunoLiegiBastonLiegi May 8, 2024
5ecc5d8
doc: added qulacs bullet in getting_started section
BrunoLiegiBastonLiegi May 13, 2024
b2f3a91
build: update lock
BrunoLiegiBastonLiegi May 13, 2024
f873e51
fix: removed wheel files
BrunoLiegiBastonLiegi May 13, 2024
df47024
Merge branch 'master' into qulacs
BrunoLiegiBastonLiegi May 15, 2024
530ceb3
docs: update backends and main diagrams
MatteoRobbiati May 15, 2024
c9e6ee1
build: update qulacs minimum version
BrunoLiegiBastonLiegi May 28, 2024
791e11a
build: merge master
BrunoLiegiBastonLiegi May 28, 2024
db03b60
updating poetry
scarrazza May 30, 2024
0fafeb1
fix: added qulacs to QIBO_NATIVE_BACKENDS
BrunoLiegiBastonLiegi Jun 4, 2024
c467ee3
build: replaced macos-latest with macos-13
BrunoLiegiBastonLiegi Jun 11, 2024
04b8671
build: replaced macos-latest with macos-13 in rules.yml
BrunoLiegiBastonLiegi Jun 11, 2024
b89d382
build: trying with macos-12
BrunoLiegiBastonLiegi Jun 11, 2024
5fe371a
fix: restored macos-latest and skipping qulacs tests when not installed
BrunoLiegiBastonLiegi Jun 11, 2024
471dd44
fix: fixed import of qulacs objects
BrunoLiegiBastonLiegi Jun 11, 2024
494ef5d
fix: fix lint
BrunoLiegiBastonLiegi Jun 11, 2024
ec1d8bb
build: restricting python version for qulacs under macos
BrunoLiegiBastonLiegi Jun 11, 2024
e33471f
build: fix python restriction
BrunoLiegiBastonLiegi Jun 11, 2024
5c11cb6
build: fixed qulacs dep for docs
BrunoLiegiBastonLiegi Jun 11, 2024
a7bb2a4
build: this is driving me crazy
BrunoLiegiBastonLiegi Jun 11, 2024
885c680
fix: I love pylint
BrunoLiegiBastonLiegi Jun 11, 2024
7f51cda
fix: added qulacs to list available backends test
BrunoLiegiBastonLiegi Jun 14, 2024
89b0997
build: merge master
BrunoLiegiBastonLiegi Jun 17, 2024
3c8cccf
fix: fixing various global backend related tests
BrunoLiegiBastonLiegi Jun 17, 2024
a579c1f
fix: fix list available backends test
BrunoLiegiBastonLiegi Jun 17, 2024
b7c32ca
Update src/qibo/backends/qulacs.py
BrunoLiegiBastonLiegi Jun 25, 2024
9a7fdaa
feat: raise error with initial_state + removed some attributes from t…
BrunoLiegiBastonLiegi Jun 25, 2024
94d5c89
fix: restored the simulator and converter attributes
BrunoLiegiBastonLiegi Jun 25, 2024
f29ae65
fix: removed attributes and moved qulacs imports
BrunoLiegiBastonLiegi Jun 26, 2024
27dac43
remove qulacs from attributes
BrunoLiegiBastonLiegi Jun 29, 2024
a59041d
removing self.qulacs calls
BrunoLiegiBastonLiegi Jun 29, 2024
6033640
Merge branch 'master' into qulacs
BrunoLiegiBastonLiegi Jun 29, 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
10 changes: 10 additions & 0 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2440,6 +2440,16 @@ The default backend order is qibojit (if available), tensorflow (if available),
numpy. The default backend can be changed using the ``QIBO_BACKEND`` environment
variable.

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

.. testcode:: python

import qibo
qibo.set_backend("qulacs")

.. note::
GPU simulation through ``qulacs`` is not supported yet.

.. autoclass:: qibo.backends.abstract.Backend
:members:
:member-order: bysource
Expand Down
Binary file modified doc/source/getting-started/backends.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/source/getting-started/backends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ if the corresponding packages are installed, following the hierarchy below:
* :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.
* :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`.
* `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.
* `qulacs <https://github.com/qulacs/qulacs>`_: an interface to the `qulacs` library for quantum simulation. GPU support is not available yet.

The default backend that is used is the first available from the above list.
The user can switch to a different using the ``qibo.set_backend`` method
Expand Down
Binary file modified doc/source/qibo_ecosystem.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
240 changes: 139 additions & 101 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ numpy = "^1.26.4"
networkx = "^3.2.1"
tensorflow = { version = "^2.16.1", markers = "sys_platform == 'linux' or sys_platform == 'darwin'", optional = true }
torch = { version = "^2.1.1", optional = true }
qulacs = { version = "^0.6.4", optional = true, markers="(sys_platform == 'darwin' and python_version > '3.9') or sys_platform != 'darwin'"}

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

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

[tool.poe.tasks]
test = "pytest"
Expand Down
6 changes: 5 additions & 1 deletion src/qibo/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from qibo.backends.tensorflow import TensorflowBackend
from qibo.config import log, raise_error

QIBO_NATIVE_BACKENDS = ("numpy", "tensorflow", "pytorch")
QIBO_NATIVE_BACKENDS = ("numpy", "tensorflow", "pytorch", "qulacs")
alecandido marked this conversation as resolved.
Show resolved Hide resolved
QIBO_NON_NATIVE_BACKENDS = ("qibojit", "qibolab", "qibo-cloud-backends", "qibotn")


Expand Down Expand Up @@ -39,6 +39,10 @@ def load(backend: str, **kwargs) -> Backend:
engine = kwargs.pop("platform", None)
kwargs["engine"] = engine
return CliffordBackend(**kwargs)
elif backend == "qulacs":
from qibo.backends.qulacs import QulacsBackend

return QulacsBackend()
else:
raise_error(
ValueError,
Expand Down
86 changes: 86 additions & 0 deletions src/qibo/backends/qulacs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import re

import numpy as np
import qulacs # pylint: disable=import-error
from qulacs import ( # pylint: disable=no-name-in-module, import-error
Comment on lines +4 to +5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this exceptions because otherwise pylint fails for mac with py3.9 in the CI? If yes, I guess that's fine, otherwise I am not sure if they are needed given that CI is using the test dependencies which include qulacs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if I remember correctly pylint was complaining in macos-python3.9

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say this is definitely the best way you could do it. At least is confined within the qibo.backends.qulacs module, though not restricted to the offending macos+py3.9 combo.

If done in the workflow, it would be positioned in a larger scope, though you may be able to ignore specifically that file for that combo.

QuantumCircuitSimulator,
converter,
)

from qibo import __version__
from qibo.backends import NumpyBackend
from qibo.config import raise_error
from qibo.result import CircuitResult, QuantumState


def circuit_to_qulacs(
circuit: "qibo.Circuit",
) -> "qulacs.QuantumCircuit": # pylint: disable=no-member
"""
Converts a qibo circuit in a qulacs circuit.

Args:
circuit (:class:`qibo.models.circuit.Circuit`): Input circuit to convert.

Returns:
qulacs.QuantumCircuit: The converted qulacs circuit.
"""
qasm_str = re.sub("^//.+\n", "", circuit.to_qasm())
qasm_str = re.sub(r"creg\s.+;", "", qasm_str)
qasm_str = re.sub(r"measure\s.+;", "", qasm_str)
circ = converter.convert_QASM_to_qulacs_circuit(qasm_str.splitlines())
return circ


class QulacsBackend(NumpyBackend):

def __init__(self):
super().__init__()

self.name = "qulacs"
self.versions = {"qibo": __version__, "qulacs": qulacs.__version__}
self.device = "CPU"

def execute_circuit(
self,
circuit: "qibo.Circuit",
initial_state=None,
nshots: int = 1000,
):
"""Execute a circuit with qulacs.

Args:
circuit (:class:`qibo.models.circuit.Circuit`): Input circuit.
nshots (int, optional): Number of shots to perform if ``circuit`` has measurements.
Defaults to :math:`10^{3}`.

Returns:
:class:`qibo.result.CircuitResult`: Object storing to the final results.
"""
if initial_state is not None:
raise_error(
NotImplementedError,
"The use of an initial state is not supported yet by the `QulacsBackend`.",
)
circ = circuit_to_qulacs(circuit)
state = (
qulacs.DensityMatrix(circuit.nqubits) # pylint: disable=no-member
if circuit.density_matrix
else qulacs.QuantumState(circuit.nqubits) # pylint: disable=no-member
)
sim = QuantumCircuitSimulator(circ, state)
sim.simulate()
if circuit.density_matrix:
dim = 2**circuit.nqubits
state = (
state.get_matrix()
.reshape(2 * circuit.nqubits * (2,))
.T.reshape(dim, dim)
)
else:
state = state.get_vector().reshape(circuit.nqubits * (2,)).T.ravel()
if len(circuit.measurements) > 0:
return CircuitResult(
state, circuit.measurements, backend=self, nshots=nshots
)
return QuantumState(state, backend=self)
4 changes: 3 additions & 1 deletion src/qibo/models/iqae.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,9 @@ def execute(self, backend=None):

# Calling and executing the quantum circuit
qc = self.construct_qae_circuit(k_i)
samples = qc(nshots=n_shots_i).frequencies(binary=True)["1"]
samples = backend.execute_circuit(qc, nshots=n_shots_i).frequencies(
binary=True
)["1"]

samples_history.append(samples)
n_shots_history.append(n_shots_i)
Expand Down
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ def get_backend(backend_name):
except (ModuleNotFoundError, ImportError):
pass

try:
get_backend("qulacs")
QULACS_INSTALLED = True
except ModuleNotFoundError:
QULACS_INSTALLED = False


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


def pytest_configure(config):
Expand Down
5 changes: 5 additions & 0 deletions tests/test_backends.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import platform
import sys

import numpy as np
import pytest
Expand Down Expand Up @@ -113,10 +114,14 @@ def test_construct_backend(backend):

def test_list_available_backends():
tensorflow = False if platform.system() == "Windows" else True
qulacs = (
False if platform.system() == "Darwin" and sys.version_info[1] == 9 else True
)
available_backends = {
"numpy": True,
"tensorflow": tensorflow,
"pytorch": True,
"qulacs": qulacs,
"qibojit": {"numba": True, "cupy": False, "cuquantum": False},
"qibolab": False,
"qibo-cloud-backends": False,
Expand Down
43 changes: 43 additions & 0 deletions tests/test_backends_qulacs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import random

import numpy as np
import pytest

from qibo import Circuit, gates
from qibo.backends import GlobalBackend, MetaBackend, NumpyBackend, set_backend
from qibo.quantum_info import random_clifford, random_density_matrix, random_statevector

numpy_bkd = NumpyBackend()


@pytest.mark.parametrize("density_matrix", [True, False])
@pytest.mark.parametrize("with_measurements", [True, False])
def test_qulacs(density_matrix, with_measurements):
c = random_clifford(3, backend=numpy_bkd, density_matrix=density_matrix)
if with_measurements:
measured_qubits = random.sample([0, 1, 2], 2)
c.add(gates.M(*measured_qubits))
qulacs_bkd = MetaBackend.load("qulacs")
nshots = 1000
qulacs_res = qulacs_bkd.execute_circuit(c, nshots=nshots)
numpy_res = numpy_bkd.execute_circuit(c, nshots=nshots)
numpy_bkd.assert_allclose(numpy_res.probabilities(), qulacs_res.probabilities())
if with_measurements:
numpy_freq = numpy_res.frequencies(binary=True)
qulacs_freq = qulacs_res.frequencies(binary=True)
numpy_freq = [numpy_freq.get(state, 0) / nshots for state in range(8)]
qulacs_freq = [qulacs_freq.get(state, 0) / nshots for state in range(8)]
numpy_bkd.assert_allclose(numpy_freq, qulacs_freq, atol=1e-1)


def test_initial_state_error():
c = Circuit(1)
qulacs_bkd = MetaBackend.load("qulacs")
initial_state = np.array([0.0, 1.0])
with pytest.raises(NotImplementedError):
qulacs_bkd.execute_circuit(c, initial_state=initial_state)


def test_set_backend():
set_backend("qulacs")
assert GlobalBackend().name == "qulacs"
4 changes: 2 additions & 2 deletions tests/test_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ def test_measurement_basis(backend, nqubits, outcome):
c.add(gates.X(q) for q in range(nqubits))
c.add(gates.H(q) for q in range(nqubits))
c.add(gates.M(*range(nqubits), basis=gates.X))
result = c(nshots=100)
result = backend.execute_circuit(c, nshots=100)
assert result.frequencies() == {nqubits * str(outcome): 100}


Expand All @@ -435,7 +435,7 @@ def test_measurement_basis_list(backend):
c.add(gates.H(2))
c.add(gates.X(3))
c.add(gates.M(0, 1, 2, 3, basis=[gates.X, gates.Z, gates.X, gates.Z]))
result = c(nshots=100)
result = backend.execute_circuit(c, nshots=100)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like a lot the explicit backend usage, but why do you need it for this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because otherwise only the GlobalBackend is tested here, if I am not mistaken. Anyway, this was failing with the QulacsBackend, but was not supposed to be testing it, even though it did due to the GlobalBackend usage.

Copy link
Member

@alecandido alecandido Jun 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that @stavros11 set these tests to run with all the available backends, through a mechanism implemented in conftest.py

qibo/tests/conftest.py

Lines 63 to 87 in c68e58b

@pytest.fixture
def backend(backend_name):
yield get_backend(backend_name)
def pytest_generate_tests(metafunc):
module_name = metafunc.module.__name__
if module_name == "tests.test_models_distcircuit_execution":
config = [(bk, acc) for acc in ACCELERATORS for bk in MULTIGPU_BACKENDS]
metafunc.parametrize("backend_name,accelerators", config)
else:
if "backend_name" in metafunc.fixturenames:
if "accelerators" in metafunc.fixturenames:
config = [(backend, None) for backend in AVAILABLE_BACKENDS]
config.extend(
(bk, acc) for acc in ACCELERATORS for bk in MULTIGPU_BACKENDS
)
metafunc.parametrize("backend_name,accelerators", config)
else:
metafunc.parametrize("backend_name", AVAILABLE_BACKENDS)
elif "accelerators" in metafunc.fixturenames:
metafunc.parametrize("accelerators", ACCELERATORS)

It's true that is using the GlobalBackend, but it should be also setting the backend registered there to all the possible backends.
So, the backend.execute_circuit(c, *args) should be fully equivalent to have the backend fixture calling set_backend(), and use c(*args).

However, that's not the case, but I'm worried that all tests should be updated to be consistent, unless they are calling set_backend() within the test.

One way or the other, I'd like if there were a unique way of doing things. And in this PR, I'd use the current one, even if it's suboptimal.
If there is truly a better way (which most likely it's true, and it will be the one you're using, or, slightly worse, changing the backend fixture to set the backend as well), we should open a PR right after this one to implement it consistently over all the tests that are backend-dependent.

What do you think? @BrunoLiegiBastonLiegi @stavros11

Copy link
Member

@stavros11 stavros11 Jun 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that @stavros11 set these tests to run with all the available backends, through a mechanism implemented in conftest.py

If I remember correctly, the backend fixture does not set the GlobalBackend. Therefore

c(nshots=100)

in a test that uses the backend fixture is not equivalent to

backend.execute_circuit(c, nshots=100)

The second will indeed test all different available backends, while the first will only test whatever GlobalBackend was set at that time. Actually, it will probably repeat the test using the same GlobalBackend multiple times, because the fixture will still loop over all available backends.

The GlobalBackend in this case is not very well defined, because if a previous test switches it (using set_backend) then this value will be used in all upcoming tests, until it is switched by another test. This is usually bad (as is the idea of GlobalBackend, and more generally global variables), because tests that are supposed to be independent actually depend on what happened in previous tests.

I think the general idea is to avoid using the GlobalBackend in tests, except for very few tests that exist for testing the GlobalBackend functionality itself. All tests of other features should use the backend.execute_circuit approach. If a test needs to use the GlobalBackend, this should ideally be done as:

def test_...():
    original_backend = qibo.get_backend()
    qibo.set_backend("backend to test")
    # test code here
    qibo.set_backend(original_backend)

We could also wrap this to another (or the same?) fixture, but I don't think that exists right now.

Copy link
Member

@alecandido alecandido Jun 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it doesn't, indeed I wrote it (in a bit convoluted way) in my previous comment (that was mainly hypothetical, eventually negating the hypothesis...).

I actually searched, and only a few tests are actually using set_backend(), most of them just to test it.

Tests using set_backend

: rg set_backend
test_backends_qibotn.py
13:    qibo.set_backend(backend="qibotn", platform="qutensornet", runcard=None)
16:    qibo.set_backend("numpy")

test_backends_clifford.py
8:from qibo import Circuit, gates, set_backend
35:def test_set_backend(backend):
38:    set_backend("clifford", platform=platform)
46:    set_backend(backend.name, platform=backend.platform)
52:    set_backend("numpy")

test_models_qcnn.py
319:    qibo.set_backend("qibojit")

test_backends_global.py
7:def test_set_backend():
11:    qibo.set_backend("numpy")
31:    qibo.set_backend("numpy")
46:    qibo.set_backend("numpy")
83:    qibo.set_backend("numpy")
91:    qibo.set_backend("numpy")

test_models_dbi.py
6:from qibo import hamiltonians, set_backend

test_backends.py
6:from qibo import construct_backend, gates, list_available_backends, set_backend
130:def test_set_backend_error():
132:        set_backend("non-existing-backend")

Many more are already using backend.execute_circuit(), against my expectation.

So, fine, this PR is aligning to the majority, and that's good. Sorry for doubting it.
We should just fix the other tests (like test_models_qcnn.py) or remove the import where is not even used (i.e. test_models_dbi.py, ...).

Copy link
Member

@stavros11 stavros11 Jun 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually searched, and only a few tests are actually using set_backend(), most of them just to test it.

This is fine, but there are also tests that are using circuit() without set_backend(). These are also invoking the GlobalBackend to execute the circuit and are even worse, because the GlobalBackend will be whetever was set beforehand (by the last set_backend() of a previous test). These may also be a bit harder to search for, because they can appear in various ways: circuit(), circuit.execute(), c(), whatever_circuit_name(), c(nshots=100) (like the one fixed here), etc.. An easy way would be to remove Circuit.__call__ and Circuit.execute methods and see which tests fail.

By the way, I think that this discussion is not very relevant to this PR and maybe should be converted to an issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I think that this discussion is not very relevant to this PR and maybe should be converted to an issue.

I agree about the issue. If you wish, feel free to open yourself, you're definitely the most expert.

An easy way would be to remove Circuit.__call__ and Circuit.execute methods and see which tests fail.

Another option is to keep Circuit.__call__, but require a backend argument to be passed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree about the issue. If you wish, feel free to open yourself, you're definitely the most expert.

#1374

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a list of tests relying on a random state of the GlobalBackend?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a list of tests relying on a random state of the GlobalBackend?

These are the tests that execute circuits using the circuit.execute interface, which invokes the GlobalBackend():

  • test_backends_global::test_circuit_execution (sets to numpy)
  • test_measurements::test_measurement_compiled_circuit (does not set)
  • test_models_circuit_execution::test_compiled_execute (does not set)
  • Various tests in test_models_qcnn are using the GlobalBackend but setting it beforehand to qibojit or numpy without reseting it after.
  • test_models_variational::test_vqe (does not set)
  • test_models_variational::test_custom_loss (does not set)

assert result.frequencies() == {"0011": 100}
assert (
c.draw()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_measurements_collapse.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def test_collapse_error(backend):
c = models.Circuit(1)
m = c.add(gates.M(0, collapse=True))
with pytest.raises(Exception) as exc_info:
c()
backend.execute_circuit(c)
assert (
str(exc_info.value)
== "The circuit contains only collapsing measurements (`collapse=True`) but `density_matrix=False`. Please set `density_matrix=True` to retrieve the final state after execution."
Expand Down
16 changes: 11 additions & 5 deletions tests/test_models_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,32 @@ def test_circuit_init():
assert c.nqubits == 2


def test_eigenstate():
def test_eigenstate(backend):
nqubits = 3
c = Circuit(nqubits)
c.add(gates.M(*list(range(nqubits))))
c2 = initialize(nqubits, eigenstate="-")
assert c(nshots=100, initial_state=c2).frequencies() == {"111": 100}
assert backend.execute_circuit(c, nshots=100, initial_state=c2).frequencies() == {
"111": 100
}
c2 = initialize(nqubits, eigenstate="+")
assert c(nshots=100, initial_state=c2).frequencies() == {"000": 100}
assert backend.execute_circuit(c, nshots=100, initial_state=c2).frequencies() == {
"000": 100
}

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


def test_initialize():
def test_initialize(backend):
nqubits = 3
for gate in [gates.X, gates.Y, gates.Z]:
c = Circuit(nqubits)
c.add(gates.M(*list(range(nqubits)), basis=gate))
c2 = initialize(nqubits, basis=gate)
assert c(nshots=100, initial_state=c2).frequencies() == {"000": 100}
assert backend.execute_circuit(
c, nshots=100, initial_state=c2
).frequencies() == {"000": 100}


@pytest.mark.parametrize("nqubits", [0, -10, 2.5])
Expand Down
6 changes: 3 additions & 3 deletions tests/test_models_iqae.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from qibo.models.iqae import IQAE


def test_iqae_init(backend=None):
def test_iqae_init(backend):
A = Circuit(3 + 1)
Q = Circuit(3 + 1)
alpha = 0.05
Expand All @@ -23,7 +23,7 @@ def test_iqae_init(backend=None):
assert iqae.method == method


def test_iqae_init_raising_errors(backend=None):
def test_iqae_init_raising_errors(backend):
A = Circuit(3 + 1)
Q = Circuit(4 + 1)
# incorrect values of:
Expand All @@ -50,7 +50,7 @@ def test_iqae_init_raising_errors(backend=None):
results = iqae.execute(backend=backend)


def test_iqae_execution(backend=None):
def test_iqae_execution(backend):
# Let's check if we get the correct result for the integral of Sin(x)^2 from 0 to 1
nbit = 3
A = A_circ(qx=list(range(nbit)), qx_measure=nbit, nbit=nbit, b_max=1, b_min=0)
Expand Down
6 changes: 5 additions & 1 deletion tests/test_models_qcnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np

import qibo
from qibo import gates
from qibo import gates, set_backend
from qibo.models import Circuit
from qibo.models.qcnn import QuantumCNN

Expand All @@ -13,6 +13,7 @@

def test_classifier_circuit2():
""" """
set_backend("numpy")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the discussion above, this is also not very good practice because it will also set numpy as the GlobalBackend for all upcoming tests. However, since everything should work with numpy, it will probably not cause any issue. Maybe we could just open an issue for the test discussion and postpone for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree, however the QCNN model doesn't seem to support the specification of the execution backend and always uses the GlobalBackend, I opened an issue about this already #1362. The tests were originally using the NumpyBackend (the global one) and thus I set it explicitely here.

nqubits = 2
nlayers = int(nqubits / 2)
init_state = np.ones(2**nqubits) / np.sqrt(2**nqubits) #
Expand Down Expand Up @@ -77,6 +78,7 @@ def get_real_vector2():

def test_classifier_circuit4():
""" """
set_backend("numpy")
nqubits = 4
nlayers = int(nqubits / 2)
init_state = np.ones(2**nqubits) / np.sqrt(2**nqubits) #
Expand Down Expand Up @@ -278,6 +280,8 @@ def test_1_qubit_classifier_circuit_error():
def test_qcnn_training():
import random

set_backend("numpy")

# generate 2 random states and labels for pytest
data = np.zeros([2, 16])
for i in range(2):
Expand Down
Loading