From 89430badb1c8843edffd8c3f3d00070a6a5fed07 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Thu, 29 Apr 2021 16:31:09 +0400 Subject: [PATCH 01/19] Remove MatmulEinsum backend --- src/qibo/backends/__init__.py | 13 +--- src/qibo/backends/einsum.py | 80 ------------------------ src/qibo/backends/numpy.py | 64 +------------------ src/qibo/backends/tensorflow.py | 46 +------------- src/qibo/tests/conftest.py | 2 +- src/qibo/tests_new/conftest.py | 1 - src/qibo/tests_new/test_backends_init.py | 4 +- src/qibo/tests_new/test_core_circuit.py | 2 +- 8 files changed, 9 insertions(+), 203 deletions(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index 2ff787a148..c36fcbf476 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -2,8 +2,8 @@ from pkgutil import iter_modules from qibo import config from qibo.config import raise_error, log, warnings -from qibo.backends.numpy import NumpyDefaultEinsumBackend, NumpyMatmulEinsumBackend -from qibo.backends.tensorflow import TensorflowCustomBackend, TensorflowDefaultEinsumBackend, TensorflowMatmulEinsumBackend +from qibo.backends.numpy import NumpyDefaultEinsumBackend +from qibo.backends.tensorflow import TensorflowCustomBackend, TensorflowDefaultEinsumBackend def _check_availability(module_name): @@ -21,7 +21,6 @@ def __init__(self): if _check_availability("numpy"): self.available_backends["numpy"] = NumpyDefaultEinsumBackend self.available_backends["numpy_defaulteinsum"] = NumpyDefaultEinsumBackend - self.available_backends["numpy_matmuleinsum"] = NumpyMatmulEinsumBackend else: # pragma: no cover raise_error(ModuleNotFoundError, "Numpy is not installed.") @@ -30,9 +29,7 @@ def __init__(self): os.environ["TF_CPP_MIN_LOG_LEVEL"] = str(config.LOG_LEVEL) import tensorflow as tf self.available_backends["defaulteinsum"] = TensorflowDefaultEinsumBackend - self.available_backends["matmuleinsum"] = TensorflowMatmulEinsumBackend self.available_backends["tensorflow_defaulteinsum"] = TensorflowDefaultEinsumBackend - self.available_backends["tensorflow_matmuleinsum"] = TensorflowMatmulEinsumBackend if _check_availability("qibo_sim_tensorflow"): self.available_backends["custom"] = TensorflowCustomBackend self.available_backends["tensorflow"] = TensorflowCustomBackend @@ -46,9 +43,7 @@ def __init__(self): log.warning("Tensorflow is not installed. Falling back to numpy. " "Numpy does not support Qibo custom operators and GPU. " "Einsum will be used to apply gates on CPU.") - # use numpy for defaulteinsum and matmuleinsum backends self.available_backends["defaulteinsum"] = NumpyDefaultEinsumBackend - self.available_backends["matmuleinsum"] = NumpyMatmulEinsumBackend self.constructed_backends = {} self._active_backend = None @@ -115,9 +110,7 @@ def set_backend(backend="custom"): The following backends are available: 'custom': Tensorflow backend with custom operators for applying gates, 'defaulteinsum': Tensorflow backend that applies gates using ``tf.einsum``, - 'matmuleinsum': Tensorflow backend that applies gates using ``tf.matmul``, - 'numpy_defaulteinsum': Numpy backend that applies gates using ``np.einsum``, - 'numpy_matmuleinsum': Numpy backend that applies gates using ``np.matmul``, + 'numpy_defaulteinsum': Numpy backend that applies gates using ``np.einsum``. Args: backend (str): A backend from the above options. diff --git a/src/qibo/backends/einsum.py b/src/qibo/backends/einsum.py index 285b2f0f02..152baea590 100644 --- a/src/qibo/backends/einsum.py +++ b/src/qibo/backends/einsum.py @@ -140,86 +140,6 @@ def _calculate_density_matrix_controlled(self): self._right0 = f"{c}{rest}{self.input},{self.gate}->{c}{rest}{self.output}" -class MatmulEinsumCache(BaseCache): - """Cache object required by the :class:`qibo.core.einsum.MatmulEinsum` backend. - - The ``vector``, ``left``, ``right``, ``left0``, ``right0`` properties are dictionaries - that hold the following keys: - - * ``ids``: Indices for the transposition before matmul. - * ``inverse_ids``: Indices for the transposition after matmul. - * ``shapes``: Tuple with four shapes that are required for ``tf.reshape`` in the ``__call__`` method of :class:`qibo.core.einsum.MatmulEinsum`. - - Args: - qubits (list): List with the qubit indices that the gate is applied to. - nqubits (int): Total number of qubits in the circuit / state vector. - ncontrol (int): Number of control qubits for `controlled_by` gates. - """ - - def __init__(self, qubits: Sequence[int], nqubits: int, - ncontrol: Optional[int] = None): - super(MatmulEinsumCache, self).__init__(nqubits, ncontrol) - self.ntargets = len(qubits) - self.nrest = nqubits - self.ntargets - self.nstates = 2 ** nqubits - - last_index = 0 - target_ids, rest_ids = {}, [] - self.shape = [] - for q in sorted(qubits): - if q > last_index: - self.shape.append(2 ** (q - last_index)) - rest_ids.append(len(self.shape) - 1) - self.shape.append(2) - target_ids[q] = len(self.shape) - 1 - last_index = q + 1 - if last_index < self.nqubits: - self.shape.append(2 ** (self.nqubits - last_index)) - rest_ids.append(len(self.shape) - 1) - - self.ids = [target_ids[q] for q in qubits] + rest_ids - self.transposed_shape = [] - self.inverse_ids = len(self.ids) * [0] - for i, r in enumerate(self.ids): - self.inverse_ids[r] = i - self.transposed_shape.append(self.shape[r]) - - self.shape = tuple(self.shape) - self.transposed_shape = tuple(self.transposed_shape) - self._vector = {"ids": self.ids, "inverse_ids": self.inverse_ids, - "shapes": (self.shape, - (2 ** self.ntargets, 2 ** self.nrest), - self.transposed_shape, - self.nqubits * (2,)), - "conjugate": False} - - def cast_shapes(self, cast_func): - for attr in ["_vector", "_left", "_right", "_left0", "_right0"]: - d = getattr(self, attr) - if d is not None: - d["shapes"] = tuple(cast_func(s) for s in d["shapes"]) - - def _calculate_density_matrix(self): - self._left = {"ids": self.ids + [len(self.ids)], - "inverse_ids": self.inverse_ids + [len(self.ids)], - "shapes": (self.shape + (self.nstates,), - (2 ** self.ntargets, (2 ** self.nrest) * self.nstates), - self.transposed_shape + (self.nstates,), - 2 * self.nqubits * (2,)), - "conjugate": False} - - self._right = dict(self._left) - self._right["inverse_ids"] = [len(self.ids)] + self.inverse_ids - self._right["conjugate"] = True - - def _calculate_density_matrix_controlled(self): # pragma: no cover - # `MatmulEinsum` falls back to `DefaultEinsum` if `controlled_by` - # and density matrices are used simultaneously due to an error - raise_error(NotImplementedError, - "MatmulEinsum backend is not implemented when multicontrol " - "gates are used on density matrices.") - - class ControlCache: """Helper tools for `controlled_by` gates. diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index 53fec9f62b..c4401875f7 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -247,15 +247,6 @@ def create_einsum_cache(self, qubits, nqubits, ncontrol=None): # pragma: no cove def einsum_call(self, cache, state, matrix): # pragma: no cover raise_error(NotImplementedError) - def _get_default_einsum(self): - # finds default einsum backend of the same engine to use for fall back - # in the case of `controlled_by` gates used on density matrices where - # matmul einsum does not work properly - import sys - module = sys.modules[self.__class__.__module__] - engine = self.name.split("_")[0].capitalize() - return getattr(module, f"{engine}DefaultEinsumBackend") - class GateCache: pass @@ -269,16 +260,8 @@ def create_gate_cache(self, gate): nactive = gate.nqubits - len(gate.control_qubits) targets = cache.control_cache.targets ncontrol = len(gate.control_qubits) - if gate.density_matrix: - # fall back to the 'defaulteinsum' backend when using - # density matrices with `controlled_by` gates because - # 'matmuleinsum' is not properly implemented for this case - backend = self._get_default_einsum() - cache.calculation_cache = backend.create_einsum_cache( - self, targets, nactive, ncontrol) - else: - cache.calculation_cache = self.create_einsum_cache( - targets, nactive, ncontrol) + cache.calculation_cache = self.create_einsum_cache( + targets, nactive, ncontrol) else: cache.calculation_cache = self.create_einsum_cache(gate.qubits, gate.nqubits) cache.calculation_cache.cast_shapes( @@ -395,46 +378,3 @@ def create_einsum_cache(self, qubits, nqubits, ncontrol=None): def einsum_call(self, cache, state, matrix): return self.einsum(cache, state, matrix) - - -class NumpyMatmulEinsumBackend(NumpyBackend): - - description = "Uses `np.matmul` as well as transpositions and reshapes " \ - "to apply gates to states via matrix multiplication." - - def __init__(self): - super().__init__() - self.name = "numpy_matmuleinsum" - self.custom_gates = False - - def create_einsum_cache(self, qubits, nqubits, ncontrol=None): - return self.einsum_module.MatmulEinsumCache(qubits, nqubits, ncontrol) - - def einsum_call(self, cache, state, matrix): - if isinstance(cache, str): - # `controlled_by` gate acting on density matrices - # fall back to defaulteinsum because matmuleinsum is not properly - # implemented. - backend = self._get_default_einsum() - return backend.einsum_call(self, cache, state, matrix) - - shapes = cache["shapes"] - - state = self.reshape(state, shapes[0]) - state = self.transpose(state, cache["ids"]) - if cache["conjugate"]: - state = self.reshape(self.conj(state), shapes[1]) - else: - state = self.reshape(state, shapes[1]) - - n = len(tuple(matrix.shape)) - if n > 2: - dim = 2 ** (n // 2) - state = self.matmul(self.reshape(matrix, (dim, dim)), state) - else: - state = self.matmul(matrix, state) - - state = self.reshape(state, shapes[2]) - state = self.transpose(state, cache["inverse_ids"]) - state = self.reshape(state, shapes[3]) - return state diff --git a/src/qibo/backends/tensorflow.py b/src/qibo/backends/tensorflow.py index 9d8bbcfe6e..6346b7261e 100644 --- a/src/qibo/backends/tensorflow.py +++ b/src/qibo/backends/tensorflow.py @@ -291,13 +291,7 @@ def density_matrix_collapse(self, gate, state, result): class TensorflowDefaultEinsumBackend(TensorflowBackend): - """Gate application backend that based on default ``einsum``. - - This is the most efficient implementation for GPU, however its - backpropagation is not working properly for complex numbers. - The user should switch to :class:`qibo.core.einsum.MatmulEinsum` - if automatic differentiation is required. - """ + """Gate application backend that based on ``tf.einsum``.""" description = "Uses `tf.einsum` to apply gates to states via matrix " \ "multiplication." @@ -315,41 +309,3 @@ def create_einsum_cache(self, qubits, nqubits, ncontrol=None): def einsum_call(self, cache, state, matrix): return numpy.NumpyDefaultEinsumBackend.einsum_call( self, cache, state, matrix) - - -class TensorflowMatmulEinsumBackend(TensorflowBackend): - - """Gate application backend based on ``matmul``. - - For Tensorflow this is more efficient than ``einsum`` on CPU but slower on GPU. - The matmul version implemented here is not the most efficient possible. - The implementation algorithm is the following. - - Assume that we are applying - a two qubit gate of shape (4, 4) to qubits 0 and 3 of a five qubit state - vector of shape 5 * (2,). We perform the following steps: - - * Reshape the state to (2, 4, 2, 2) - * Transpose to (2, 2, 4, 2) to bring the target qubits in the beginning. - * Reshape to (4, 8). - * Apply the gate using the matmul (4, 4) x (4, 8). - * Reshape to the original shape 5 * (2,) and traspose so that the final - qubit order agrees with the initial. - """ - - description = "Uses `tf.matmul` as well as transpositions and reshapes " \ - "to apply gates to states via matrix multiplication." - - def __init__(self): - from qibo.backends import einsum - super().__init__() - self.name = "tensorflow_matmuleinsum" - self.custom_gates = False - - def create_einsum_cache(self, qubits, nqubits, ncontrol=None): - return numpy.NumpyMatmulEinsumBackend.create_einsum_cache( - self, qubits, nqubits, ncontrol) - - def einsum_call(self, cache, state, matrix): - return numpy.NumpyMatmulEinsumBackend.einsum_call( - self, cache, state, matrix) diff --git a/src/qibo/tests/conftest.py b/src/qibo/tests/conftest.py index 28109f2a2f..2b450c0888 100644 --- a/src/qibo/tests/conftest.py +++ b/src/qibo/tests/conftest.py @@ -34,7 +34,7 @@ def pytest_generate_tests(metafunc): metafunc.parametrize("accelerators", accelerators) if "backend" in metafunc.fixturenames: - backends = ["custom", "defaulteinsum", "matmuleinsum"] + backends = ["custom", "defaulteinsum"] if "custom" not in K.available_backends: # pragma: no cover backends.remove("custom") metafunc.parametrize("backend", backends) diff --git a/src/qibo/tests_new/conftest.py b/src/qibo/tests_new/conftest.py index 701e49348e..8452b5e40b 100644 --- a/src/qibo/tests_new/conftest.py +++ b/src/qibo/tests_new/conftest.py @@ -10,7 +10,6 @@ _available_backends = set(K.available_backends.keys()) _available_backends.remove("numpy") _available_backends.remove("defaulteinsum") -_available_backends.remove("matmuleinsum") _ENGINES = "numpy" _ACCELERATORS = None if "tensorflow" in _available_backends: diff --git a/src/qibo/tests_new/test_backends_init.py b/src/qibo/tests_new/test_backends_init.py index 913c2b4a37..36e36d4b6b 100644 --- a/src/qibo/tests_new/test_backends_init.py +++ b/src/qibo/tests_new/test_backends_init.py @@ -18,8 +18,6 @@ def test_set_backend(backend): backends.set_backend(backend) if backend == "defaulteinsum": target_name = "tensorflow_defaulteinsum" - elif backend == "matmuleinsum": - target_name = "tensorflow_matmuleinsum" else: target_name = backend assert K.name == target_name @@ -45,7 +43,7 @@ def test_set_backend_errors(): backends.set_backend("numpy_badgates") h = gates.H(0) with pytest.warns(RuntimeWarning): - backends.set_backend("numpy_matmuleinsum") + backends.set_backend("numpy") backends.set_backend(original_backend) diff --git a/src/qibo/tests_new/test_core_circuit.py b/src/qibo/tests_new/test_core_circuit.py index 630f1b5c53..a703a0753e 100644 --- a/src/qibo/tests_new/test_core_circuit.py +++ b/src/qibo/tests_new/test_core_circuit.py @@ -90,7 +90,7 @@ def create_circuit(theta = 0.1234): def test_compiling_twice_exception(): """Check that compiling a circuit a second time raises error.""" original_backend = qibo.get_backend() - qibo.set_backend("matmuleinsum") + qibo.set_backend("defaulteinsum") c = Circuit(2) c.add([gates.H(0), gates.H(1)]) c.compile() From 44a2d710cd797509019e0d3a6354418ad52ab45c Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Thu, 29 Apr 2021 16:40:30 +0400 Subject: [PATCH 02/19] Rename defaulteinsum to numpy and tensorflow --- src/qibo/backends/__init__.py | 19 ++++++--------- src/qibo/backends/numpy.py | 32 ++++++------------------- src/qibo/backends/tensorflow.py | 25 +++---------------- src/qibo/tests/conftest.py | 6 ++--- src/qibo/tests_new/conftest.py | 8 ++----- src/qibo/tests_new/test_core_circuit.py | 2 +- 6 files changed, 23 insertions(+), 69 deletions(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index c36fcbf476..c8172ff846 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -2,8 +2,8 @@ from pkgutil import iter_modules from qibo import config from qibo.config import raise_error, log, warnings -from qibo.backends.numpy import NumpyDefaultEinsumBackend -from qibo.backends.tensorflow import TensorflowCustomBackend, TensorflowDefaultEinsumBackend +from qibo.backends.numpy import NumpyBackend +from qibo.backends.tensorflow import TensorflowCustomBackend, TensorflowBackend def _check_availability(module_name): @@ -19,8 +19,7 @@ def __init__(self): # check if numpy is installed if _check_availability("numpy"): - self.available_backends["numpy"] = NumpyDefaultEinsumBackend - self.available_backends["numpy_defaulteinsum"] = NumpyDefaultEinsumBackend + self.available_backends["numpy"] = NumpyBackend else: # pragma: no cover raise_error(ModuleNotFoundError, "Numpy is not installed.") @@ -28,26 +27,22 @@ def __init__(self): if _check_availability("tensorflow"): os.environ["TF_CPP_MIN_LOG_LEVEL"] = str(config.LOG_LEVEL) import tensorflow as tf - self.available_backends["defaulteinsum"] = TensorflowDefaultEinsumBackend - self.available_backends["tensorflow_defaulteinsum"] = TensorflowDefaultEinsumBackend + self.available_backends["tensorflow"] = TensorflowBackend if _check_availability("qibo_sim_tensorflow"): self.available_backends["custom"] = TensorflowCustomBackend - self.available_backends["tensorflow"] = TensorflowCustomBackend else: # pragma: no cover log.warning("Einsum will be used to apply gates with Tensorflow. " "Removing custom operators from available backends.") - self.available_backends["tensorflow"] = TensorflowDefaultEinsumBackend active_backend = "tensorflow" else: # pragma: no cover # case not tested because CI has tf installed log.warning("Tensorflow is not installed. Falling back to numpy. " "Numpy does not support Qibo custom operators and GPU. " "Einsum will be used to apply gates on CPU.") - self.available_backends["defaulteinsum"] = NumpyDefaultEinsumBackend self.constructed_backends = {} self._active_backend = None - self.qnp = self.construct_backend("numpy_defaulteinsum") + self.qnp = self.construct_backend("numpy") # Create the default active backend if "QIBO_BACKEND" in os.environ: # pragma: no cover self.active_backend = os.environ.get("QIBO_BACKEND") @@ -109,8 +104,8 @@ def set_backend(backend="custom"): The following backends are available: 'custom': Tensorflow backend with custom operators for applying gates, - 'defaulteinsum': Tensorflow backend that applies gates using ``tf.einsum``, - 'numpy_defaulteinsum': Numpy backend that applies gates using ``np.einsum``. + 'tensorflow': Tensorflow backend that applies gates using ``tf.einsum``, + 'numpy': Numpy backend that applies gates using ``np.einsum``. Args: backend (str): A backend from the above options. diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index c4401875f7..8ea2f2c963 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -1,11 +1,11 @@ -from abc import abstractmethod from qibo.backends import abstract from qibo.config import raise_error, log class NumpyBackend(abstract.AbstractBackend): - description = "Base class for numpy backends" + description = "Uses `np.einsum` to apply gates to states via matrix " \ + "multiplication." def __init__(self): super().__init__() @@ -13,6 +13,7 @@ def __init__(self): self.backend = np self.name = "numpy" self.np = np + self.custom_gates = False from qibo.backends import matrices self.matrices = matrices.NumpyMatrices(self.dtypes('DTYPECPX')) @@ -239,13 +240,11 @@ def __exit__(self, *args): def set_seed(self, seed): self.backend.random.seed(seed) - @abstractmethod - def create_einsum_cache(self, qubits, nqubits, ncontrol=None): # pragma: no cover - raise_error(NotImplementedError) + def create_einsum_cache(self, qubits, nqubits, ncontrol=None): + return self.einsum_module.DefaultEinsumCache(qubits, nqubits, ncontrol) - @abstractmethod - def einsum_call(self, cache, state, matrix): # pragma: no cover - raise_error(NotImplementedError) + def einsum_call(self, cache, state, matrix): + return self.einsum(cache, state, matrix) class GateCache: pass @@ -361,20 +360,3 @@ def density_matrix_collapse(self, gate, state, result): state = substate / norm state = self._append_zeros(state, sorted_qubits, density_matrix_result) return self.reshape(state, gate.cache.flat_shape) - - -class NumpyDefaultEinsumBackend(NumpyBackend): - - description = "Uses `np.einsum` to apply gates to states via matrix " \ - "multiplication." - - def __init__(self): - super().__init__() - self.name = "numpy_defaulteinsum" - self.custom_gates = False - - def create_einsum_cache(self, qubits, nqubits, ncontrol=None): - return self.einsum_module.DefaultEinsumCache(qubits, nqubits, ncontrol) - - def einsum_call(self, cache, state, matrix): - return self.einsum(cache, state, matrix) diff --git a/src/qibo/backends/tensorflow.py b/src/qibo/backends/tensorflow.py index 6346b7261e..408d22c594 100644 --- a/src/qibo/backends/tensorflow.py +++ b/src/qibo/backends/tensorflow.py @@ -14,13 +14,15 @@ def __init__(self): class TensorflowBackend(numpy.NumpyBackend): - description = "Base class for Tensorflow backends." + description = "Uses `tf.einsum` to apply gates to states via matrix " \ + "multiplication." def __init__(self): super().__init__() import tensorflow as tf self.backend = tf self.name = "tensorflow" + self.custom_gates = False self.cpu_devices = tf.config.list_logical_devices("CPU") self.gpu_devices = tf.config.list_logical_devices("GPU") @@ -288,24 +290,3 @@ def density_matrix_collapse(self, gate, state, result): state = gate.gate_op(state, gate.cache.qubits_tensor, result, 2 * gate.nqubits, False, self.get_threads()) return state / self.trace(state) - - -class TensorflowDefaultEinsumBackend(TensorflowBackend): - """Gate application backend that based on ``tf.einsum``.""" - - description = "Uses `tf.einsum` to apply gates to states via matrix " \ - "multiplication." - - def __init__(self): - super().__init__() - from qibo.backends import einsum - self.name = "tensorflow_defaulteinsum" - self.custom_gates = False - - def create_einsum_cache(self, qubits, nqubits, ncontrol=None): - return numpy.NumpyDefaultEinsumBackend.create_einsum_cache( - self, qubits, nqubits, ncontrol) - - def einsum_call(self, cache, state, matrix): - return numpy.NumpyDefaultEinsumBackend.einsum_call( - self, cache, state, matrix) diff --git a/src/qibo/tests/conftest.py b/src/qibo/tests/conftest.py index 2b450c0888..c0564ff546 100644 --- a/src/qibo/tests/conftest.py +++ b/src/qibo/tests/conftest.py @@ -34,9 +34,9 @@ def pytest_generate_tests(metafunc): metafunc.parametrize("accelerators", accelerators) if "backend" in metafunc.fixturenames: - backends = ["custom", "defaulteinsum"] - if "custom" not in K.available_backends: # pragma: no cover - backends.remove("custom") + backends = ["tensorflow"] + if "custom" in K.available_backends: + backends.append("custom") metafunc.parametrize("backend", backends) # skip distributed tests if "custom" backend is not available diff --git a/src/qibo/tests_new/conftest.py b/src/qibo/tests_new/conftest.py index 8452b5e40b..ef3b9b5bdf 100644 --- a/src/qibo/tests_new/conftest.py +++ b/src/qibo/tests_new/conftest.py @@ -8,13 +8,10 @@ from qibo import K _available_backends = set(K.available_backends.keys()) -_available_backends.remove("numpy") -_available_backends.remove("defaulteinsum") _ENGINES = "numpy" _ACCELERATORS = None if "tensorflow" in _available_backends: _ENGINES = "numpy,tensorflow" - _available_backends.remove("tensorflow") if "custom" in _available_backends: _ACCELERATORS = "2/GPU:0,1/GPU:0+1/GPU:1,2/GPU:0+1/GPU:1+1/GPU:2" _BACKENDS = ",".join(_available_backends) @@ -40,7 +37,7 @@ def pytest_addoption(parser): parser.addoption("--engines", type=str, default=_ENGINES, help="Backend libaries (eg. numpy, tensorflow, etc.) to test.") parser.addoption("--backends", type=str, default=_BACKENDS, - help="Calculation schemes (eg. custom, defaulteinsum, etc.) to test.") + help="Calculation schemes (eg. custom, tensorflow, numpy etc.) to test.") parser.addoption("--accelerators", type=str, default=_ACCELERATORS, help="Accelerator configurations for testing the distributed circuit.") # see `_ACCELERATORS` for the string format of the `--accelerators` flag @@ -56,8 +53,7 @@ def pytest_generate_tests(metafunc): Test functions may have one or more of the following arguments: engine: Backend library (eg. numpy, tensorflow, etc.), - backend: Calculation backend (eg. custom, defaulteinsum, - numpy_defaulteinsum, etc.), + backend: Calculation backend (eg. custom, tensorflow, numpy), accelerators: Dictionary with the accelerator configuration for distributed circuits, for example: {'/GPU:0': 1, '/GPU:1': 1}, tested_backend: The first backend when testing agreement between diff --git a/src/qibo/tests_new/test_core_circuit.py b/src/qibo/tests_new/test_core_circuit.py index a703a0753e..b52256baa3 100644 --- a/src/qibo/tests_new/test_core_circuit.py +++ b/src/qibo/tests_new/test_core_circuit.py @@ -90,7 +90,7 @@ def create_circuit(theta = 0.1234): def test_compiling_twice_exception(): """Check that compiling a circuit a second time raises error.""" original_backend = qibo.get_backend() - qibo.set_backend("defaulteinsum") + qibo.set_backend("tensorflow") c = Circuit(2) c.add([gates.H(0), gates.H(1)]) c.compile() From e3c4e49ef50b39b295c5d42d61bc54b353c60969 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sat, 1 May 2021 23:56:47 +0400 Subject: [PATCH 03/19] Fix active backend --- src/qibo/backends/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index ad8cc0a28c..a049854499 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -22,13 +22,14 @@ def __init__(self): import tensorflow as tf from qibo.backends.tensorflow import TensorflowBackend self.available_backends["tensorflow"] = TensorflowBackend + active_backend = "tensorflow" if self.check_availability("qibotf"): from qibo.backends.tensorflow import TensorflowCustomBackend self.available_backends["custom"] = TensorflowCustomBackend + active_backend = "custom" else: # pragma: no cover log.warning("Einsum will be used to apply gates with Tensorflow. " "Removing custom operators from available backends.") - active_backend = "tensorflow" else: # pragma: no cover # case not tested because CI has tf installed log.warning("Tensorflow is not installed. Falling back to numpy. " From 421dc860d0112b71e6817c33fa1fefe3a4acca3b Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sun, 2 May 2021 00:08:38 +0400 Subject: [PATCH 04/19] Simplify einsum cache --- .../backends/{einsum.py => einsum_utils.py} | 96 ++++++------------- src/qibo/backends/numpy.py | 9 +- 2 files changed, 34 insertions(+), 71 deletions(-) rename src/qibo/backends/{einsum.py => einsum_utils.py} (76%) diff --git a/src/qibo/backends/einsum.py b/src/qibo/backends/einsum_utils.py similarity index 76% rename from src/qibo/backends/einsum.py rename to src/qibo/backends/einsum_utils.py index 152baea590..3e918dd04b 100644 --- a/src/qibo/backends/einsum.py +++ b/src/qibo/backends/einsum_utils.py @@ -4,26 +4,13 @@ Gates use ``einsum`` to apply gates to state vectors. The einsum string that specifies the contraction indices is created and cached when a gate is created so that it is not recalculated every time the gate is called on a state. This -functionality is implemented in :class:`qibo.core.einsum.DefaultEinsum`. - -Due to an `issue `_ -with automatic differentiation and complex numbers in ``einsum``, we have -implemented an alternative calculation backend based on ``matmul`` in -:class:`qibo.core.einsum.MatmulEinsum`. Note that this is slower than -the default ``einsum`` on GPU but slightly faster on CPU. - -The user can switch the default einsum used by the gates by changing the -``einsum`` variable in `config.py`. It is recommended to use the default unless -automatic differentiation is required. For the latter case, we refer to our -examples. +functionality is implemented in :class:`qibo.backends.numpy.NumpyBackend`. """ -from abc import ABC, abstractmethod from qibo.config import raise_error -from typing import Dict, List, Optional, Sequence -class BaseCache: - """Base cache object for einsum backends defined in `einsum.py`. +class EinsumCache: + """Cache object required to apply gates using ``einsum``. ``circuit.calculation_cache`` is an object of this class. @@ -31,13 +18,36 @@ class BaseCache: calculations. ``self.left``, ``self.right``, ``self.left0`` and ``self.right0`` return the cache elements required for density matrix calculations. + + Args: + qubits (list): List with the qubit indices that the gate is applied to. + nqubits (int): Total number of qubits in the circuit / state vector. + ncontrol (int): Number of control qubits for `controlled_by` gates. """ + from qibo.config import EINSUM_CHARS as _chars - def __init__(self, nqubits, ncontrol: Optional[int] = None): + def __init__(self, qubits, nqubits, ncontrol=None): self.nqubits = nqubits self.ncontrol = ncontrol + if nqubits + len(qubits) > len(self._chars): # pragma: no cover + raise_error(NotImplementedError, "Not enough einsum characters.") + + input_state = list(self._chars[:nqubits]) + output_state = input_state[:] + gate_chars = list(self._chars[nqubits : nqubits + len(qubits)]) + + for i, q in enumerate(qubits): + gate_chars.append(input_state[q]) + output_state[q] = gate_chars[i] + + self.input = "".join(input_state) + self.output = "".join(output_state) + self.gate = "".join(gate_chars) + self.rest = self._chars[nqubits + len(qubits):] + # Cache for state vectors - self._vector = None + self._vector = f"{self.input},{self.gate}->{self.output}" + # Cache for density matrices self._left = None self._right = None @@ -78,53 +88,8 @@ def right0(self): def cast_shapes(self, cast_func): pass - @abstractmethod - def _calculate_density_matrix(self): # pragma: no cover - """Calculates `left` and `right` elements.""" - raise_error(NotImplementedError) - - @abstractmethod - def _calculate_density_matrix_controlled(self): # pragma: no cover - """Calculates `left0` and `right0` elements.""" - raise_error(NotImplementedError) - - -class DefaultEinsumCache(BaseCache): - """Cache object required by the :class:`qibo.core.einsum.DefaultEinsum` backend. - - The ``vector``, ``left``, ``right``, ``left0``, ``right0`` properties are - strings that hold the einsum indices. - - Args: - qubits (list): List with the qubit indices that the gate is applied to. - nqubits (int): Total number of qubits in the circuit / state vector. - ncontrol (int): Number of control qubits for `controlled_by` gates. - """ - from qibo.config import EINSUM_CHARS as _chars - - def __init__(self, qubits: Sequence[int], nqubits: int, - ncontrol: Optional[int] = None): - super(DefaultEinsumCache, self).__init__(nqubits, ncontrol) - - if nqubits + len(qubits) > len(self._chars): # pragma: no cover - raise_error(NotImplementedError, "Not enough einsum characters.") - - input_state = list(self._chars[:nqubits]) - output_state = input_state[:] - gate_chars = list(self._chars[nqubits : nqubits + len(qubits)]) - - for i, q in enumerate(qubits): - gate_chars.append(input_state[q]) - output_state[q] = gate_chars[i] - - self.input = "".join(input_state) - self.output = "".join(output_state) - self.gate = "".join(gate_chars) - self.rest = self._chars[nqubits + len(qubits):] - - self._vector = f"{self.input},{self.gate}->{self.output}" - def _calculate_density_matrix(self): + """Calculates `left` and `right` elements.""" if self.nqubits > len(self.rest): # pragma: no cover raise_error(NotImplementedError, "Not enough einsum characters.") @@ -133,6 +98,7 @@ def _calculate_density_matrix(self): self._right = f"{rest}{self.input},{self.gate}->{rest}{self.output}" def _calculate_density_matrix_controlled(self): + """Calculates `left0` and `right0` elements.""" if self.nqubits + 1 > len(self.rest): # pragma: no cover raise_error(NotImplementedError, "Not enough einsum characters.") rest, c = self.rest[:self.nqubits], self.rest[self.nqubits] @@ -205,7 +171,7 @@ def calculate_dm(self): self._reverse_dm = self.revert(self._order_dm) @staticmethod - def revert(transpose_order) -> List[int]: + def revert(transpose_order): reverse_order = len(transpose_order) * [0] for i, r in enumerate(transpose_order): reverse_order[r] = i diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index 3d1fa31b5c..14a9df648f 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -1,4 +1,4 @@ -from qibo.backends import abstract +from qibo.backends import abstract, einsum_utils from qibo.config import raise_error, log @@ -26,9 +26,6 @@ def __init__(self): self.oom_error = MemoryError self.optimization = None - from qibo.backends import einsum - self.einsum_module = einsum - def set_device(self, name): log.warning("Numpy does not support device placement. " "Aborting device change.") @@ -245,7 +242,7 @@ def set_seed(self, seed): self.backend.random.seed(seed) def create_einsum_cache(self, qubits, nqubits, ncontrol=None): - return self.einsum_module.DefaultEinsumCache(qubits, nqubits, ncontrol) + return einsum_utils.EinsumCache(qubits, nqubits, ncontrol) def einsum_call(self, cache, state, matrix): return self.einsum(cache, state, matrix) @@ -259,7 +256,7 @@ def create_gate_cache(self, gate): cache.tensor_shape = self.cast(s * gate.nqubits * (2,), dtype='DTYPEINT') cache.flat_shape = self.cast(s * (2 ** gate.nqubits,), dtype='DTYPEINT') if gate.is_controlled_by: - cache.control_cache = self.einsum_module.ControlCache(gate) + cache.control_cache = einsum_utils.ControlCache(gate) nactive = gate.nqubits - len(gate.control_qubits) targets = cache.control_cache.targets ncontrol = len(gate.control_qubits) From f0ca8c14be9fd33e68c201600e50b58ca647b8a8 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sun, 2 May 2021 00:11:06 +0400 Subject: [PATCH 05/19] Remove cast_shapes --- src/qibo/backends/einsum_utils.py | 3 --- src/qibo/backends/numpy.py | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/qibo/backends/einsum_utils.py b/src/qibo/backends/einsum_utils.py index 3e918dd04b..9d9c1709fd 100644 --- a/src/qibo/backends/einsum_utils.py +++ b/src/qibo/backends/einsum_utils.py @@ -85,9 +85,6 @@ def right0(self): self._calculate_density_matrix_controlled() return self._right0 - def cast_shapes(self, cast_func): - pass - def _calculate_density_matrix(self): """Calculates `left` and `right` elements.""" if self.nqubits > len(self.rest): # pragma: no cover diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index 14a9df648f..c1de62ae2f 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -264,8 +264,6 @@ def create_gate_cache(self, gate): targets, nactive, ncontrol) else: cache.calculation_cache = self.create_einsum_cache(gate.qubits, gate.nqubits) - cache.calculation_cache.cast_shapes( - lambda x: self.cast(x, dtype='DTYPEINT')) return cache def state_vector_call(self, gate, state): From 47d062f017dad270d0ae8b2ff78eb06984709316 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sun, 2 May 2021 00:14:27 +0400 Subject: [PATCH 06/19] Remove engines from tests --- src/qibo/tests_new/conftest.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/qibo/tests_new/conftest.py b/src/qibo/tests_new/conftest.py index 667ba0ae96..bfcb1a6096 100644 --- a/src/qibo/tests_new/conftest.py +++ b/src/qibo/tests_new/conftest.py @@ -8,10 +8,8 @@ from qibo import K _available_backends = set(K.available_backends.keys()) -_ENGINES = "numpy" _ACCELERATORS = None if "tensorflow" in _available_backends: - _ENGINES = "numpy,tensorflow" if "custom" in _available_backends: _ACCELERATORS = "2/GPU:0,1/GPU:0+1/GPU:1,2/GPU:0+1/GPU:1+1/GPU:2" _BACKENDS = ",".join(_available_backends) @@ -34,8 +32,6 @@ def pytest_configure(config): def pytest_addoption(parser): - parser.addoption("--engines", type=str, default=_ENGINES, - help="Backend libaries (eg. numpy, tensorflow, etc.) to test.") parser.addoption("--backends", type=str, default=_BACKENDS, help="Calculation schemes (eg. custom, tensorflow, numpy etc.) to test.") parser.addoption("--accelerators", type=str, default=_ACCELERATORS, @@ -44,7 +40,7 @@ def pytest_addoption(parser): parser.addoption("--target-backend", type=str, default="numpy", help="Base backend that other backends are tested against.") # `test_backends_agreement.py` tests that backend methods agree between - # different backends by testing each backend in `--engines` with the + # different backends by testing each backend in `--backends` with the # `--target-backend` @@ -64,7 +60,6 @@ def pytest_generate_tests(metafunc): This function parametrizes the above arguments using the values given by the user when calling `pytest`. """ - engines = metafunc.config.option.engines.split(",") backends = metafunc.config.option.backends.split(",") accelerators = metafunc.config.option.accelerators @@ -80,7 +75,7 @@ def pytest_generate_tests(metafunc): # for `test_backends_agreement.py` if "tested_backend" in metafunc.fixturenames: target = metafunc.config.option.target_backend - metafunc.parametrize("tested_backend", [x for x in engines if x != target]) + metafunc.parametrize("tested_backend", [x for x in backends if x != target]) metafunc.parametrize("target_backend", [target]) # for `test_core_*.py` From 9e88ad839f4116af3f3ad4a42096109ab827d3ee Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sun, 2 May 2021 00:54:50 +0400 Subject: [PATCH 07/19] Fix pylint --- src/qibo/backends/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index a049854499..5896a3fb26 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -19,7 +19,7 @@ def __init__(self): # check if tensorflow is installed and use it as default backend. if self.check_availability("tensorflow"): os.environ["TF_CPP_MIN_LOG_LEVEL"] = str(config.LOG_LEVEL) - import tensorflow as tf + import tensorflow as tf # pylint: disable=E0401 from qibo.backends.tensorflow import TensorflowBackend self.available_backends["tensorflow"] = TensorflowBackend active_backend = "tensorflow" From df07ac4fd1e3891969d01b5cdbd213cf0e29ed87 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 3 May 2021 14:01:58 +0400 Subject: [PATCH 08/19] Update tests for numpy backend --- src/qibo/tests/conftest.py | 5 +---- src/qibo/tests_new/test_backends_init.py | 7 ++++--- src/qibo/tests_new/test_core_circuit.py | 3 +++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/qibo/tests/conftest.py b/src/qibo/tests/conftest.py index c0564ff546..698982893e 100644 --- a/src/qibo/tests/conftest.py +++ b/src/qibo/tests/conftest.py @@ -34,10 +34,7 @@ def pytest_generate_tests(metafunc): metafunc.parametrize("accelerators", accelerators) if "backend" in metafunc.fixturenames: - backends = ["tensorflow"] - if "custom" in K.available_backends: - backends.append("custom") - metafunc.parametrize("backend", backends) + metafunc.parametrize("backend", K.available_backends) # skip distributed tests if "custom" backend is not available module_name = "qibo.tests.test_distributed" diff --git a/src/qibo/tests_new/test_backends_init.py b/src/qibo/tests_new/test_backends_init.py index 32c3f31386..fc15d28697 100644 --- a/src/qibo/tests_new/test_backends_init.py +++ b/src/qibo/tests_new/test_backends_init.py @@ -34,9 +34,10 @@ def test_set_backend_errors(): backends.set_backend("numpy_custom") with pytest.raises(ValueError): backends.set_backend("numpy_badgates") - h = gates.H(0) - with pytest.warns(RuntimeWarning): - backends.set_backend("numpy") + if original_backend != "numpy": + h = gates.H(0) + with pytest.warns(RuntimeWarning): + backends.set_backend("numpy") backends.set_backend(original_backend) diff --git a/src/qibo/tests_new/test_core_circuit.py b/src/qibo/tests_new/test_core_circuit.py index b52256baa3..11c92371b8 100644 --- a/src/qibo/tests_new/test_core_circuit.py +++ b/src/qibo/tests_new/test_core_circuit.py @@ -89,7 +89,10 @@ def create_circuit(theta = 0.1234): def test_compiling_twice_exception(): """Check that compiling a circuit a second time raises error.""" + from qibo import K original_backend = qibo.get_backend() + if "tensorflow" not in K.available_backends: + pytest.skip("Skipping compilation test because Tensorflow is not available.") qibo.set_backend("tensorflow") c = Circuit(2) c.add([gates.H(0), gates.H(1)]) From 20ddd8b367aaf18cb916d788584df12fa8f05d5b Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 3 May 2021 14:52:35 +0400 Subject: [PATCH 09/19] Rename custom backend to qibotf --- src/qibo/backends/__init__.py | 8 +++---- src/qibo/backends/abstract.py | 1 + src/qibo/backends/numpy.py | 1 - src/qibo/backends/tensorflow.py | 5 +---- src/qibo/core/circuit.py | 2 +- src/qibo/core/distcircuit.py | 4 ++-- src/qibo/core/fusion.py | 4 ++-- src/qibo/core/gates.py | 22 +++++++++---------- src/qibo/tests_new/test_backends_init.py | 3 +-- src/qibo/tests_new/test_cirq.py | 10 ++++----- src/qibo/tests_new/test_core_callbacks.py | 2 +- src/qibo/tests_new/test_core_circuit.py | 3 ++- .../test_core_circuit_backpropagation.py | 4 ++-- .../tests_new/test_core_circuit_features.py | 2 +- .../test_core_gates_density_matrix.py | 3 ++- 15 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index af8ce21724..f8c4c4b4cd 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -25,8 +25,8 @@ def __init__(self): active_backend = "tensorflow" if self.check_availability("qibotf"): from qibo.backends.tensorflow import TensorflowCustomBackend - self.available_backends["custom"] = TensorflowCustomBackend - active_backend = "custom" + self.available_backends["qibotf"] = TensorflowCustomBackend + active_backend = "qibotf" else: # pragma: no cover log.warning("Einsum will be used to apply gates with Tensorflow. " "Removing custom operators from available backends.") @@ -108,11 +108,11 @@ def check_availability(module_name): numpy_matrices = K.qnp.matrices -def set_backend(backend="custom"): +def set_backend(backend="qibotf"): """Sets backend used for mathematical operations and applying gates. The following backends are available: - 'custom': Tensorflow backend with custom operators for applying gates, + 'qibotf': Tensorflow backend with custom operators for applying gates, 'tensorflow': Tensorflow backend that applies gates using ``tf.einsum``, 'numpy': Numpy backend that applies gates using ``np.einsum``. diff --git a/src/qibo/backends/abstract.py b/src/qibo/backends/abstract.py index e064589e16..352472a1c0 100644 --- a/src/qibo/backends/abstract.py +++ b/src/qibo/backends/abstract.py @@ -16,6 +16,7 @@ def __init__(self): self.gpu_devices = [] self.default_device = [] + self.op = None self._matrices = None self.numeric_types = None self.tensor_types = None diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index c1de62ae2f..cdaf945af1 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -13,7 +13,6 @@ def __init__(self): self.backend = np self.name = "numpy" self.np = np - self.custom_gates = False self.numeric_types = (np.int, np.float, np.complex, np.int32, np.int64, np.float32, np.float64, diff --git a/src/qibo/backends/tensorflow.py b/src/qibo/backends/tensorflow.py index f9e468430b..f5892d0abd 100644 --- a/src/qibo/backends/tensorflow.py +++ b/src/qibo/backends/tensorflow.py @@ -22,7 +22,6 @@ def __init__(self): import tensorflow as tf # pylint: disable=E0401 self.backend = tf self.name = "tensorflow" - self.custom_gates = False self.cpu_devices = tf.config.list_logical_devices("CPU") self.gpu_devices = tf.config.list_logical_devices("GPU") @@ -210,9 +209,7 @@ def __init__(self): "compiled.") from qibotf import custom_operators as op # pylint: disable=E0401 super().__init__() - self.name = "custom" - self.custom_gates = True - self.custom_einsum = None + self.name = "qibotf" self.op = op from qibo.config import get_threads self.get_threads = get_threads diff --git a/src/qibo/core/circuit.py b/src/qibo/core/circuit.py index 2c0d1dab64..805fe4cd08 100644 --- a/src/qibo/core/circuit.py +++ b/src/qibo/core/circuit.py @@ -128,7 +128,7 @@ def compile(self): raise_error(RuntimeError, "Circuit is already compiled.") if not self.queue: raise_error(RuntimeError, "Cannot compile circuit without gates.") - if K.custom_gates: + if K.op is not None: raise_error(RuntimeError, "Cannot compile circuit that uses custom " "operators.") for gate in self.queue: diff --git a/src/qibo/core/distcircuit.py b/src/qibo/core/distcircuit.py index 38c16422cc..de3fd203c3 100644 --- a/src/qibo/core/distcircuit.py +++ b/src/qibo/core/distcircuit.py @@ -98,9 +98,9 @@ def _add(self, gate: gates.Gate): Also checks that there are sufficient qubits to use as global. """ - if K.name != "custom": + if K.op is None: raise_error(NotImplementedError, "Distributed circuit does not " - "support native tensorflow gates.") + "support non-custom gates.") if isinstance(gate, gates.KrausChannel): raise_error(NotImplementedError, "Distributed circuits do not " "support channels.") diff --git a/src/qibo/core/fusion.py b/src/qibo/core/fusion.py index 0ac2dfe750..f5d3b5aae4 100644 --- a/src/qibo/core/fusion.py +++ b/src/qibo/core/fusion.py @@ -48,7 +48,7 @@ def __init__(self): self.completed = False self.special_gate = None self._fused_gates = None - if K.custom_gates: + if K.op is not None: self.K = K.np else: self.K = K @@ -258,7 +258,7 @@ def _one_qubit_matrix(self, gate0: "Gate", gate1: "Gate"): 4x4 matrix that corresponds to the Kronecker product of the 2x2 gate matrices. """ - if K.custom_gates: + if K.op is not None: return self.K.kron(gate0.unitary, gate1.unitary) else: matrix = self.K.tensordot(gate0.unitary, gate1.unitary, axes=0) diff --git a/src/qibo/core/gates.py b/src/qibo/core/gates.py index 4ddaa19738..9cd49e784a 100644 --- a/src/qibo/core/gates.py +++ b/src/qibo/core/gates.py @@ -13,7 +13,7 @@ class BackendGate(BaseBackendGate): module = sys.modules[__name__] def __init__(self): - if K.name == "custom": + if K.op is not None: if not K.executing_eagerly(): raise_error(NotImplementedError, "Custom operator gates should not be used in " @@ -48,7 +48,7 @@ def matrix(self): def calculate_matrix(self): matrix = self.construct_unitary() - if K.name != "custom": + if K.op is None: rank = int(math.log2(int(matrix.shape[0]))) matrix = K.reshape(matrix, 2 * rank * (2,)) return matrix @@ -182,7 +182,7 @@ def cache(self): cache.reduced_target_qubits = list( reduced_target_qubits[i] for i in self.target_qubits) - if K.name != "custom": + if K.op is None: sorted_qubits = sorted(self.target_qubits) cache.order = list(sorted_qubits) s = 1 + self.density_matrix @@ -263,14 +263,14 @@ def calculate_probs(): return result def state_vector_call(self, state): - if K.name == "custom": + if K.op is not None: result = self.result_tensor() else: result = self.result.binary[-1] return K.state_vector_collapse(self, state, result) def density_matrix_call(self, state): - if K.name == "custom": + if K.op is not None: result = self.result_tensor() else: result = self.result_list() @@ -346,7 +346,7 @@ def __init__(self, q, theta, trainable=True): self.gate_op = K.op.apply_z_pow def calculate_matrix(self): - if K.name == "custom": + if K.op is not None: return K.cast(K.qnp.exp(1j * self.parameters)) else: return MatrixGate.calculate_matrix(self) @@ -437,7 +437,7 @@ def __init__(self, q0, q1, **params): def calculate_matrix(self): matrix = self.base.construct_unitary(self) - if K.name != "custom": + if K.op is None: matrix = K.reshape(self.control_unitary(matrix), 4 * (2,)) return matrix @@ -520,7 +520,7 @@ def __init__(self, q0, q1, theta, phi, trainable=True): self.gate_op = K.op.apply_fsim def calculate_matrix(self): - if K.name == "custom": + if K.op is not None: theta, phi = self.parameters cos, isin = K.qnp.cos(theta) + 0j, -1j * K.qnp.sin(theta) phase = K.qnp.exp(-1j * phi) @@ -550,7 +550,7 @@ def __init__(self, q0, q1, unitary, phi, trainable=True): self.gate_op = K.op.apply_fsim def calculate_matrix(self): - if K.name == "custom": + if K.op is not None: unitary, phi = self.parameters matrix = K.qnp.zeros(5) matrix[:4] = K.qnp.reshape(unitary, (4,)) @@ -1037,7 +1037,7 @@ def cache(self): cache.qubits_tensor = qubits + [q + self.nqubits for q in qubits] cache.target_qubits_dm = self.qubits + tuple(q + self.nqubits for q in self.qubits) - if K.name != "custom": + if K.op is None: cache.calculation_cache = K.create_einsum_cache( cache.target_qubits_dm, 2 * self.nqubits) @@ -1057,7 +1057,7 @@ def state_vector_call(self, state): "state vectors when T1 < T2.") def density_matrix_call(self, state): - if K.name == "custom": + if K.op is not None: return self.gate_op(state, self.matrix, self.cache.qubits_tensor, 2 * self.nqubits, *self.cache.target_qubits_dm, K.get_threads()) diff --git a/src/qibo/tests_new/test_backends_init.py b/src/qibo/tests_new/test_backends_init.py index fc15d28697..96ecac5424 100644 --- a/src/qibo/tests_new/test_backends_init.py +++ b/src/qibo/tests_new/test_backends_init.py @@ -18,8 +18,7 @@ def test_set_backend(backend): assert repr(K) == backend assert K.executing_eagerly() h = gates.H(0) - if backend == "custom": - assert K.custom_einsum is None + if backend == "qibotf": assert h.gate_op else: assert h.gate_op is None diff --git a/src/qibo/tests_new/test_cirq.py b/src/qibo/tests_new/test_cirq.py index 22073059c5..8790517602 100644 --- a/src/qibo/tests_new/test_cirq.py +++ b/src/qibo/tests_new/test_cirq.py @@ -3,7 +3,7 @@ import cirq import pytest import qibo -from qibo import models, gates +from qibo import models, gates, K def random_state(nqubits): @@ -56,13 +56,11 @@ def assert_gates_equivalent(qibo_gate, cirq_gates, nqubits, initial_state = random_state(nqubits) target_state, target_depth = execute_cirq(cirq_gates, nqubits, np.copy(initial_state)) - backend = qibo.get_backend() - if ndevices is None or "numpy" in backend: - accelerators = None - else: + accelerators = None + if ndevices is not None: accelerators = {"/GPU:0": ndevices} - if backend != "custom" and accelerators: + if K.op is None and accelerators: with pytest.raises(NotImplementedError): c = models.Circuit(nqubits, accelerators) c.add(qibo_gate) diff --git a/src/qibo/tests_new/test_core_callbacks.py b/src/qibo/tests_new/test_core_callbacks.py index 1355c7342d..8ed18b4cd6 100644 --- a/src/qibo/tests_new/test_core_callbacks.py +++ b/src/qibo/tests_new/test_core_callbacks.py @@ -185,7 +185,7 @@ def test_entropy_in_compiled_circuit(backend): c.add(gates.CallbackGate(entropy)) c.add(gates.CNOT(0, 1)) c.add(gates.CallbackGate(entropy)) - if backend == "custom": + if backend == "qibotf": with pytest.raises(RuntimeError): c.compile() else: diff --git a/src/qibo/tests_new/test_core_circuit.py b/src/qibo/tests_new/test_core_circuit.py index 11c92371b8..03cd740e86 100644 --- a/src/qibo/tests_new/test_core_circuit.py +++ b/src/qibo/tests_new/test_core_circuit.py @@ -75,8 +75,9 @@ def create_circuit(theta = 0.1234): r1 = c1.execute() # Run compiled circuit + from qibo import K c2 = create_circuit() - if backend == "custom": + if K.op is not None: with pytest.raises(RuntimeError): c2.compile() else: diff --git a/src/qibo/tests_new/test_core_circuit_backpropagation.py b/src/qibo/tests_new/test_core_circuit_backpropagation.py index 740e932bbc..9b6416a86e 100644 --- a/src/qibo/tests_new/test_core_circuit_backpropagation.py +++ b/src/qibo/tests_new/test_core_circuit_backpropagation.py @@ -7,7 +7,7 @@ def test_variable_backpropagation(backend): - if backend == "custom": + if backend == "qibotf": pytest.skip("Custom gates do not support automatic differentiation.") original_backend = qibo.get_backend() qibo.set_backend(backend) @@ -34,7 +34,7 @@ def test_variable_backpropagation(backend): def test_two_variables_backpropagation(backend): - if backend == "custom": + if backend == "qibotf": pytest.skip("Custom gates do not support automatic differentiation.") original_backend = qibo.get_backend() qibo.set_backend(backend) diff --git a/src/qibo/tests_new/test_core_circuit_features.py b/src/qibo/tests_new/test_core_circuit_features.py index efb4943196..9ecb5f50f3 100644 --- a/src/qibo/tests_new/test_core_circuit_features.py +++ b/src/qibo/tests_new/test_core_circuit_features.py @@ -32,7 +32,7 @@ def custom_circuit(initial_state, theta): else: c = custom_circuit - if backend == "custom" and compile: + if backend == "qibotf" and compile: with pytest.raises(NotImplementedError): result = c(initial_state, theta) else: diff --git a/src/qibo/tests_new/test_core_gates_density_matrix.py b/src/qibo/tests_new/test_core_gates_density_matrix.py index 2924928764..c701490944 100644 --- a/src/qibo/tests_new/test_core_gates_density_matrix.py +++ b/src/qibo/tests_new/test_core_gates_density_matrix.py @@ -150,7 +150,8 @@ def test_unitary_gate(backend, nqubits): shape = 2 * (2 ** nqubits,) matrix = np.random.random(shape) + 1j * np.random.random(shape) initial_rho = random_density_matrix(nqubits) - if backend == "custom" and nqubits > 2: + from qibo import K + if K.op is not None and nqubits > 2: with pytest.raises(NotImplementedError): gate = gates.Unitary(matrix, *range(nqubits)) else: From b8b04fd4e80a6e255288e61303d7b0e5417a8885 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 3 May 2021 14:54:20 +0400 Subject: [PATCH 10/19] Fix old test --- src/qibo/tests/test_hamiltonians.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/tests/test_hamiltonians.py b/src/qibo/tests/test_hamiltonians.py index ebd7a71ba8..0135c256b9 100644 --- a/src/qibo/tests/test_hamiltonians.py +++ b/src/qibo/tests/test_hamiltonians.py @@ -398,7 +398,7 @@ def test_trotter_hamiltonian_three_qubit_term(backend): dt = 1e-2 initial_state = utils.random_numpy_state(4) - if backend == "custom": + if K.op is not None: with pytest.raises(NotImplementedError): circuit = trotter_h.circuit(dt=dt) else: From 1b3848789822661d7f039cf1754e2bd11df0e5f0 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 3 May 2021 15:00:48 +0400 Subject: [PATCH 11/19] Fix coverage --- src/qibo/models/variational.py | 7 +++---- src/qibo/tests_new/test_core_circuit.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/qibo/models/variational.py b/src/qibo/models/variational.py index 4684949f2c..8504cb261b 100644 --- a/src/qibo/models/variational.py +++ b/src/qibo/models/variational.py @@ -66,13 +66,14 @@ def minimize(self, initial_state, method='Powell', jac=None, hess=None, ``CMAEvolutionStrategy.result``, and for ``'sgd'`` the options used during the optimization. """ + from qibo import K def _loss(params, circuit, hamiltonian): circuit.set_parameters(params) final_state = circuit() return hamiltonian.expectation(final_state) if compile: - if get_backend() == "custom": + if K.op is not None: raise_error(RuntimeError, "Cannot compile VQE that uses custom operators. " "Set the compile flag to False.") from qibo import K @@ -82,14 +83,12 @@ def _loss(params, circuit, hamiltonian): if method == 'sgd': # check if gates are using the MatmulEinsum backend - from qibo import K - if K.name == "custom": + if K.op is not None: raise_error(RuntimeError, 'SGD VQE requires native Tensorflow ' 'gates because gradients are not ' 'supported in the custom kernels.') loss = _loss else: - from qibo import K loss = lambda p, c, h: K.qnp.dtypes("DTYPE")(_loss(p, c, h)) result, parameters, extra = self.optimizers.optimize(loss, initial_state, args=(self.circuit, self.hamiltonian), diff --git a/src/qibo/tests_new/test_core_circuit.py b/src/qibo/tests_new/test_core_circuit.py index 03cd740e86..d9b4aee42b 100644 --- a/src/qibo/tests_new/test_core_circuit.py +++ b/src/qibo/tests_new/test_core_circuit.py @@ -92,7 +92,7 @@ def test_compiling_twice_exception(): """Check that compiling a circuit a second time raises error.""" from qibo import K original_backend = qibo.get_backend() - if "tensorflow" not in K.available_backends: + if "tensorflow" not in K.available_backends: # pragma: no cover pytest.skip("Skipping compilation test because Tensorflow is not available.") qibo.set_backend("tensorflow") c = Circuit(2) From 89957fdba2ce418f0b2941c50bc9c8f87017448c Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 3 May 2021 15:28:27 +0400 Subject: [PATCH 12/19] Fix example tests --- examples/test_examples.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/test_examples.py b/examples/test_examples.py index 6477c52431..859f6c869f 100644 --- a/examples/test_examples.py +++ b/examples/test_examples.py @@ -47,8 +47,6 @@ def run_script(args, script_name="main.py"): script_name (str): Name of the script file. max_time (float): Time-out time in seconds. """ - import qibo - qibo.set_backend("custom") code = open(script_name, "r").read() end = code.find("\nif __name__ ==") code = code[:end] + "\n\nmain(**args)" @@ -146,7 +144,7 @@ def test_benchmarks(nqubits, type): header = ("import argparse\nimport os\nimport time" "\nimport qibo\nimport circuits\n\n") args = {"nqubits": nqubits, "type": type, - "backend": "custom", "precision": "double", + "backend": "qibotf", "precision": "double", "device": None, "accelerators": None, "nshots": None, "fuse": False, "compile": False, "nlayers": None, "gate_type": None, "params": {}, @@ -257,7 +255,7 @@ def test_falqon(nqubits, delta_t, max_layers): sys.path[-1] = path os.chdir(path) run_script(args) - + @pytest.mark.parametrize("nqubits", [5, 6, 7]) def test_grover_example1(nqubits): @@ -287,4 +285,3 @@ def test_grover_example3(nqubits, num_1): sys.path[-1] = path os.chdir(path) run_script(args, script_name="example3.py") - From e46825b47d2b371bd91437fe3ddd9687a1d92f00 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 3 May 2021 15:43:36 +0400 Subject: [PATCH 13/19] Remove custom references from tests --- src/qibo/tests/conftest.py | 2 +- src/qibo/tests/test_distributed.py | 20 +++++++++---------- src/qibo/tests/test_evolution.py | 4 ++-- src/qibo/tests/test_fusion.py | 4 ++-- src/qibo/tests_new/conftest.py | 18 +++++++---------- src/qibo/tests_new/test_backends_init.py | 4 ---- src/qibo/tests_new/test_core_gates.py | 3 ++- src/qibo/tests_new/test_measurement_gate.py | 4 +++- src/qibo/tests_new/test_models_variational.py | 4 ++-- 9 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/qibo/tests/conftest.py b/src/qibo/tests/conftest.py index 698982893e..4f13794981 100644 --- a/src/qibo/tests/conftest.py +++ b/src/qibo/tests/conftest.py @@ -27,7 +27,7 @@ def pytest_configure(config): def pytest_generate_tests(metafunc): from qibo import K if "accelerators" in metafunc.fixturenames: - if "custom" in K.available_backends: + if "qibotf" in K.available_backends: accelerators = [None, {"/GPU:0": 1, "/GPU:1": 1}] else: # pragma: no cover accelerators = [None] diff --git a/src/qibo/tests/test_distributed.py b/src/qibo/tests/test_distributed.py index 0848bc5c6b..4d8e7b88e5 100644 --- a/src/qibo/tests/test_distributed.py +++ b/src/qibo/tests/test_distributed.py @@ -34,7 +34,7 @@ def test_ndevices(): def test_transform_queue_simple(): original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") devices = {"/GPU:0": 1, "/GPU:1": 1} c = models.Circuit(4, devices) c.add((gates.H(i) for i in range(4))) @@ -55,7 +55,7 @@ def test_transform_queue_simple(): def test_transform_queue_more_gates(): original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") devices = {"/GPU:0": 2, "/GPU:1": 2} c = models.Circuit(4, devices) c.add(gates.H(0)) @@ -220,7 +220,7 @@ def test_unsupported_gates_errors(): @pytest.mark.parametrize("ndevices", [2, 4, 8]) def test_simple_execution(ndevices): original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") devices = {"/GPU:0": ndevices // 2, "/GPU:1": ndevices // 2} dist_c = models.Circuit(6, devices) @@ -240,7 +240,7 @@ def test_simple_execution(ndevices): @pytest.mark.parametrize("ndevices", [2, 4]) def test_execution_pretransformed_circuit(ndevices): original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") devices = {"/GPU:0": ndevices // 2, "/GPU:1": ndevices // 2} dist_c = models.Circuit(4, devices) dist_c.add((gates.H(i) for i in range(dist_c.nglobal, 4))) @@ -262,7 +262,7 @@ def test_execution_pretransformed_circuit(ndevices): @pytest.mark.parametrize("ndevices", [2, 4, 8]) def test_simple_execution_global(ndevices): original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") devices = {"/GPU:0": ndevices // 2, "/GPU:1": ndevices // 2} dist_c = models.Circuit(6, devices) @@ -280,7 +280,7 @@ def test_simple_execution_global(ndevices): def test_execution_with_global_swap(): original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") devices = {"/GPU:0": 2, "/GPU:1": 1, "/GPU:2": 1} dist_c = models.Circuit(6, devices) @@ -302,7 +302,7 @@ def test_execution_with_global_swap(): @pytest.mark.parametrize("ndevices", [2, 4, 8]) def test_execution_special_gate(ndevices): original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") devices = {"/GPU:0": ndevices // 2, "/GPU:1": ndevices // 2} dist_c = models.Circuit(6, devices) @@ -324,7 +324,7 @@ def test_execution_special_gate(ndevices): @pytest.mark.parametrize("ndevices", [2, 4]) def test_controlled_execution(ndevices): original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") devices = {"/GPU:0": ndevices} dist_c = models.Circuit(4, devices) dist_c.add((gates.H(i) for i in range(dist_c.nglobal, 4))) @@ -367,7 +367,7 @@ def test_controlled_execution_large(ndevices): def test_distributed_circuit_addition(): # Attempt to add circuits with different devices original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") devices = {"/GPU:0": 2, "/GPU:1": 2} c1 = models.Circuit(6, devices) c2 = models.Circuit(6, {"/GPU:0": 2}) @@ -408,7 +408,7 @@ def test_distributed_qft_global_qubits_validity(nqubits, ndevices): {"/GPU:0": 2, "/GPU:1": 5, "/GPU:2": 1}]) def test_distributed_qft_execution(nqubits, accel): original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") dist_c = models.QFT(nqubits, accelerators=accel) c = models.QFT(nqubits) diff --git a/src/qibo/tests/test_evolution.py b/src/qibo/tests/test_evolution.py index dce2fecf2b..89f612cd3c 100644 --- a/src/qibo/tests/test_evolution.py +++ b/src/qibo/tests/test_evolution.py @@ -106,8 +106,8 @@ def test_trotterized_evolution(nqubits, solver, dt, accel=None, h=1.0): def test_trotterized_evolution_distributed(): - import qibo - if qibo.get_backend() != "custom": # pragma: no cover + from qibo import K + if K.op is None: # pragma: no cover pytest.skip("Distributed circuit works only with custom backend.") test_trotterized_evolution(4, "exp", 1e-2, accel={"/GPU:0": 2}) diff --git a/src/qibo/tests/test_fusion.py b/src/qibo/tests/test_fusion.py index 56bb782a50..a5ad8a717e 100644 --- a/src/qibo/tests/test_fusion.py +++ b/src/qibo/tests/test_fusion.py @@ -124,7 +124,7 @@ def test_fusion_errors(): # Fuse distributed circuit after gates are set import qibo - if qibo.get_backend() == "custom": + if qibo.get_backend() == "qibotf": c = Circuit(4, accelerators={"/GPU:0": 2}) c.add((gates.H(i) for i in range(4))) final_state = c() @@ -157,7 +157,7 @@ def test_circuit_fuse_variational_layer(backend, nqubits, nlayers, accelerators) """Check fused variational layer execution.""" import qibo if accelerators: - backend = "custom" + backend = "qibotf" original_backend = qibo.get_backend() qibo.set_backend(backend) theta = 2 * np.pi * np.random.random((2 * nlayers * nqubits,)) diff --git a/src/qibo/tests_new/conftest.py b/src/qibo/tests_new/conftest.py index bfcb1a6096..ac4aa25e29 100644 --- a/src/qibo/tests_new/conftest.py +++ b/src/qibo/tests_new/conftest.py @@ -10,7 +10,7 @@ _available_backends = set(K.available_backends.keys()) _ACCELERATORS = None if "tensorflow" in _available_backends: - if "custom" in _available_backends: + if "qibotf" in _available_backends: _ACCELERATORS = "2/GPU:0,1/GPU:0+1/GPU:1,2/GPU:0+1/GPU:1+1/GPU:2" _BACKENDS = ",".join(_available_backends) @@ -33,7 +33,7 @@ def pytest_configure(config): def pytest_addoption(parser): parser.addoption("--backends", type=str, default=_BACKENDS, - help="Calculation schemes (eg. custom, tensorflow, numpy etc.) to test.") + help="Calculation schemes (eg. qibotf, tensorflow, numpy etc.) to test.") parser.addoption("--accelerators", type=str, default=_ACCELERATORS, help="Accelerator configurations for testing the distributed circuit.") # see `_ACCELERATORS` for the string format of the `--accelerators` flag @@ -49,7 +49,7 @@ def pytest_generate_tests(metafunc): Test functions may have one or more of the following arguments: engine: Backend library (eg. numpy, tensorflow, etc.), - backend: Calculation backend (eg. custom, tensorflow, numpy), + backend: Calculation backend (eg. qibotf, tensorflow, numpy), accelerators: Dictionary with the accelerator configuration for distributed circuits, for example: {'/GPU:0': 1, '/GPU:1': 1}, tested_backend: The first backend when testing agreement between @@ -63,13 +63,9 @@ def pytest_generate_tests(metafunc): backends = metafunc.config.option.backends.split(",") accelerators = metafunc.config.option.accelerators - if "custom" not in backends: # pragma: no cover + if "qibotf" not in backends: # pragma: no cover # skip tests that require custom operators - tests_to_skip = { - "qibo.tests_new.test_core_states_distributed" - } - # for `test_tensorflow_custom_operators.py` - if metafunc.module.__name__ in tests_to_skip: + if metafunc.module.__name__ == "qibo.tests_new.test_core_states_distributed": pytest.skip("Custom operator tests require Tensorflow engine.") # for `test_backends_agreement.py` @@ -87,10 +83,10 @@ def pytest_generate_tests(metafunc): metafunc.parametrize("accelerators", [None]) else: config = [(b, None) for b in backends] - if "custom" in backends: + if "qibotf" in backends: for x in accelerators.split(","): devdict = {dev[1:]: int(dev[0]) for dev in x.split("+")} - config.append(("custom", devdict)) + config.append(("qibotf", devdict)) metafunc.parametrize("backend,accelerators", config) else: metafunc.parametrize("backend", backends) diff --git a/src/qibo/tests_new/test_backends_init.py b/src/qibo/tests_new/test_backends_init.py index 96ecac5424..437d3ada4b 100644 --- a/src/qibo/tests_new/test_backends_init.py +++ b/src/qibo/tests_new/test_backends_init.py @@ -29,10 +29,6 @@ def test_set_backend_errors(): original_backend = backends.get_backend() with pytest.raises(ValueError): backends.set_backend("test") - with pytest.raises(ValueError): - backends.set_backend("numpy_custom") - with pytest.raises(ValueError): - backends.set_backend("numpy_badgates") if original_backend != "numpy": h = gates.H(0) with pytest.warns(RuntimeWarning): diff --git a/src/qibo/tests_new/test_core_gates.py b/src/qibo/tests_new/test_core_gates.py index 9c446fe026..3435a86775 100644 --- a/src/qibo/tests_new/test_core_gates.py +++ b/src/qibo/tests_new/test_core_gates.py @@ -378,7 +378,8 @@ def test_unitary_initialization(backend): gate = gates.Unitary(matrix, 0, 1) with pytest.raises(TypeError): gate = gates.Unitary("abc", 0, 1) - if backend == "custom": + from qibo import K + if K.op is not None: with pytest.raises(NotImplementedError): gate = gates.Unitary(matrix, 0, 1, 2) qibo.set_backend(original_backend) diff --git a/src/qibo/tests_new/test_measurement_gate.py b/src/qibo/tests_new/test_measurement_gate.py index 4cb4022bf9..3cf6e6e9cb 100644 --- a/src/qibo/tests_new/test_measurement_gate.py +++ b/src/qibo/tests_new/test_measurement_gate.py @@ -236,7 +236,8 @@ def test_circuit_copy_with_measurements(backend, accelerators): def test_measurement_compiled_circuit(backend): - if backend == "custom": + from qibo import K + if K.op is not None: # use native gates because custom gates do not support compilation pytest.skip("Custom backend does not support compilation.") original_backend = qibo.get_backend() @@ -245,6 +246,7 @@ def test_measurement_compiled_circuit(backend): c.add(gates.X(0)) c.add(gates.M(0)) c.add(gates.M(1)) + c.compile() result = c(nshots=100) target_binary_samples = np.zeros((100, 2)) target_binary_samples[:, 0] = 1 diff --git a/src/qibo/tests_new/test_models_variational.py b/src/qibo/tests_new/test_models_variational.py index ff410d5280..f83c462b39 100644 --- a/src/qibo/tests_new/test_models_variational.py +++ b/src/qibo/tests_new/test_models_variational.py @@ -141,11 +141,11 @@ def test_vqe(backend, method, options, compile, filename): def test_vqe_custom_gates_errors(): """Check that ``RuntimeError``s is raised when using custom gates.""" - if "custom" not in qibo.K.available_backends: # pragma: no cover + if "qibotf" not in qibo.K.available_backends: # pragma: no cover pytest.skip("Custom backend not available.") original_backend = qibo.get_backend() - qibo.set_backend("custom") + qibo.set_backend("qibotf") nqubits = 6 circuit = models.Circuit(nqubits) From ce186b325fe0f95aa7869f80d6a01c78ac46af02 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 3 May 2021 16:32:57 +0400 Subject: [PATCH 14/19] Dont skip distributed tests --- src/qibo/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/tests/conftest.py b/src/qibo/tests/conftest.py index 4f13794981..1378c36f8a 100644 --- a/src/qibo/tests/conftest.py +++ b/src/qibo/tests/conftest.py @@ -39,7 +39,7 @@ def pytest_generate_tests(metafunc): # skip distributed tests if "custom" backend is not available module_name = "qibo.tests.test_distributed" if metafunc.module.__name__ == module_name: - if "custom" not in K.available_backends: # pragma: no cover + if "qibotf" not in K.available_backends: # pragma: no cover pytest.skip("Distributed circuits require custom operators.") # skip parallel tests on Windows From 04a88e9c6faf3d0a70a02fb50bb4393865aceaae Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Mon, 3 May 2021 16:36:00 +0400 Subject: [PATCH 15/19] Skip test after switching backend --- src/qibo/tests_new/test_measurement_gate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibo/tests_new/test_measurement_gate.py b/src/qibo/tests_new/test_measurement_gate.py index 3cf6e6e9cb..5a5aa31569 100644 --- a/src/qibo/tests_new/test_measurement_gate.py +++ b/src/qibo/tests_new/test_measurement_gate.py @@ -236,12 +236,12 @@ def test_circuit_copy_with_measurements(backend, accelerators): def test_measurement_compiled_circuit(backend): + original_backend = qibo.get_backend() + qibo.set_backend(backend) from qibo import K if K.op is not None: # use native gates because custom gates do not support compilation pytest.skip("Custom backend does not support compilation.") - original_backend = qibo.get_backend() - qibo.set_backend(backend) c = models.Circuit(2) c.add(gates.X(0)) c.add(gates.M(0)) From 9efc1cf672fe318b50c750bd486b973363f84006 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Tue, 4 May 2021 12:26:49 +0400 Subject: [PATCH 16/19] Update SGD compatible backends error --- src/qibo/models/variational.py | 14 ++++---------- src/qibo/optimizers.py | 10 +++------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/qibo/models/variational.py b/src/qibo/models/variational.py index 8504cb261b..098ba8b200 100644 --- a/src/qibo/models/variational.py +++ b/src/qibo/models/variational.py @@ -1,4 +1,3 @@ -from qibo import get_backend from qibo.config import raise_error from qibo.core.circuit import Circuit from qibo.models.evolution import StateEvolution @@ -76,20 +75,15 @@ def _loss(params, circuit, hamiltonian): if K.op is not None: raise_error(RuntimeError, "Cannot compile VQE that uses custom operators. " "Set the compile flag to False.") - from qibo import K for gate in self.circuit.queue: _ = gate.cache loss = K.compile(_loss) - - if method == 'sgd': - # check if gates are using the MatmulEinsum backend - if K.op is not None: - raise_error(RuntimeError, 'SGD VQE requires native Tensorflow ' - 'gates because gradients are not ' - 'supported in the custom kernels.') - loss = _loss else: + loss = _loss + + if method != "sgd": loss = lambda p, c, h: K.qnp.dtypes("DTYPE")(_loss(p, c, h)) + result, parameters, extra = self.optimizers.optimize(loss, initial_state, args=(self.circuit, self.hamiltonian), method=method, jac=jac, hess=hess, hessp=hessp, diff --git a/src/qibo/optimizers.py b/src/qibo/optimizers.py index cd0ce2875b..cbd9fe5d44 100644 --- a/src/qibo/optimizers.py +++ b/src/qibo/optimizers.py @@ -164,15 +164,11 @@ def sgd(loss, initial_parameters, args=(), options=None, compile=False): - ``'nmessage'`` (int, default: ``1e3``): Every how many epochs to print a message of the loss function. """ - # check if gates are using the MatmulEinsum backend - compatible_backends = { - "tensorflow_defaulteinsum", "tensorflow_matmuleinsum"} from qibo import K - if K.name not in compatible_backends: # pragma: no cover - from qibo.config import raise_error - raise_error(RuntimeError, "SGD requires native Tensorflow backend.") + from qibo.config import log, raise_error + if K.name != "tensorflow": + raise_error(RuntimeError, "SGD optimizer requires Tensorflow backend.") - from qibo.config import log sgd_options = {"nepochs": 1000000, "nmessage": 1000, "optimizer": "Adagrad", From 5f725713d4c437b6b63e84bd5512e907872d0cf4 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Tue, 4 May 2021 12:33:32 +0400 Subject: [PATCH 17/19] Update available backend warnings --- src/qibo/backends/__init__.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index f8c4c4b4cd..6155fbd334 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -13,8 +13,10 @@ def __init__(self): if self.check_availability("numpy"): from qibo.backends.numpy import NumpyBackend self.available_backends["numpy"] = NumpyBackend - else: # pragma: no cover - raise_error(ModuleNotFoundError, "Numpy is not installed.") + else: # pragma: no cover + raise_error(ModuleNotFoundError, "Numpy is not installed. " + "Please install it using " + "`pip install numpy`.") # check if tensorflow is installed and use it as default backend. if self.check_availability("tensorflow"): @@ -28,13 +30,18 @@ def __init__(self): self.available_backends["qibotf"] = TensorflowCustomBackend active_backend = "qibotf" else: # pragma: no cover - log.warning("Einsum will be used to apply gates with Tensorflow. " - "Removing custom operators from available backends.") + log.warning("qibotf library was not found. `tf.einsum` will be " + "used to apply gates. In order to install Qibo's " + "high performance custom operators please use " + "`pip install qibotf`.") else: # pragma: no cover # case not tested because CI has tf installed - log.warning("Tensorflow is not installed. Falling back to numpy. " - "Numpy does not support Qibo custom operators and GPU. " - "Einsum will be used to apply gates on CPU.") + log.warning("Tensorflow is not installed, falling back to numpy. " + "Numpy backend uses `np.einsum` and supports CPU only. " + "To enable GPU acceleration please install Tensorflow " + "with `pip install tensorflow`. To install the " + "optimized Qibo custom operators please use " + "`pip install qibotf` after installing Tensorflow.") self.constructed_backends = {} self._active_backend = None From 86a2706ed8fabbae004c5933079dea3442931adf Mon Sep 17 00:00:00 2001 From: Stefano Carrazza Date: Tue, 4 May 2021 15:54:29 +0200 Subject: [PATCH 18/19] replacing casting wit to_numpy --- src/qibo/models/variational.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/models/variational.py b/src/qibo/models/variational.py index 098ba8b200..46b7fdc5fb 100644 --- a/src/qibo/models/variational.py +++ b/src/qibo/models/variational.py @@ -82,7 +82,7 @@ def _loss(params, circuit, hamiltonian): loss = _loss if method != "sgd": - loss = lambda p, c, h: K.qnp.dtypes("DTYPE")(_loss(p, c, h)) + loss = lambda p, c, h: K.to_numpy(_loss(p, c, h)) result, parameters, extra = self.optimizers.optimize(loss, initial_state, args=(self.circuit, self.hamiltonian), From fc9dffd9ba4ed32724877a578a9bb8b4c2136c4f Mon Sep 17 00:00:00 2001 From: Stefano Carrazza Date: Tue, 4 May 2021 15:54:40 +0200 Subject: [PATCH 19/19] fixing tests/examples backend name --- examples/reuploading_classifier/qlassifier.py | 2 +- src/qibo/tests/test_evolution.py | 2 +- src/qibo/tests_new/test_models_variational.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/reuploading_classifier/qlassifier.py b/examples/reuploading_classifier/qlassifier.py index b7227daff9..e6e7b8f477 100644 --- a/examples/reuploading_classifier/qlassifier.py +++ b/examples/reuploading_classifier/qlassifier.py @@ -119,7 +119,7 @@ def minimize(self, method='BFGS', options=None, compile=True): from qibo import K circuit = self.circuit(self.training_set[0]) for gate in circuit.queue: - if K.name not in {"tensorflow_defaulteinsum", "tensorflow_matmuleinsum"}: + if K.name != "tensorflow": from qibo.config import raise_error raise_error(RuntimeError, 'SGD VQE requires native Tensorflow ' diff --git a/src/qibo/tests/test_evolution.py b/src/qibo/tests/test_evolution.py index 89f612cd3c..46a819d9b9 100644 --- a/src/qibo/tests/test_evolution.py +++ b/src/qibo/tests/test_evolution.py @@ -326,7 +326,7 @@ def test_scheduling_optimization(method, options, messages, trotter, filename): if method == "sgd": from qibo import K - if K.name not in {"tensorflow_defaulteinsum", "tensorflow_matmuleinsum"}: + if K.name != "tensorflow": with pytest.raises(RuntimeError): best, params, _ = adevp.minimize([0.5, 1], method=method, options=options, messages=messages) diff --git a/src/qibo/tests_new/test_models_variational.py b/src/qibo/tests_new/test_models_variational.py index f83c462b39..e2700b62ec 100644 --- a/src/qibo/tests_new/test_models_variational.py +++ b/src/qibo/tests_new/test_models_variational.py @@ -94,7 +94,7 @@ def test_vqe(backend, method, options, compile, filename): """Performs a VQE circuit minimization test.""" original_backend = qibo.get_backend() original_threads = qibo.get_threads() - if (method == "sgd" or compile) and backend != "tensorflow_matmuleinsum": + if (method == "sgd" or compile) and backend != "tensorflow": pytest.skip("Skipping SGD test for unsupported backend.") qibo.set_backend(backend) @@ -281,7 +281,7 @@ def test_qaoa_errors(): @pytest.mark.parametrize(test_names, test_values) def test_qaoa_optimization(backend, method, options, trotter, filename): original_backend = qibo.get_backend() - if method == "sgd" and backend != "tensorflow_matmuleinsum": + if method == "sgd" and backend != "tensorflow": pytest.skip("Skipping SGD test for unsupported backend.") qibo.set_backend(backend) h = hamiltonians.XXZ(3, trotter=trotter)