diff --git a/.github/scripts/build-linux.sh b/.github/scripts/build-linux.sh deleted file mode 100755 index cb57879920..0000000000 --- a/.github/scripts/build-linux.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash - -set -e -x - -bash --version -rpm -q centos-release - -# enter repository folder -cd /io - -# load retry script -source .github/scripts/retry.sh - -# check python versions -ls /opt/python - -if [ $PYTHON_VERSION == "3.5" ]; then - PYBIN="/opt/python/cp35-cp35m/bin" -elif [ $PYTHON_VERSION == "3.6" ]; then - PYBIN="/opt/python/cp36-cp36m/bin" -elif [ $PYTHON_VERSION == "3.7" ]; then - PYBIN="/opt/python/cp37-cp37m/bin" -elif [ $PYTHON_VERSION == "3.8" ]; then - PYBIN="/opt/python/cp38-cp38/bin" -elif [ $PYTHON_VERSION == "3.9" ]; then - PYBIN="/opt/python/cp39-cp39/bin" -else - echo "Unsupported Python version $PYTHON_VERSION" - exit 1 -fi - -# install cuda-11.0 -curl https://developer.download.nvidia.com/compute/cuda/repos/rhel7/x86_64/cuda-repo-rhel7-10.2.89-1.x86_64.rpm -o cuda-repo.rpm -rpm -i cuda-repo.rpm -retry yum install -y cuda-compiler-11-0 cuda-libraries-devel-11-0 - -# set env variables -export PYTHON=$PYBIN/python -export CUDA_PATH=/usr/local/cuda-11.0/ - -# build wheel -retry $PYTHON -m pip install -r requirements.txt -retry $PYTHON setup.py bdist_wheel --dist-dir=wheelhouse - -# patch auditwheel -POLICY_JSON=$(find / -name policy.json) -sed -i "s/libresolv.so.2\"/libresolv.so.2\", \"libtensorflow_framework.so.2\"/g" $POLICY_JSON -cat $POLICY_JSON - -# repair wheel -auditwheel show wheelhouse/*.whl -auditwheel repair wheelhouse/*.whl -w wheelhouse - -# move the wheel to dist/ folder -mkdir -p dist -mv wheelhouse/qibo*manylinux2010*.whl dist/ diff --git a/.github/scripts/retry.sh b/.github/scripts/retry.sh deleted file mode 100755 index 83bdf7bd58..0000000000 --- a/.github/scripts/retry.sh +++ /dev/null @@ -1,40 +0,0 @@ -# MIT LICENSE -# -# Copyright (c) 2018 Travis CI GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of -# this software and associated documentation files (the "Software"), to deal in -# the Software without restriction, including without limitation the rights to -# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -# the Software, and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/bash/travis_retry.bash -retry() { - local result=0 - local count=1 - while [[ "${count}" -le 3 ]]; do - [[ "${result}" -ne 0 ]] && { - echo -e "\\n${ANSI_RED}The command \"${*}\" failed. Retrying, ${count} of 3.${ANSI_RESET}\\n" >&2 - } - "${@}" && { result=0 && break; } || result="${?}" - count="$((count + 1))" - sleep 1 - done - - [[ "${count}" -gt 3 ]] && { - echo -e "\\n${ANSI_RED}The command \"${*}\" failed 3 times.${ANSI_RESET}\\n" >&2 - } - - return "${result}" -} diff --git a/.github/workflows/rules.yml b/.github/workflows/rules.yml index e241b5bd9a..aefb48c5ee 100644 --- a/.github/workflows/rules.yml +++ b/.github/workflows/rules.yml @@ -21,11 +21,18 @@ jobs: with: brew: libomp - name: Install package + if: startsWith(matrix.os, 'windows') == 0 + run: | + python -m pip install --upgrade pip + pip install pylint + pip install pytest-cov + pip install .[qibotf,docs,tests] + - name: Install package on Windows + if: startsWith(matrix.os, 'windows') run: | python -m pip install --upgrade pip pip install pylint pip install pytest-cov - pip install -r requirements.txt pip install .[docs,tests] - name: Test with pylint run: | diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e9cd6f7a6c..c5f8b7a361 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -24,26 +24,11 @@ jobs: uses: mstksg/get-package@v1 with: brew: libomp - - name: Build linux wheels - if: startsWith(matrix.os, 'ubuntu') - run: docker run --rm -e PYTHON_VERSION -v `pwd`:/io quay.io/pypa/manylinux2010_x86_64 /io/.github/scripts/build-linux.sh - env: - PYTHON_VERSION: ${{ matrix.python-version }} - - name: Build macos wheels - if: startsWith(matrix.os, 'macos') + - name: Build wheels run: | python -m pip install --upgrade pip - pip install -r requirements.txt - python setup.py bdist_wheel - pip install delocate - cd dist - delocate-wheel -v `ls` - - name: Build windows wheels - if: startsWith(matrix.os, 'windows') - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - python setup.py bdist_wheel + pip install wheel + python setup.py sdist bdist_wheel - name: Store wheels as artifacts uses: actions/upload-artifact@v2 with: @@ -51,6 +36,14 @@ jobs: path: dist - name: Test wheels if: startsWith(matrix.os, 'windows') == 0 + run: | + python -m pip install --upgrade pip + pip install qibotf pytest cirq ply sklearn + pip install -r requirements.txt + pip install qibo --no-index --find-links ./dist/ + pytest --pyargs qibo + - name: Test wheels on windows + if: startsWith(matrix.os, 'windows') run: | python -m pip install --upgrade pip pip install pytest cirq ply sklearn diff --git a/doc/source/applications.rst b/doc/source/applications.rst index 4de8e08c9e..10c4d27aa2 100644 --- a/doc/source/applications.rst +++ b/doc/source/applications.rst @@ -23,4 +23,5 @@ problems. tutorials/shor/README.md tutorials/qPDF/qPDF.ipynb tutorials/bell-variational/README.md - tutorials/grover/README.md + tutorials/falqon/README.md + tutorials/grover/README.md \ No newline at end of file diff --git a/doc/source/tutorials/falqon/README.md b/doc/source/tutorials/falqon/README.md new file mode 120000 index 0000000000..93cfe1a74d --- /dev/null +++ b/doc/source/tutorials/falqon/README.md @@ -0,0 +1 @@ +../../../../examples/falqon/README.md \ No newline at end of file diff --git a/doc/source/tutorials/falqon/images b/doc/source/tutorials/falqon/images new file mode 120000 index 0000000000..42ef035371 --- /dev/null +++ b/doc/source/tutorials/falqon/images @@ -0,0 +1 @@ +../../../../examples/falqon/images/ \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index e2cbba73eb..a0808d7d34 100644 --- a/examples/README.md +++ b/examples/README.md @@ -18,6 +18,7 @@ physics problems. - [Shor's factorization algorithm](shor/README.md) - [Determining the proton content with proton with a quantum computer](qPDF/qPDF.ipynb) - [Maximal violation of Bell inequalities variationally](bell-variational/README.md) +- [Feedback-based ALgorithm for Quantum OptimizatioN - FALQON](falqon/README.md) - [A general Grover model](grover/README.md) In the `benchmarks` folder we have included examples concerning: diff --git a/examples/falqon/README.md b/examples/falqon/README.md new file mode 100644 index 0000000000..9dabdf6a66 --- /dev/null +++ b/examples/falqon/README.md @@ -0,0 +1,50 @@ +# Feedback-based ALgorithm for Quantum OptimizatioN - FALQON + +Code at: [https://github.com/Quantum-TII/qibo/tree/master/examples/falqon](https://github.com/Quantum-TII/qibo/tree/master/examples/falqon) + +Quantum Approximate Optimisation Algorithm (QAOA) is considered as one of the most important algorithms for optimisation in Quantum Computers, see [arXiv:1411.4028](https://arxiv.org/abs/1411.4028) by Farhi, Goldstone and Gutmann for more information. + +In this QAOA algorithm, the aim is to have a problem Hamiltonian H_P and a mixer Hamiltonian H_B. Then, starting with the ground state of H_B, the goal is to repeatedly apply e^(i H_P c) and e^(i H_B b), where c and b are tunable parameters. The values of such parameters are to be found via classical optimization + +In the FALQON algorithm, [arXiv:2103.08619](https://arxiv.org/abs/2103.08619) by Magann, Rudinger, Grace and Sarovan, they propose a similar although conceptually different algorithm. The proposal consists in evolving the initial state using the Schrödinger equation + +![schrodingerequation](images/schrodinger_equation.png) + +This equation satisfies that the expectation value of H_P is monotonically decreasing. This feature is used to create a Hamiltonian evolution with 1 layer using e^(i H_P c) and e^(i H_B b). In the first layer, b=0, and c is a parameter to be defined. Then, the quantity A = i[H_P, H_B] is measured. Its expectation value is then taken to be the parameter b for the next layer. As more layers are added, the +approximation to the ground state of the problem Hamiltonian H_P is more and more accurate. + +![scheme](images/scheme.png) + +### Running the code + +This example contains just one file +- `main.py` is the file where the algorithm is run. The main class `FALQON` is now introduced in `QIBO` + +The `FALQON` class behaves similarly to the `QAOA` one. It admits the following parameters: +- `hamiltonian`: problem Hamiltonian + whose ground state is sought. +- `mixer`: mixer Hamiltonian. + If ``None``, `qibo.hamiltonians.X` is used. +- `solver`: solver used to apply the exponential operators. + Default solver is 'exp'. +- `callbacks`: List of callbacks to calculate during evolution. +- `accelerators`: Dictionary of devices to use for distributed + execution. See `qibo.tensorflow.distcircuit.DistributedCircuit` + for more details. This option is available only when ``hamiltonian`` + is a `qibo.abstractions.hamiltonians.TrotterHamiltonian`. +- `memory_device`: Name of device where the full state will be saved. + Relevant only for distributed execution (when ``accelerators`` is + given). + +When performing the execution of the problem, the following variables are to be set: + +- `delta_t`: initial guess for the time step. A too large delta_t will make the algorithm fail. +- `max_layers`: maximum number of layers allowed for the FALQON. +- `initial_state`: initial state vector of the FALQON. +- `tol`: Tolerance of energy change. If not specified, no check is done. +- `callback`: Called after each iteration for scipy optimizers. +- `options`: a dictionary with options for the different optimizers. +- `compile`: whether the TensorFlow graph should be compiled. +- `processes`: number of processes when using the paralle BFGS method. + +The attached example provides an easy implementation of the FALQON method for a Heisenberg XXZ model. diff --git a/examples/falqon/images/scheme.png b/examples/falqon/images/scheme.png new file mode 100644 index 0000000000..3e5785185e Binary files /dev/null and b/examples/falqon/images/scheme.png differ diff --git a/examples/falqon/images/schrodinger_equation.png b/examples/falqon/images/schrodinger_equation.png new file mode 100644 index 0000000000..520ed1a9fa Binary files /dev/null and b/examples/falqon/images/schrodinger_equation.png differ diff --git a/examples/falqon/main.py b/examples/falqon/main.py new file mode 100644 index 0000000000..b1ef1a5222 --- /dev/null +++ b/examples/falqon/main.py @@ -0,0 +1,24 @@ +import argparse +from qibo import models, hamiltonians + + +def main(nqubits, delta_t=.1, max_layers=100): + # create XXZ Hamiltonian for nqubits qubits + hamiltonian = hamiltonians.XXZ(nqubits) + # create FALQON model for this Hamiltonian + falqon = models.FALQON(hamiltonian) + + best_energy, final_parameters = falqon.minimize(delta_t, max_layers)[:2] + + print('The optimal energy found is', best_energy) + + return best_energy, final_parameters + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--nqubits", default=5, type=int, help="Number of qubits.") + parser.add_argument("--delta_t", default=.1, type=float, help="Optimization parameter, time step for the first layer") + parser.add_argument("--max_layers", default=100, type=int, help="Maximum number of layers") + args = vars(parser.parse_args()) + main(**args) diff --git a/examples/reuploading_classifier/datasets.py b/examples/reuploading_classifier/datasets.py index 958c0beb93..1f0d1d76d4 100644 --- a/examples/reuploading_classifier/datasets.py +++ b/examples/reuploading_classifier/datasets.py @@ -53,6 +53,9 @@ def create_target(name): * np.sqrt(2 / 3)], dtype=complex), np.array([1 / np.sqrt(3), np.exp(-1j * 2 * np.pi / 3) * np.sqrt(2 / 3)], dtype=complex)] + else: + raise NotImplementedError('This dataset is not implemented') + return targets diff --git a/examples/reuploading_classifier/main.py b/examples/reuploading_classifier/main.py index 84a051d483..eab3095bcb 100644 --- a/examples/reuploading_classifier/main.py +++ b/examples/reuploading_classifier/main.py @@ -1,10 +1,10 @@ # /usr/bin/env python -import datasets as ds -import numpy as np from qlassifier import single_qubit_classifier import pickle import argparse +#TODO: fix issue with .pkl + parser = argparse.ArgumentParser() parser.add_argument("--dataset", default='tricrown', help="Name of the example", type=str) @@ -20,9 +20,13 @@ def main(dataset, layers): layers (int): Number of layers to use in the classifier """ ql = single_qubit_classifier(dataset, layers) # Define classifier - with open('saved_parameters.pkl', 'rb') as f: - # Load previous results. Have we ever run these problem? - data = pickle.load(f) + try: + with open('saved_parameters.pkl', 'rb') as f: + # Load previous results. Have we ever run these problem? + data = pickle.load(f) + except: + data = {dataset: {}} + try: parameters = data[dataset][layers] print('Problem solved before, obtaining parameters from file...') @@ -31,6 +35,7 @@ def main(dataset, layers): print('Problem never solved, finding optimal parameters...') result, parameters = ql.minimize( method='l-bfgs-b', options={'disp': True}) + data[dataset][layers] = parameters with open('saved_parameters.pkl', 'wb') as f: pickle.dump(data, f, pickle.HIGHEST_PROTOCOL) diff --git a/examples/reuploading_classifier/saved_parameters.pkl b/examples/reuploading_classifier/saved_parameters.pkl index 4b94cdc48a..48bb6da9e1 100644 Binary files a/examples/reuploading_classifier/saved_parameters.pkl and b/examples/reuploading_classifier/saved_parameters.pkl differ diff --git a/examples/test_examples.py b/examples/test_examples.py index 38151403f6..6477c52431 100644 --- a/examples/test_examples.py +++ b/examples/test_examples.py @@ -246,6 +246,19 @@ def test_shor(N, times, A, semiclassical, enhance): run_script(args) +@pytest.mark.parametrize("nqubits", [4, 5]) +@pytest.mark.parametrize("delta_t", [0.5, 0.1]) +@pytest.mark.parametrize("max_layers", [10, 100]) +def test_falqon(nqubits, delta_t, max_layers): + if "functions" in sys.modules: + del sys.modules["functions"] + args = locals() + path = os.path.join(base_dir, "falqon") + sys.path[-1] = path + os.chdir(path) + run_script(args) + + @pytest.mark.parametrize("nqubits", [5, 6, 7]) def test_grover_example1(nqubits): args = locals() @@ -274,3 +287,4 @@ def test_grover_example3(nqubits, num_1): sys.path[-1] = path os.chdir(path) run_script(args, script_name="example3.py") + diff --git a/requirements.txt b/requirements.txt index 4ade79c53e..08beb742bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,5 @@ # Runtime requirements for qibo -# numpy (included in tf) -tensorflow==2.4.1 scipy sympy cma diff --git a/setup.py b/setup.py index b59c6f2abc..ff0b52fdc6 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,11 @@ # Installation script for python from setuptools import setup, find_packages -from setuptools.command.build_py import build_py as _build_py -from setuptools.dist import Distribution -import subprocess import os import re -import sys PACKAGE = "qibo" + # Returns the qibo version def get_version(): """ Gets the version from the package's __init__ file @@ -21,40 +18,12 @@ def get_version(): if mo: return mo.group(1) -# Custom compilation step -class Build(_build_py): - def run(self): - if os.name != 'nt': # skip windows - commands = [ - ["make", "-j", "%s" % os.cpu_count(), - "-C", "src/qibo/tensorflow/custom_operators/"],] - for command in commands: - if subprocess.call(command) != 0: - sys.exit(-1) - _build_py.run(self) - - -# Register wheel with binary version -class BinaryDistribution(Distribution): - """This class is needed in order to create OS specific wheels.""" - - def has_ext_modules(self): - return True - - def is_pure(self): - return False - -# Patch to generate manylinux2010 packages -from setuptools.command.install import install -class InstallPlatlib(install): - def finalize_options(self): - install.finalize_options(self) - self.install_lib = self.install_platlib # Read in requirements requirements = open('requirements.txt').readlines() requirements = [r.strip() for r in requirements] + # load long description from README this_directory = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f: @@ -69,11 +38,9 @@ def finalize_options(self): url="https://github.com/Quantum-TII/qibo", packages=find_packages("src"), package_dir={"": "src"}, - cmdclass={"build_py": Build, "install": InstallPlatlib}, - package_data={"": ["*.so", "*.out"]}, + package_data={"": ["*.out"]}, include_package_data=True, zip_safe=False, - distclass=BinaryDistribution, classifiers=[ "Programming Language :: Python :: 3", "Topic :: Scientific/Engineering :: Physics", @@ -81,7 +48,8 @@ def finalize_options(self): install_requires=requirements, extras_require={ "docs": ["sphinx", "sphinx_rtd_theme", "recommonmark", "sphinxcontrib-bibtex", "sphinx_markdown_tables", "nbsphinx", "IPython"], - "tests": ["cirq", "ply", "sklearn"], + "tests": ["pytest", "cirq", "ply", "sklearn"], + "qibotf": ["qibotf"], }, python_requires=">=3.6.0", long_description=long_description, diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index 0cd3283963..e6e53ba054 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -22,12 +22,13 @@ def __init__(self): if self.check_availability("tensorflow"): from qibo.backends.tensorflow import TensorflowDefaultEinsumBackend, TensorflowMatmulEinsumBackend os.environ["TF_CPP_MIN_LOG_LEVEL"] = str(config.LOG_LEVEL) - import tensorflow as tf + import tensorflow as tf # pylint: disable=E0401 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"): + if self.check_availability("qibotf"): + from qibo.backends.tensorflow import TensorflowCustomBackend self.available_backends["custom"] = TensorflowCustomBackend self.available_backends["tensorflow"] = TensorflowCustomBackend else: # pragma: no cover @@ -84,7 +85,7 @@ def construct_backend(self, name): new_backend = self.available_backends.get(name)() if self.active_backend is not None: new_backend.set_precision(self.active_backend.precision) - if self.active_backend.default_device is not None: + if self.active_backend.default_device: new_backend.set_device(self.active_backend.default_device) self.constructed_backends[name] = new_backend return self.constructed_backends.get(name) diff --git a/src/qibo/backends/abstract.py b/src/qibo/backends/abstract.py index e898fec397..e064589e16 100644 --- a/src/qibo/backends/abstract.py +++ b/src/qibo/backends/abstract.py @@ -14,9 +14,9 @@ def __init__(self): self.cpu_devices = [] self.gpu_devices = [] - self.default_device = None + self.default_device = [] - self.matrices = None + self._matrices = None self.numeric_types = None self.tensor_types = None self.native_types = None @@ -43,8 +43,6 @@ def set_precision(self, dtype): else: raise_error(ValueError, f'dtype {dtype} not supported.') self.precision = dtype - if self.matrices is not None: - self.matrices.dtype = self.dtypes('DTYPECPX') def set_device(self, name): parts = name[1:].split(":") @@ -80,6 +78,23 @@ def cpu_fallback(self, func, *args): with self.device(self.get_cpu()): return func(*args) + @property + def matrices(self): + if self._matrices is None: + from qibo.backends.matrices import Matrices + self._matrices = Matrices(self) + return self._matrices + + @abstractmethod + def to_numpy(self, x): # pragma: no cover + """Convert tensor to numpy.""" + raise_error(NotImplementedError) + + @abstractmethod + def to_complex(self, re, img): # pragma: no cover + """Creates complex number from real numbers.""" + raise_error(NotImplementedError) + @abstractmethod def cast(self, x, dtype='DTYPECPX'): # pragma: no cover """Casts tensor to the given dtype.""" diff --git a/src/qibo/backends/matrices.py b/src/qibo/backends/matrices.py index 7092c20222..a21b7beeca 100644 --- a/src/qibo/backends/matrices.py +++ b/src/qibo/backends/matrices.py @@ -1,12 +1,12 @@ import numpy as np -class NumpyMatrices: +class Matrices: _NAMES = ["I", "H", "X", "Y", "Z", "CNOT", "CZ", "SWAP", "TOFFOLI"] - def __init__(self, dtype): - self._dtype = dtype + def __init__(self, backend): + self.backend = backend self._I = None self._H = None self._X = None @@ -22,16 +22,9 @@ def allocate_matrices(self): for name in self._NAMES: getattr(self, f"_set{name}")() - def cast(self, x): - return x.astype(self.dtype) - @property def dtype(self): - return self._dtype - - @dtype.setter - def dtype(self, dtype): - self._dtype = dtype + return self.backend._dtypes.get('DTYPECPX') @property def I(self): @@ -70,69 +63,46 @@ def TOFFOLI(self): return self._TOFFOLI def _setI(self): - self._I = self.cast(np.eye(2, dtype=self.dtype)) + self._I = self.backend.cast(np.eye(2, dtype=self.dtype)) def _setH(self): m = np.ones((2, 2), dtype=self.dtype) m[1, 1] = -1 - self._H = self.cast(m / np.sqrt(2)) + self._H = self.backend.cast(m / np.sqrt(2)) def _setX(self): m = np.zeros((2, 2), dtype=self.dtype) m[0, 1], m[1, 0] = 1, 1 - self._X = self.cast(m) + self._X = self.backend.cast(m) def _setY(self): m = np.zeros((2, 2), dtype=self.dtype) m[0, 1], m[1, 0] = -1j, 1j - self._Y = self.cast(m) + self._Y = self.backend.cast(m) def _setZ(self): m = np.eye(2, dtype=self.dtype) m[1, 1] = -1 - self._Z = self.cast(m) + self._Z = self.backend.cast(m) def _setCNOT(self): m = np.eye(4, dtype=self.dtype) m[2, 2], m[2, 3] = 0, 1 m[3, 2], m[3, 3] = 1, 0 - self._CNOT = self.cast(m) + self._CNOT = self.backend.cast(m) def _setCZ(self): m = np.diag([1, 1, 1, -1]) - self._CZ = self.cast(m) + self._CZ = self.backend.cast(m) def _setSWAP(self): m = np.eye(4, dtype=self.dtype) m[1, 1], m[1, 2] = 0, 1 m[2, 1], m[2, 2] = 1, 0 - self._SWAP = self.cast(m) + self._SWAP = self.backend.cast(m) def _setTOFFOLI(self): m = np.eye(8, dtype=self.dtype) m[-2, -2], m[-2, -1] = 0, 1 m[-1, -2], m[-1, -1] = 1, 0 - self._TOFFOLI = self.cast(m) - - -class TensorflowMatrices(NumpyMatrices): - - def __init__(self, dtype): - import tensorflow as tf - self.tf = tf - self.tftype = dtype - if dtype == tf.complex128: - super().__init__(np.complex128) - elif dtype == tf.complex64: - super().__init__(np.complex64) - - @NumpyMatrices.dtype.setter - def dtype(self, dtype): - self.tftype = dtype - if dtype == self.tf.complex128: - self._dtype = np.complex128 - elif dtype == self.tf.complex64: - self._dtype = np.complex64 - - def cast(self, x): - return self.tf.cast(x, dtype=self.tftype) + self._TOFFOLI = self.backend.cast(m) diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index 53fec9f62b..4f527e5095 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -14,8 +14,6 @@ def __init__(self): self.name = "numpy" self.np = np - from qibo.backends import matrices - self.matrices = matrices.NumpyMatrices(self.dtypes('DTYPECPX')) self.numeric_types = (np.int, np.float, np.complex, np.int32, np.int64, np.float32, np.float64, np.complex64, np.complex128) @@ -34,6 +32,12 @@ def set_device(self, name): log.warning("Numpy does not support device placement. " "Aborting device change.") + def to_numpy(self, x): + return x + + def to_complex(self, re, img): # pragma: no cover + return re + 1j * img + def cast(self, x, dtype='DTYPECPX'): if isinstance(dtype, str): dtype = self.dtypes(dtype) diff --git a/src/qibo/backends/tensorflow.py b/src/qibo/backends/tensorflow.py index 9d8bbcfe6e..d7197d5327 100644 --- a/src/qibo/backends/tensorflow.py +++ b/src/qibo/backends/tensorflow.py @@ -6,7 +6,7 @@ class Optimization: def __init__(self): - import tensorflow as tf + import tensorflow as tf # pylint: disable=E0401 self.Variable = tf.Variable self.GradientTape = tf.GradientTape self.optimizers = tf.optimizers @@ -18,7 +18,7 @@ class TensorflowBackend(numpy.NumpyBackend): def __init__(self): super().__init__() - import tensorflow as tf + import tensorflow as tf # pylint: disable=E0401 self.backend = tf self.name = "tensorflow" @@ -33,15 +33,12 @@ def __init__(self): # case not tested by GitHub workflows because it requires no device raise_error(RuntimeError, "Unable to find Tensorflow devices.") - from qibo.backends import matrices - self.matrices = matrices.TensorflowMatrices(self.dtypes('DTYPECPX')) - self.tensor_types = (self.np.ndarray, tf.Tensor, tf.Variable) self.native_types = (tf.Tensor, tf.Variable) self.Tensor = tf.Tensor self.random = tf.random self.newaxis = tf.newaxis - from tensorflow.python.framework import errors_impl # pylint: disable=E0611 + from tensorflow.python.framework import errors_impl # pylint: disable=E0611,E0401 self.oom_error = errors_impl.ResourceExhaustedError self.optimization = Optimization() @@ -52,6 +49,12 @@ def __init__(self): def set_device(self, name): abstract.AbstractBackend.set_device(self, name) + def to_numpy(self, x): + return x.numpy() + + def to_complex(self, re, img): + return self.backend.complex(re, img) + def cast(self, x, dtype='DTYPECPX'): if isinstance(dtype, str): dtype = self.dtypes(dtype) @@ -197,13 +200,13 @@ class TensorflowCustomBackend(TensorflowBackend): "This is the fastest simulation engine." def __init__(self): - from qibo.backends import _check_availability - if not _check_availability("qibo_sim_tensorflow"): # pragma: no cover + from qibo.backends import Backend + if not Backend.check_availability("qibotf"): # pragma: no cover # CI can compile custom operators so this case is not tested raise_error(RuntimeError, "Cannot initialize Tensorflow custom " "backend if custom operators are not " "compiled.") - from qibo_sim_tensorflow import custom_operators as op + from qibotf import custom_operators as op # pylint: disable=E0401 super().__init__() self.name = "custom" self.custom_gates = True diff --git a/src/qibo/core/hamiltonians.py b/src/qibo/core/hamiltonians.py index 47399495d3..685da97183 100644 --- a/src/qibo/core/hamiltonians.py +++ b/src/qibo/core/hamiltonians.py @@ -221,11 +221,13 @@ def __init__(self, hamiltonian, symbol_map): term_dict = self.symbolic.as_coefficients_dict() self.constant = 0 if 1 in term_dict: - self.constant = self.matrices.dtype(term_dict.pop(1)) + dtype = getattr(K.np, self.matrices.dtype) + self.constant = dtype(term_dict.pop(1)) self.terms = dict() target_ids = set() for term, coeff in term_dict.items(): - targets, matrices = [], [self.matrices.dtype(coeff)] + dtype = getattr(K.np, self.matrices.dtype) + targets, matrices = [], [dtype(coeff)] for factor in term.as_ordered_factors(): if factor.is_symbol: self._check_symbolmap(factor) diff --git a/src/qibo/models/__init__.py b/src/qibo/models/__init__.py index 5119924bc5..671188e7f2 100644 --- a/src/qibo/models/__init__.py +++ b/src/qibo/models/__init__.py @@ -1,5 +1,5 @@ from qibo.models.circuit import Circuit, QFT from qibo.models.evolution import StateEvolution, AdiabaticEvolution -from qibo.models.variational import VQE, QAOA +from qibo.models.variational import VQE, QAOA, FALQON from qibo.models.grover import Grover from qibo.models import hep diff --git a/src/qibo/models/evolution.py b/src/qibo/models/evolution.py index 7913ea4454..74efe9a009 100644 --- a/src/qibo/models/evolution.py +++ b/src/qibo/models/evolution.py @@ -324,7 +324,7 @@ def minimize(self, initial_parameters, method="BFGS", options=None, if method == "sgd": loss = self._loss else: - loss = lambda p, ae, h1, msg, hist: self._loss(p, ae, h1, msg, hist).numpy() + loss = lambda p, ae, h1, msg, hist: K.to_numpy(self._loss(p, ae, h1, msg, hist)) result, parameters, extra = optimizers.optimize(loss, initial_parameters, args=(self, self.h1, self.opt_messages, self.opt_history), diff --git a/src/qibo/models/variational.py b/src/qibo/models/variational.py index df3e71a95d..4684949f2c 100644 --- a/src/qibo/models/variational.py +++ b/src/qibo/models/variational.py @@ -285,7 +285,7 @@ def _loss(params, qaoa, hamiltonian): if method == "sgd": loss = lambda p, c, h: _loss(K.cast(p), c, h) else: - 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_p, args=(self, self.hamiltonian), method=method, jac=jac, hess=hess, hessp=hessp, @@ -294,3 +294,90 @@ def _loss(params, qaoa, hamiltonian): compile=compile, processes=processes) self.set_parameters(parameters) return result, parameters, extra + + +class FALQON(QAOA): + """ Feedback-based ALgorithm for Quantum OptimizatioN (FALQON) model. + + The FALQON is introduced in `arXiv:2103.08619 `_. + It inherits the QAOA class. + + Args: + hamiltonian (:class:`qibo.abstractions.hamiltonians.Hamiltonian`): problem Hamiltonian + whose ground state is sought. + mixer (:class:`qibo.abstractions.hamiltonians.Hamiltonian`): mixer Hamiltonian. + If ``None``, :class:`qibo.hamiltonians.X` is used. + solver (str): solver used to apply the exponential operators. + Default solver is 'exp' (:class:`qibo.solvers.Exponential`). + callbacks (list): List of callbacks to calculate during evolution. + accelerators (dict): Dictionary of devices to use for distributed + execution. See :class:`qibo.tensorflow.distcircuit.DistributedCircuit` + for more details. This option is available only when ``hamiltonian`` + is a :class:`qibo.abstractions.hamiltonians.TrotterHamiltonian`. + memory_device (str): Name of device where the full state will be saved. + Relevant only for distributed execution (when ``accelerators`` is + given). + + Example: + :: + + import numpy as np + from qibo import models, hamiltonians + # create XXZ Hamiltonian for four qubits + hamiltonian = hamiltonians.XXZ(4) + # create FALQON model for this Hamiltonian + falqon = models.FALQON(hamiltonian) + # optimize using random initial variational parameters + # and default options and initial state + delta_t = 0.01 + best_energy, final_parameters, extra = falqon.minimize(delta_t, max_layers) + """ + + def __init__(self, hamiltonian, mixer=None, solver="exp", callbacks=[], + accelerators=None, memory_device="/CPU:0"): + super().__init__(hamiltonian, mixer, solver, callbacks, accelerators, memory_device) + self.evol_hamiltonian = 1j * (self.hamiltonian @ self.mixer - self.mixer @ self.hamiltonian) + + def minimize(self, delta_t, max_layers, initial_state=None, tol=None, callback=None): + """Optimizes the variational parameters of the FALQON. + + Args: + delta_t (float): initial guess for the time step. A too large delta_t will make the algorithm fail. + max_layers (int): maximum number of layers allowed for the FALQON. + initial_state (np.ndarray): initial state vector of the FALQON. + tol (float): Tolerance of energy change. If not specified, no check is done. + callback (callable): Called after each iteration for scipy optimizers. + options (dict): a dictionary with options for the different optimizers. + + Return: + The final energy (expectation value of the ``hamiltonian``). + The corresponding best parameters. + extra: variable with historical data for the energy and callbacks. + """ + import numpy as np + parameters = np.array([delta_t, 0]) + + def _loss(params, falqon, hamiltonian): + falqon.set_parameters(params) + state = falqon(initial_state) + return hamiltonian.expectation(state) + + energy = [np.inf] + callback_result = [] + for it in range(1, max_layers + 1): + beta = _loss(parameters, self, self.evol_hamiltonian) + + if tol is not None: + energy.append(np.array(_loss(parameters, self, self.hamiltonian))) + if abs(energy[-1] - energy[-2]) < tol: + break + + if callback is not None: + callback_result.append(callback(parameters)) + + parameters = np.hstack([parameters, np.array([delta_t, delta_t * beta])]) + + self.set_parameters(parameters) + final_loss = _loss(parameters, self, self.hamiltonian) + extra = {'energies': energy, 'callbacks': callback_result} + return final_loss, parameters, extra diff --git a/src/qibo/optimizers.py b/src/qibo/optimizers.py index 8f9cac9337..cd0ce2875b 100644 --- a/src/qibo/optimizers.py +++ b/src/qibo/optimizers.py @@ -18,7 +18,8 @@ def optimize(loss, initial_parameters, args=(), method='Powell', args (tuple): optional arguments for the loss function. method (str): Name of optimizer to use. Can be ``'cma'``, ``'sgd'`` or one of the Newtonian methods supported by - :meth:`qibo.optimizers.newtonian` and ``'parallel_L-BFGS-B'``. + :meth:`qibo.optimizers.newtonian` and ``'parallel_L-BFGS-B'``. ``sgd`` is + only available for backends based on tensorflow. jac (dict): Method for computing the gradient vector for scipy optimizers. hess (dict): Method for computing the hessian matrix for scipy optimizers. hessp (callable): Hessian of objective function times an arbitrary @@ -166,16 +167,11 @@ def sgd(loss, initial_parameters, args=(), options=None, compile=False): # check if gates are using the MatmulEinsum backend compatible_backends = { "tensorflow_defaulteinsum", "tensorflow_matmuleinsum"} - from qibo.core.circuit import Circuit - for argument in args: - if isinstance(argument, Circuit): - 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 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 sgd_options = {"nepochs": 1000000, "nmessage": 1000, diff --git a/src/qibo/tests/test_density_matrix.py b/src/qibo/tests/test_density_matrix.py index ec9a38726d..e7e0580ab5 100644 --- a/src/qibo/tests/test_density_matrix.py +++ b/src/qibo/tests/test_density_matrix.py @@ -1,7 +1,7 @@ import numpy as np import pytest import qibo -from qibo import models, gates, callbacks +from qibo import models, gates, callbacks, K from qibo.tests import utils @@ -38,7 +38,6 @@ def test_circuit_dm(backend): def test_density_matrix_circuit_initial_state(backend): """Check that circuit transforms state vector initial state to density matrix.""" - import tensorflow as tf original_backend = qibo.get_backend() qibo.set_backend(backend) initial_psi = utils.random_numpy_state(3) @@ -47,7 +46,7 @@ def test_density_matrix_circuit_initial_state(backend): target_rho = np.outer(initial_psi, initial_psi.conj()) np.testing.assert_allclose(final_rho, target_rho) - initial_psi = tf.cast(initial_psi, dtype=final_rho.dtype) + initial_psi = K.cast(initial_psi, dtype=final_rho.dtype) final_rho = c(initial_psi) np.testing.assert_allclose(final_rho, target_rho) qibo.set_backend(original_backend) @@ -191,7 +190,7 @@ def test_unitary_channel(backend, density_matrix): initial_state = utils.random_numpy_state(4) c = models.Circuit(4, density_matrix=density_matrix) c.add(gates.UnitaryChannel(probs, matrices, seed=123)) - final_state = c(initial_state, nshots=20).numpy() + final_state = K.to_numpy(c(initial_state, nshots=20)) eye = np.eye(2, dtype=final_state.dtype) ma1 = np.kron(np.kron(a1, eye), np.kron(eye, eye)) diff --git a/src/qibo/tests/test_evolution.py b/src/qibo/tests/test_evolution.py index 445fe5f1a8..dce2fecf2b 100644 --- a/src/qibo/tests/test_evolution.py +++ b/src/qibo/tests/test_evolution.py @@ -1,6 +1,6 @@ import pytest import numpy as np -from qibo import callbacks, hamiltonians, models +from qibo import callbacks, hamiltonians, models, K from qibo.config import raise_error from scipy.linalg import expm @@ -71,7 +71,7 @@ def test_state_time_dependent_evolution_final_state(nqubits=2, dt=1e-2): # Analytical solution target_psi = [np.ones(2 ** nqubits) / np.sqrt(2 ** nqubits)] for n in range(int(1 / dt)): - prop = expm(-1j * dt * ham(n * dt).matrix.numpy()) + prop = expm(-1j * dt * K.to_numpy(ham(n * dt).matrix)) target_psi.append(prop.dot(target_psi[-1])) checker = TimeStepChecker(target_psi, atol=1e-8) @@ -223,11 +223,11 @@ def test_energy_callback(solver, atol, dt=1e-2): final_psi = adev(final_time=1) target_psi = np.ones(4) / 2 - calc_energy = lambda psi: psi.conj().dot(h1.matrix.numpy().dot(psi)) + calc_energy = lambda psi: psi.conj().dot(K.to_numpy(h1.matrix).dot(psi)) target_energies = [calc_energy(target_psi)] ham = lambda t: h0 * (1 - t) + h1 * t for n in range(int(1 / dt)): - prop = ham(n * dt).exp(dt).numpy() + prop = K.to_numpy(ham(n * dt).exp(dt)) target_psi = prop.dot(target_psi) target_energies.append(calc_energy(target_psi)) @@ -247,7 +247,7 @@ def test_rk4_evolution(solver, trotter, dt=1e-3): target_psi = [np.ones(8) / np.sqrt(8)] ham = lambda t: h0 * (1 - t) + h1 * t for n in range(int(1 / dt)): - prop = ham(n * dt).exp(dt).numpy() + prop = K.to_numpy(ham(n * dt).exp(dt)) target_psi.append(prop.dot(target_psi[-1])) checker = TimeStepChecker(target_psi, atol=dt) @@ -282,7 +282,7 @@ def test_trotterized_adiabatic_evolution(accelerators, nqubits, dt): target_psi = [np.ones(2 ** nqubits) / np.sqrt(2 ** nqubits)] ham = lambda t: dense_h0 * (1 - t) + dense_h1 * t for n in range(int(1 / dt)): - prop = ham(n * dt).exp(dt).numpy() + prop = K.to_numpy(ham(n * dt).exp(dt)) target_psi.append(prop.dot(target_psi[-1])) local_h0 = hamiltonians.X(nqubits, trotter=True) @@ -323,7 +323,16 @@ def test_scheduling_optimization(method, options, messages, trotter, filename): h1 = hamiltonians.TFIM(3, trotter=trotter) sp = lambda t, p: (1 - p) * np.sqrt(t) + p * t adevp = models.AdiabaticEvolution(h0, h1, sp, dt=1e-1) - best, params, _ = adevp.minimize([0.5, 1], method=method, options=options, - messages=messages) + + if method == "sgd": + from qibo import K + if K.name not in {"tensorflow_defaulteinsum", "tensorflow_matmuleinsum"}: + with pytest.raises(RuntimeError): + best, params, _ = adevp.minimize([0.5, 1], method=method, options=options, + messages=messages) + else: + best, params, _ = adevp.minimize([0.5, 1], method=method, options=options, + messages=messages) + if filename is not None: assert_regression_fixture(params, filename) diff --git a/src/qibo/tests/test_hamiltonians.py b/src/qibo/tests/test_hamiltonians.py index 543fc27297..ebd7a71ba8 100644 --- a/src/qibo/tests/test_hamiltonians.py +++ b/src/qibo/tests/test_hamiltonians.py @@ -8,14 +8,13 @@ def test_hamiltonian_initialization(): """Testing hamiltonian initialization errors.""" - import tensorflow as tf dtype = K.dtypes('DTYPECPX') with pytest.raises(TypeError): H = Hamiltonian(2, "test") H1 = Hamiltonian(2, np.eye(4)) H1 = Hamiltonian(2, np.eye(4), numpy=True) - H1 = Hamiltonian(2, tf.eye(4, dtype=dtype)) - H1 = Hamiltonian(2, tf.eye(4, dtype=dtype), numpy=True) + H1 = Hamiltonian(2, K.eye(4, dtype=dtype)) + H1 = Hamiltonian(2, K.eye(4, dtype=dtype), numpy=True) with pytest.raises(ValueError): H1 = Hamiltonian(-2, np.eye(4)) with pytest.raises(RuntimeError): @@ -97,12 +96,11 @@ def test_right_operations(numpy): @pytest.mark.parametrize("numpy", [True, False]) def test_hamiltonian_mul(numpy): """Test multiplication with ``np.array`` and ``tf.Tensor`` scalar.""" - import tensorflow as tf h = TFIM(nqubits=3, h=1.0, numpy=numpy) h2 = h * np.array(2) np.testing.assert_allclose(h2.matrix, 2 * np.array(h.matrix)) _ = h.eigenvectors() - h2 = h * tf.cast(2, dtype=tf.complex128) + h2 = h * K.cast(2, dtype='DTYPECPX') np.testing.assert_allclose(h2.matrix, 2 * np.array(h.matrix)) @@ -115,8 +113,8 @@ def test_hamiltonian_matmul(numpy): m1 = H1.matrix m2 = H2.matrix else: - m1 = H1.matrix.numpy() - m2 = H2.matrix.numpy() + m1 = K.to_numpy(H1.matrix) + m2 = K.to_numpy(H2.matrix) np.testing.assert_allclose((H1 @ H2).matrix, m1 @ m2) np.testing.assert_allclose((H2 @ H1).matrix, m2 @ m1) @@ -357,7 +355,7 @@ def test_trotter_hamiltonian_matmul(nqubits, normalize): local_ham = TFIM(nqubits, h=1.0, trotter=True) dense_ham = TFIM(nqubits, h=1.0) - state = utils.random_tensorflow_complex((2 ** nqubits,)) + state = utils.random_backend_complex((2 ** nqubits,)) trotter_ev = local_ham.expectation(state, normalize) target_ev = dense_ham.expectation(state, normalize) np.testing.assert_allclose(trotter_ev, target_ev) @@ -433,7 +431,7 @@ def test_trotter_hamiltonian_make_compatible_simple(): def test_trotter_hamiltonian_make_compatible_redundant(): """Test ``make_compatible`` with redudant two-qubit terms.""" h0 = X(2, trotter=True) - target_matrix = h0.dense.matrix.numpy() + target_matrix = K.to_numpy(h0.dense.matrix) target_matrix = np.kron(target_matrix, np.eye(2, dtype=target_matrix.dtype)) parts = [{(0, 1, 2): TFIM(3, numpy=True)}] h1 = TrotterHamiltonian(*parts) diff --git a/src/qibo/tests/utils.py b/src/qibo/tests/utils.py index 9907cfa4b7..f9f06c65a5 100644 --- a/src/qibo/tests/utils.py +++ b/src/qibo/tests/utils.py @@ -7,13 +7,11 @@ def random_numpy_complex(shape, dtype=np.complex128): return (np.random.random(shape) + 1j * np.random.random(shape)).astype(dtype) -def random_tensorflow_complex(shape, dtype="float64"): - import tensorflow as tf - if isinstance(dtype, str): - dtype = getattr(tf, dtype) - _re = tf.random.uniform(shape, dtype=dtype) - _im = tf.random.uniform(shape, dtype=dtype) - return tf.complex(_re, _im) +def random_backend_complex(shape, dtype="float64"): + from qibo import K + _re = K.random_uniform(shape, dtype=dtype) + _im = K.random_uniform(shape, dtype=dtype) + return K.to_complex(_re, _im) def random_numpy_state(nqubits, dtype=np.complex128): diff --git a/src/qibo/tests_new/conftest.py b/src/qibo/tests_new/conftest.py index 701e49348e..e9a7d92c5e 100644 --- a/src/qibo/tests_new/conftest.py +++ b/src/qibo/tests_new/conftest.py @@ -82,10 +82,6 @@ def pytest_generate_tests(metafunc): if metafunc.module.__name__ in tests_to_skip: pytest.skip("Custom operator tests require Tensorflow engine.") - # for `test_backends_matrices.py` - if "engine" in metafunc.fixturenames: - metafunc.parametrize("engine", engines) - # for `test_backends_agreement.py` if "tested_backend" in metafunc.fixturenames: target = metafunc.config.option.target_backend diff --git a/src/qibo/tests_new/regressions/falqon1.out b/src/qibo/tests_new/regressions/falqon1.out new file mode 100644 index 0000000000..b510817b61 --- /dev/null +++ b/src/qibo/tests_new/regressions/falqon1.out @@ -0,0 +1,12 @@ +1.000000000000000056e-01 +0.000000000000000000e+00 +1.000000000000000056e-01 +5.960079923851835382e-02 +1.000000000000000056e-01 +1.163174774926115235e-01 +1.000000000000000056e-01 +1.526867293265563030e-01 +1.000000000000000056e-01 +1.367890776526200225e-01 +1.000000000000000056e-01 +8.903718462471775508e-02 diff --git a/src/qibo/tests_new/regressions/falqon2.out b/src/qibo/tests_new/regressions/falqon2.out new file mode 100644 index 0000000000..bc05940bf9 --- /dev/null +++ b/src/qibo/tests_new/regressions/falqon2.out @@ -0,0 +1,6 @@ +1.000000000000000021e-02 +0.000000000000000000e+00 +1.000000000000000021e-02 +5.999600007999924189e-04 +1.000000000000000021e-02 +1.199705365195721882e-03 diff --git a/src/qibo/tests_new/regressions/falqon3.out b/src/qibo/tests_new/regressions/falqon3.out new file mode 100644 index 0000000000..bc05940bf9 --- /dev/null +++ b/src/qibo/tests_new/regressions/falqon3.out @@ -0,0 +1,6 @@ +1.000000000000000021e-02 +0.000000000000000000e+00 +1.000000000000000021e-02 +5.999600007999924189e-04 +1.000000000000000021e-02 +1.199705365195721882e-03 diff --git a/src/qibo/tests_new/regressions/falqon4.out b/src/qibo/tests_new/regressions/falqon4.out new file mode 100644 index 0000000000..2c96fe3e31 --- /dev/null +++ b/src/qibo/tests_new/regressions/falqon4.out @@ -0,0 +1,4 @@ +1.000000000000000021e-02 +0.000000000000000000e+00 +1.000000000000000021e-02 +5.999600007999925273e-04 diff --git a/src/qibo/tests_new/test_backends_init.py b/src/qibo/tests_new/test_backends_init.py index 913c2b4a37..aa9d4546d0 100644 --- a/src/qibo/tests_new/test_backends_init.py +++ b/src/qibo/tests_new/test_backends_init.py @@ -4,10 +4,7 @@ def test_construct_backend(backend): bk = K.construct_backend(backend) - try: - assert bk.name == backend - except AssertionError: - assert bk.name.split("_")[-1] == backend + assert bk.name == backend with pytest.raises(ValueError): bk = K.construct_backend("test") @@ -16,15 +13,9 @@ def test_set_backend(backend): """Check ``set_backend`` for switching gate backends.""" original_backend = backends.get_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 - assert str(K) == target_name - assert repr(K) == target_name + assert K.name == backend + assert str(K) == backend + assert repr(K) == backend assert K.executing_eagerly() h = gates.H(0) if backend == "custom": @@ -44,8 +35,9 @@ def test_set_backend_errors(): with pytest.raises(ValueError): backends.set_backend("numpy_badgates") h = gates.H(0) - with pytest.warns(RuntimeWarning): - backends.set_backend("numpy_matmuleinsum") + if "numpy" not in original_backend: + with pytest.warns(RuntimeWarning): + backends.set_backend("numpy_matmuleinsum") backends.set_backend(original_backend) @@ -79,14 +71,14 @@ def test_set_precision_matrices(backend, precision): backends.set_backend(backend) backends.set_precision(precision) if precision == "single": - assert matrices.dtype == np.complex64 + assert matrices.dtype == "complex64" assert matrices.H.dtype == np.complex64 - assert K.matrices.dtype == K.backend.complex64 + assert K.matrices.dtype == "complex64" assert K.matrices.X.dtype == K.backend.complex64 else: - assert matrices.dtype == np.complex128 + assert matrices.dtype == "complex128" assert matrices.H.dtype == np.complex128 - assert K.matrices.dtype == K.backend.complex128 + assert K.matrices.dtype == "complex128" assert K.matrices.X.dtype == K.backend.complex128 backends.set_precision(original_precision) backends.set_backend(original_backend) diff --git a/src/qibo/tests_new/test_backends_matrices.py b/src/qibo/tests_new/test_backends_matrices.py index de603b8e4b..c7b6c4127f 100644 --- a/src/qibo/tests_new/test_backends_matrices.py +++ b/src/qibo/tests_new/test_backends_matrices.py @@ -1,44 +1,38 @@ import pytest +import qibo import numpy as np -from qibo.backends import matrices -from qibo.config import raise_error - -TARGET_MATRICES = { - "I": np.array([[1, 0], [0, 1]]), - "H": np.array([[1, 1], [1, -1]]) / np.sqrt(2), - "X": np.array([[0, 1], [1, 0]]), - "Y": np.array([[0, -1j], [1j, 0]]), - "Z": np.array([[1, 0], [0, -1]]), - "CNOT": np.array([[1, 0, 0, 0], [0, 1, 0, 0], - [0, 0, 0, 1], [0, 0, 1, 0]]), - "CZ": np.array([[1, 0, 0, 0], [0, 1, 0, 0], - [0, 0, 1, 0], [0, 0, 0, -1]]), - "SWAP": np.array([[1, 0, 0, 0], [0, 0, 1, 0], - [0, 1, 0, 0], [0, 0, 0, 1]]), - "TOFFOLI": np.array([[1, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 1], - [0, 0, 0, 0, 0, 0, 1, 0]]) -} @pytest.mark.parametrize("dtype", ["complex64", "complex128"]) -def test_matrices(engine, dtype): - if engine == "numpy": - mobj = matrices.NumpyMatrices(getattr(np, dtype)) - elif engine == "tensorflow": - import tensorflow as tf - mobj = matrices.TensorflowMatrices(getattr(tf, dtype)) - else: # pragma: no cover - # this case exists only for test consistency checking and - # should never execute - raise_error(ValueError, "Unknown engine {}.".format(engine)) - for matrixname, target in TARGET_MATRICES.items(): +def test_matrices(backend, dtype): + from qibo.backends.matrices import Matrices + original_backend = qibo.get_backend() + qibo.set_backend(backend) + mobj = Matrices(qibo.K) + target_matrices = { + "I": np.array([[1, 0], [0, 1]]), + "H": np.array([[1, 1], [1, -1]]) / np.sqrt(2), + "X": np.array([[0, 1], [1, 0]]), + "Y": np.array([[0, -1j], [1j, 0]]), + "Z": np.array([[1, 0], [0, -1]]), + "CNOT": np.array([[1, 0, 0, 0], [0, 1, 0, 0], + [0, 0, 0, 1], [0, 0, 1, 0]]), + "CZ": np.array([[1, 0, 0, 0], [0, 1, 0, 0], + [0, 0, 1, 0], [0, 0, 0, -1]]), + "SWAP": np.array([[1, 0, 0, 0], [0, 0, 1, 0], + [0, 1, 0, 0], [0, 0, 0, 1]]), + "TOFFOLI": np.array([[1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 1, 0]]) + } + for matrixname, target in target_matrices.items(): np.testing.assert_allclose(getattr(mobj, matrixname), target) + qibo.set_backend(original_backend) def test_modifying_matrices_error(): diff --git a/src/qibo/tests_new/test_models_variational.py b/src/qibo/tests_new/test_models_variational.py index 6a07dedbeb..ff410d5280 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 != "matmuleinsum": + if (method == "sgd" or compile) and backend != "tensorflow_matmuleinsum": 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 != "matmuleinsum": + if method == "sgd" and backend != "tensorflow_matmuleinsum": pytest.skip("Skipping SGD test for unsupported backend.") qibo.set_backend(backend) h = hamiltonians.XXZ(3, trotter=trotter) @@ -291,3 +291,40 @@ def test_qaoa_optimization(backend, method, options, trotter, filename): if filename is not None: assert_regression_fixture(params, filename) qibo.set_backend(original_backend) + + +test_names = "delta_t,max_layers,tolerance,filename" +test_values = [ + (0.1, 5, None, "falqon1.out"), + (0.01, 2, None, "falqon2.out"), + (0.01, 2, 1e-5, "falqon3.out"), + (0.01, 5, 1, "falqon4.out") + ] +@pytest.mark.parametrize(test_names, test_values) +def test_falqon_optimization(backend, delta_t, max_layers, tolerance, filename): + original_backend = qibo.get_backend() + qibo.set_backend(backend) + h = hamiltonians.XXZ(3) + falqon = models.FALQON(h) + best, params, extra = falqon.minimize(delta_t, max_layers, tol=tolerance) + if filename is not None: + assert_regression_fixture(params, filename) + qibo.set_backend(original_backend) + + +def test_falqon_optimization_callback(backend): + original_backend = qibo.get_backend() + qibo.set_backend(backend) + + class TestCallback: + from qibo import K + + def __call__(self, x): + return self.K.sum(x) + + callback = TestCallback() + h = hamiltonians.XXZ(3) + falqon = models.FALQON(h) + best, params, extra = falqon.minimize(0.1, 5, callback=callback) + assert len(extra["callbacks"]) == 5 + qibo.set_backend(original_backend)