diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index 9fd00e71df..3ca9e40f37 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -2595,12 +2595,11 @@ The main calculation engine is defined in the abstract backend object :class:`qibo.backends.abstract.Backend`. This object defines the methods required by all Qibo models to perform simulation. -Qibo currently provides two different calculation backends, one based on -numpy and one based on Tensorflow. It is possible to define new backends by -inheriting :class:`qibo.backends.abstract.Backend` and implementing -its abstract methods. +Qibo supports several backends (see the :ref:`Backend drivers section `), +which can be used depending on the specific needs: +lightweight simulation, quantum machine learning, hardware execution, etc. -An additional backend is shipped as the separate library qibojit. +Among them, the default choice is a backend provided by the qibojit library. This backend is supplemented by custom operators defined under which can be used to efficiently apply gates to state vectors or density matrices. diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 6ea78b1836..21e25e2465 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -554,7 +554,9 @@ refer to the :ref:`Optimizers ` section of the documentation. Note that if the Stochastic Gradient Descent optimizer is used then the user has to use a backend based on tensorflow or pytorch primitives and not the default custom backend, as custom operators currently do not support automatic differentiation. -To switch the backend one can do ``qibo.set_backend("tensorflow")`` or ``qibo.set_backend("pytorch")``. +To switch the backend one can do ``qibo.set_backend(backend="qiboml", platform="tensorflow")`` +or ``qibo.set_backend(backend="qiboml", platform="pytorch")``, after ensuring the +``qiboml`` package has been installed. Check the :ref:`How to use automatic differentiation? ` section for more details. @@ -800,7 +802,7 @@ using the ``pytorch`` framework. import torch from qibo import Circuit, gates, set_backend - set_backend("pytorch") + set_backend(backend="qiboml", platform="pytorch") # Optimization parameters nepochs = 1000 @@ -1314,9 +1316,9 @@ Let's see how to use them. For starters, let's define a dummy circuit with some # visualize the circuit circuit.draw() - # q0: ─RZ─RX─RZ─RX─RZ─o────o────────M─ - # q1: ─RZ─RX─RZ─RX─RZ─X─RZ─X─o────o─M─ - # q2: ─RZ─RX─RZ─RX─RZ────────X─RZ─X─M─ + # 0: ─RZ─RX─RZ─RX─RZ─o────o────────M─ + # 1: ─RZ─RX─RZ─RX─RZ─X─RZ─X─o────o─M─ + # 2: ─RZ─RX─RZ─RX─RZ────────X─RZ─X─M─ .. testoutput:: :hide: @@ -1432,12 +1434,12 @@ number of CNOT or RX pairs (depending on the value of ``insertion_gate``) insert circuit in correspondence to the original ones. Since we decided to simulate noisy CNOTs:: Level 1 - q0: ─X─ --> q0: ─X───X──X─ - q1: ─o─ --> q1: ─o───o──o─ + 0: ─X─ --> 0: ─X───X──X─ + 1: ─o─ --> 1: ─o───o──o─ Level 2 - q0: ─X─ --> q0: ─X───X──X───X──X─ - q1: ─o─ --> q1: ─o───o──o───o──o─ + 0: ─X─ --> 0: ─X───X──X───X──X─ + 1: ─o─ --> 1: ─o───o──o───o──o─ . . @@ -1501,8 +1503,10 @@ combined with the readout mitigation: Clifford Data Regression (CDR) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -For CDR instead, you don't need to define anything additional. However, keep in mind that the input -circuit is expected to be decomposed in the set of primitive gates :math:`RX(\frac{\pi}{2}), CNOT, X` and :math:`RZ(\theta)`. +For CDR instead, you don't need to define anything additional. +However, keep in mind that the input circuit is expected to be +decomposed in the set of primitive gates +:math:`RX(\frac{\pi}{2}), CNOT, X` and :math:`RZ(\theta)`. .. testcode:: @@ -1526,14 +1530,16 @@ circuit is expected to be decomposed in the set of primitive gates :math:`RX(\fr ... -Again, the mitigated expected value improves over the noisy one and is also slightly better compared to ZNE. +Again, the mitigated expected value improves over the noisy one +and is also slightly better compared to ZNE. Variable Noise CDR (vnCDR) ^^^^^^^^^^^^^^^^^^^^^^^^^^ -Being a combination of ZNE and CDR, vnCDR requires you to define the noise levels as done in ZNE, and the same -caveat about the input circuit for CDR is valid here as well. +Being a combination of ZNE and CDR, vnCDR requires you to define +the noise levels as done in ZNE, and the same caveat about the +input circuit for CDR is valid here as well. .. testcode:: @@ -1559,8 +1565,10 @@ caveat about the input circuit for CDR is valid here as well. ... -The result is similar to the one obtained by CDR. Usually, one would expect slightly better results for vnCDR, -however, this can substantially vary depending on the circuit and the observable considered and, therefore, it is hard to tell +The result is similar to the one obtained by CDR. +Usually, one would expect slightly better results for vnCDR. +However, this can substantially vary depending on the circuit +and the observable considered and, therefore, it is hard to tell a priori. @@ -1591,8 +1599,11 @@ The use of iCS is straightforward, analogous to CDR and vnCDR. ... -Again, the mitigated expected value improves over the noisy one and is also slightly better compared to ZNE. -This was just a basic example usage of the three methods, for all the details about them you should check the API-reference page :ref:`Error Mitigation `. +Again, the mitigated expected value improves over the noisy +one and is also slightly better compared to ZNE. +This was just a basic example usage of the three methods, +for all the details about them you should check the API-reference page +:ref:`Error Mitigation `. .. _timeevol-example: @@ -2139,8 +2150,6 @@ Qibo implements a built-in transpiler with customizable options for each step. T be used at each transpiler step are reported below with a short description. The initial placement can be found with one of the following procedures: -- Trivial: logical-physical qubit mapping is an identity. -- Custom: custom logical-physical qubit mapping. - Random greedy: the best mapping is found within a set of random layouts based on a greedy policy. - Subgraph isomorphism: the initial mapping is the one that guarantees the execution of most gates at the beginning of the circuit without introducing any SWAP. @@ -2168,22 +2177,22 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile from qibo import gates from qibo.models import Circuit - from qibo.transpiler.pipeline import Passes, assert_transpiling + from qibo.transpiler.pipeline import Passes from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.router import ShortestPaths from qibo.transpiler.unroller import Unroller, NativeGates from qibo.transpiler.placer import Random + from qibo.transpiler.asserts import assert_transpiling # Define connectivity as nx.Graph def star_connectivity(): - chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] - chip.add_edges_from(graph_list) + chip = nx.Graph([("q0", "q2"), ("q1", "q2"), ("q2", "q3"), ("q2", "q4")]) return chip # Define the circuit - circuit = Circuit(2) + # wire_names must match nodes in the connectivity graph. + # The index in wire_names represents the logical qubit number in the circuit. + circuit = Circuit(2, wire_names=["q0", "q1"]) circuit.add(gates.H(0)) circuit.add(gates.CZ(0, 1)) @@ -2205,13 +2214,10 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile transpiled_circ, final_layout = custom_pipeline(circuit) # Optinally call assert_transpiling to check that the final circuit can be executed on hardware - # For this test it is necessary to get the initial layout - initial_layout = custom_pipeline.get_initial_layout() assert_transpiling( original_circuit=circuit, transpiled_circuit=transpiled_circ, connectivity=star_connectivity(), - initial_layout=initial_layout, final_layout=final_layout, native_gates=NativeGates.default() ) diff --git a/doc/source/code-examples/examples.rst b/doc/source/code-examples/examples.rst index 8e3b849a38..e9d04eb7af 100644 --- a/doc/source/code-examples/examples.rst +++ b/doc/source/code-examples/examples.rst @@ -39,9 +39,9 @@ evaluation performance, e.g.: .. code-block:: python import numpy as np - # switch backend to "tensorflow" + # switch backend to "tensorflow" through the Qiboml provider import qibo - qibo.set_backend("tensorflow") + qibo.set_backend(backend="qiboml", platform="tensorflow") from qibo import Circuit, gates circuit = Circuit(2) @@ -54,7 +54,7 @@ evaluation performance, e.g.: init_state = np.ones(4) / 2.0 + i circuit(init_state) -Note that compiling is only supported when the native ``tensorflow`` backend is +Note that compiling is only supported when the ``tensorflow`` backend is used. This backend is much slower than ``qibojit`` which uses custom operators to apply gates. @@ -226,7 +226,7 @@ For applications that require the state vector to be collapsed during measuremen we refer to the :ref:`How to collapse state during measurements? ` The measured shots are obtained using pseudo-random number generators of the -underlying backend (numpy or Tensorflow). If the user has installed a custom +underlying backend. If the user has installed a custom backend (eg. qibojit) and asks for frequencies with more than 100000 shots, a custom Metropolis algorithm will be used to obtain the corresponding samples, for increase performance. The user can change the threshold for which this @@ -313,20 +313,20 @@ For example circuit.draw() # Prints ''' - q0: ─H─U1─U1─U1─U1───────────────────────────x─── - q1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─ - q2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─ - q3: ─────────o──|───────o──|────o──|──H─U1───|─x─ - q4: ────────────o──────────o───────o────o──H─x─── + 0: ─H─U1─U1─U1─U1───────────────────────────x─── + 1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─ + 2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─ + 3: ─────────o──|───────o──|────o──|──H─U1───|─x─ + 4: ────────────o──────────o───────o────o──H─x─── ''' .. testoutput:: :hide: - q0: ─H─U1─U1─U1─U1───────────────────────────x─── - q1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─ - q2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─ - q3: ─────────o──|───────o──|────o──|──H─U1───|─x─ - q4: ────────────o──────────o───────o────o──H─x─── + 0: ─H─U1─U1─U1─U1───────────────────────────x─── + 1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─ + 2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─ + 3: ─────────o──|───────o──|────o──|──H─U1───|─x─ + 4: ────────────o──────────o───────o────o──H─x─── How to visualize a circuit with style? -------------------------------------- diff --git a/doc/source/getting-started/installation.rst b/doc/source/getting-started/installation.rst index 8c356b2592..a6a9213a69 100644 --- a/doc/source/getting-started/installation.rst +++ b/doc/source/getting-started/installation.rst @@ -7,15 +7,15 @@ Operating systems support In the table below we summarize the status of *pre-compiled binaries distributed with pypi* for the packages listed above. -+------------------+------+---------+------------+ -| Operating System | qibo | qibojit | tensorflow | -+==================+======+=========+============+ -| Linux x86 | Yes | Yes | Yes | -+------------------+------+---------+------------+ -| MacOS >= 10.15 | Yes | Yes | Yes | -+------------------+------+---------+------------+ -| Windows | Yes | Yes | Yes | -+------------------+------+---------+------------+ ++------------------+------+---------+-----------+---------+ +| Operating System | qibo | qibojit |Tensorflow | Pytorch | ++==================+======+=========+===========+=========+ +| Linux x86 | Yes | Yes | Yes | Yes | ++------------------+------+---------+-----------+---------+ +| MacOS >= 10.15 | Yes | Yes | Yes | Yes | ++------------------+------+---------+-----------+---------+ +| Windows | Yes | Yes | Yes | Yes | ++------------------+------+---------+-----------+---------+ .. note:: All packages are supported for Python >= 3.9. @@ -148,35 +148,6 @@ Then proceed with the ``qibojit`` installation using ``pip`` _______________________ -.. _installing-tensorflow: - -tensorflow -^^^^^^^^^^ - -If the `TensorFlow `_ package is installed Qibo -will detect and provide to the user the possibility to use ``tensorflow`` -backend. - -This backend is used by default if ``qibojit`` is not installed, however, if -needed, in order to switch to the ``tensorflow`` backend please do: - -.. code-block:: python - - import qibo - qibo.set_backend("tensorflow") - -In order to install the package, we recommend the installation using: - -.. code-block:: bash - - pip install qibo tensorflow - -.. note:: - TensorFlow can be installed following its `documentation - `_. - -_______________________ - .. _installing-numpy: numpy @@ -197,26 +168,45 @@ please do: _______________________ -.. _installing-pytorch: +.. _installing-qml-backends: -pytorch -^^^^^^^ +Backends with automatic differentiation support +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you need automatic differentiation support, for tracing gradients of your +quantum algorithm or for building some quantum machine learning routine, +the right backends for you are those provided by the `Qiboml `__ +package. -If the `PyTorch `_ package is installed Qibo -will detect and provide to the user the possibility to use ``pytorch`` -backend. +In particular, Qiboml currently support `Pytorch `_ and +`Tensorflow `_ interfaces, integrating the qibo functionalities +into these well-known machine learning frameworks. Quantum layers can be constructed +and added to your Pytorch or Tensorflow models, and trained using any supported +optimization routine. -In order to switch to the ``pytorch`` backend please do: +In order to use these quantum machine learning backends please make sure the +preferred package is installed following `Tensorflow's `_ +or `Pytorch's `_ installation instructions. + +To switch to Tensorflow or Pytorch backend please do: .. code-block:: python import qibo - qibo.set_backend("pytorch") + # in case of Tensorflow + qibo.set_backend(backend="qiboml", platform="tensorflow") + # in case of Pytorch + qibo.set_backend(backend="qiboml", platform="pytorch") -In order to install the package, we recommend the installation using: +In order to start using automatic differentiation tools with Qibo, +we recommend the installation using: .. code-block:: bash - pip install qibo torch + pip install qibo qiboml tensorflow -_______________________ +or + +.. code-block:: bash + + pip install qibo qiboml torch diff --git a/poetry.lock b/poetry.lock index 0702b00d3e..f2b87cb108 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,16 +1,5 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. -[[package]] -name = "absl-py" -version = "2.1.0" -description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py." -optional = false -python-versions = ">=3.7" -files = [ - {file = "absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff"}, - {file = "absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308"}, -] - [[package]] name = "alabaster" version = "0.7.16" @@ -117,21 +106,6 @@ six = ">=1.12.0" astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] -[[package]] -name = "astunparse" -version = "1.6.3" -description = "An AST unparser for Python" -optional = false -python-versions = "*" -files = [ - {file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"}, - {file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"}, -] - -[package.dependencies] -six = ">=1.6.1,<2.0" -wheel = ">=0.23.0,<1.0" - [[package]] name = "attrs" version = "21.4.0" @@ -1407,17 +1381,6 @@ docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2. testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] typing = ["typing-extensions (>=4.12.2)"] -[[package]] -name = "flatbuffers" -version = "24.3.25" -description = "The FlatBuffers serialization format for Python" -optional = false -python-versions = "*" -files = [ - {file = "flatbuffers-24.3.25-py2.py3-none-any.whl", hash = "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812"}, - {file = "flatbuffers-24.3.25.tar.gz", hash = "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4"}, -] - [[package]] name = "fonttools" version = "4.55.0" @@ -1547,17 +1510,6 @@ pygments = ">=2.7" sphinx = ">=5.0,<7.0" sphinx-basic-ng = "*" -[[package]] -name = "gast" -version = "0.6.0" -description = "Python AST that abstracts the underlying Python version" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -files = [ - {file = "gast-0.6.0-py3-none-any.whl", hash = "sha256:52b182313f7330389f72b069ba00f174cfe2a06411099547288839c6cbafbd54"}, - {file = "gast-0.6.0.tar.gz", hash = "sha256:88fc5300d32c7ac6ca7b515310862f71e6fdf2c029bbec7c66c0f5dd47b6b1fb"}, -] - [[package]] name = "google-api-core" version = "2.23.0" @@ -1613,21 +1565,6 @@ pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0.dev0)"] -[[package]] -name = "google-pasta" -version = "0.2.0" -description = "pasta is an AST-based Python refactoring library" -optional = false -python-versions = "*" -files = [ - {file = "google-pasta-0.2.0.tar.gz", hash = "sha256:c9f2c8dfc8f96d0d5808299920721be30c9eec37f2389f28904f454565c8a16e"}, - {file = "google_pasta-0.2.0-py2-none-any.whl", hash = "sha256:4612951da876b1a10fe3960d7226f0c7682cf901e16ac06e473b267a5afa8954"}, - {file = "google_pasta-0.2.0-py3-none-any.whl", hash = "sha256:b32482794a366b5366a32c92a9a9201b107821889935a02b3e51f6b432ea84ed"}, -] - -[package.dependencies] -six = "*" - [[package]] name = "googleapis-common-protos" version = "1.66.0" @@ -1825,44 +1762,6 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] -[[package]] -name = "h5py" -version = "3.12.1" -description = "Read and write HDF5 files from Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "h5py-3.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f0f1a382cbf494679c07b4371f90c70391dedb027d517ac94fa2c05299dacda"}, - {file = "h5py-3.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb65f619dfbdd15e662423e8d257780f9a66677eae5b4b3fc9dca70b5fd2d2a3"}, - {file = "h5py-3.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b15d8dbd912c97541312c0e07438864d27dbca857c5ad634de68110c6beb1c2"}, - {file = "h5py-3.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59685fe40d8c1fbbee088c88cd4da415a2f8bee5c270337dc5a1c4aa634e3307"}, - {file = "h5py-3.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:577d618d6b6dea3da07d13cc903ef9634cde5596b13e832476dd861aaf651f3e"}, - {file = "h5py-3.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ccd9006d92232727d23f784795191bfd02294a4f2ba68708825cb1da39511a93"}, - {file = "h5py-3.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad8a76557880aed5234cfe7279805f4ab5ce16b17954606cca90d578d3e713ef"}, - {file = "h5py-3.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1473348139b885393125126258ae2d70753ef7e9cec8e7848434f385ae72069e"}, - {file = "h5py-3.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:018a4597f35092ae3fb28ee851fdc756d2b88c96336b8480e124ce1ac6fb9166"}, - {file = "h5py-3.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:3fdf95092d60e8130ba6ae0ef7a9bd4ade8edbe3569c13ebbaf39baefffc5ba4"}, - {file = "h5py-3.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06a903a4e4e9e3ebbc8b548959c3c2552ca2d70dac14fcfa650d9261c66939ed"}, - {file = "h5py-3.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b3b8f3b48717e46c6a790e3128d39c61ab595ae0a7237f06dfad6a3b51d5351"}, - {file = "h5py-3.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:050a4f2c9126054515169c49cb900949814987f0c7ae74c341b0c9f9b5056834"}, - {file = "h5py-3.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4b41d1019322a5afc5082864dfd6359f8935ecd37c11ac0029be78c5d112c9"}, - {file = "h5py-3.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4d51919110a030913201422fb07987db4338eba5ec8c5a15d6fab8e03d443fc"}, - {file = "h5py-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:513171e90ed92236fc2ca363ce7a2fc6f2827375efcbb0cc7fbdd7fe11fecafc"}, - {file = "h5py-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59400f88343b79655a242068a9c900001a34b63e3afb040bd7cdf717e440f653"}, - {file = "h5py-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e465aee0ec353949f0f46bf6c6f9790a2006af896cee7c178a8c3e5090aa32"}, - {file = "h5py-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba51c0c5e029bb5420a343586ff79d56e7455d496d18a30309616fdbeed1068f"}, - {file = "h5py-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:52ab036c6c97055b85b2a242cb540ff9590bacfda0c03dd0cf0661b311f522f8"}, - {file = "h5py-3.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2b8dd64f127d8b324f5d2cd1c0fd6f68af69084e9e47d27efeb9e28e685af3e"}, - {file = "h5py-3.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4532c7e97fbef3d029735db8b6f5bf01222d9ece41e309b20d63cfaae2fb5c4d"}, - {file = "h5py-3.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdf6d7936fa824acfa27305fe2d9f39968e539d831c5bae0e0d83ed521ad1ac"}, - {file = "h5py-3.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84342bffd1f82d4f036433e7039e241a243531a1d3acd7341b35ae58cdab05bf"}, - {file = "h5py-3.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:62be1fc0ef195891949b2c627ec06bc8e837ff62d5b911b6e42e38e0f20a897d"}, - {file = "h5py-3.12.1.tar.gz", hash = "sha256:326d70b53d31baa61f00b8aa5f95c2fcb9621a3ee8365d770c551a13dbbcbfdf"}, -] - -[package.dependencies] -numpy = ">=1.19.3" - [[package]] name = "httpcore" version = "0.16.3" @@ -2321,27 +2220,6 @@ files = [ {file = "jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed"}, ] -[[package]] -name = "keras" -version = "3.6.0" -description = "Multi-backend Keras." -optional = false -python-versions = ">=3.9" -files = [ - {file = "keras-3.6.0-py3-none-any.whl", hash = "sha256:49585e4577f6e86bd890d96dfbcb1890f5bab5967ef831c07fd63f9d86e4bfe9"}, - {file = "keras-3.6.0.tar.gz", hash = "sha256:405727525a3522ed8f9ec0b46e0667e4c65fcf714a067322c16a00d902ded41d"}, -] - -[package.dependencies] -absl-py = "*" -h5py = "*" -ml-dtypes = "*" -namex = "*" -numpy = "*" -optree = "*" -packaging = "*" -rich = "*" - [[package]] name = "kiwisolver" version = "1.4.7" @@ -2492,6 +2370,7 @@ files = [ ] [[package]] +<<<<<<< HEAD name = "libclang" version = "18.1.1" description = "Clang Python Bindings, mirrored from the official LLVM repo: https://github.com/llvm/llvm-project/tree/main/clang/bindings/python, to make the installation process easier." @@ -2511,6 +2390,8 @@ files = [ ] [[package]] +======= +>>>>>>> master name = "llvmlite" version = "0.43.0" description = "lightweight wrapper around basic LLVM functionality" @@ -2577,30 +2458,6 @@ importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - [[package]] name = "markupsafe" version = "3.0.2" @@ -2760,17 +2617,6 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - [[package]] name = "mistune" version = "3.0.2" @@ -2802,34 +2648,38 @@ tbb = "==2021.*" [[package]] name = "ml-dtypes" -version = "0.4.1" +version = "0.5.0" description = "" optional = false python-versions = ">=3.9" files = [ - {file = "ml_dtypes-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1fe8b5b5e70cd67211db94b05cfd58dace592f24489b038dc6f9fe347d2e07d5"}, - {file = "ml_dtypes-0.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c09a6d11d8475c2a9fd2bc0695628aec105f97cab3b3a3fb7c9660348ff7d24"}, - {file = "ml_dtypes-0.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5e8f75fa371020dd30f9196e7d73babae2abd51cf59bdd56cb4f8de7e13354"}, - {file = "ml_dtypes-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:15fdd922fea57e493844e5abb930b9c0bd0af217d9edd3724479fc3d7ce70e3f"}, - {file = "ml_dtypes-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d55b588116a7085d6e074cf0cdb1d6fa3875c059dddc4d2c94a4cc81c23e975"}, - {file = "ml_dtypes-0.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e138a9b7a48079c900ea969341a5754019a1ad17ae27ee330f7ebf43f23877f9"}, - {file = "ml_dtypes-0.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74c6cfb5cf78535b103fde9ea3ded8e9f16f75bc07789054edc7776abfb3d752"}, - {file = "ml_dtypes-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:274cc7193dd73b35fb26bef6c5d40ae3eb258359ee71cd82f6e96a8c948bdaa6"}, - {file = "ml_dtypes-0.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:827d3ca2097085cf0355f8fdf092b888890bb1b1455f52801a2d7756f056f54b"}, - {file = "ml_dtypes-0.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:772426b08a6172a891274d581ce58ea2789cc8abc1c002a27223f314aaf894e7"}, - {file = "ml_dtypes-0.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:126e7d679b8676d1a958f2651949fbfa182832c3cd08020d8facd94e4114f3e9"}, - {file = "ml_dtypes-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:df0fb650d5c582a9e72bb5bd96cfebb2cdb889d89daff621c8fbc60295eba66c"}, - {file = "ml_dtypes-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e35e486e97aee577d0890bc3bd9e9f9eece50c08c163304008587ec8cfe7575b"}, - {file = "ml_dtypes-0.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:560be16dc1e3bdf7c087eb727e2cf9c0e6a3d87e9f415079d2491cc419b3ebf5"}, - {file = "ml_dtypes-0.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad0b757d445a20df39035c4cdeed457ec8b60d236020d2560dbc25887533cf50"}, - {file = "ml_dtypes-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:ef0d7e3fece227b49b544fa69e50e607ac20948f0043e9f76b44f35f229ea450"}, - {file = "ml_dtypes-0.4.1.tar.gz", hash = "sha256:fad5f2de464fd09127e49b7fd1252b9006fb43d2edc1ff112d390c324af5ca7a"}, + {file = "ml_dtypes-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c32138975797e681eb175996d64356bcfa124bdbb6a70460b9768c2b35a6fa4"}, + {file = "ml_dtypes-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab046f2ff789b1f11b2491909682c5d089934835f9a760fafc180e47dcb676b8"}, + {file = "ml_dtypes-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7a9152f5876fef565516aa5dd1dccd6fc298a5891b2467973905103eb5c7856"}, + {file = "ml_dtypes-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:968fede07d1f9b926a63df97d25ac656cac1a57ebd33701734eaf704bc55d8d8"}, + {file = "ml_dtypes-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60275f2b51b56834e840c4809fca840565f9bf8e9a73f6d8c94f5b5935701215"}, + {file = "ml_dtypes-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76942f6aeb5c40766d5ea62386daa4148e6a54322aaf5b53eae9e7553240222f"}, + {file = "ml_dtypes-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e7534392682c3098bc7341648c650864207169c654aed83143d7a19c67ae06f"}, + {file = "ml_dtypes-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:dc74fd9995513d33eac63d64e436240f5494ec74d522a9f0920194942fc3d2d7"}, + {file = "ml_dtypes-0.5.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d4b1a70a3e5219790d6b55b9507606fc4e02911d1497d16c18dd721eb7efe7d0"}, + {file = "ml_dtypes-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a988bac6572630e1e9c2edd9b1277b4eefd1c86209e52b0d061b775ac33902ff"}, + {file = "ml_dtypes-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a38df8df61194aeaae1ab7579075779b4ad32cd1cffd012c28be227fa7f2a70a"}, + {file = "ml_dtypes-0.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:afa08343069874a30812871d639f9c02b4158ace065601406a493a8511180c02"}, + {file = "ml_dtypes-0.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d3b3db9990c3840986a0e70524e122cfa32b91139c3653df76121ba7776e015f"}, + {file = "ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04fde367b2fe901b1d47234426fe8819909bd1dd862a5adb630f27789c20599"}, + {file = "ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54415257f00eb44fbcc807454efac3356f75644f1cbfc2d4e5522a72ae1dacab"}, + {file = "ml_dtypes-0.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:cb5cc7b25acabd384f75bbd78892d0c724943f3e2e1986254665a1aa10982e07"}, + {file = "ml_dtypes-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f2b59233a0dbb6a560b3137ed6125433289ccba2f8d9c3695a52423a369ed15"}, + {file = "ml_dtypes-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:099e09edd54e676903b4538f3815b5ab96f5b119690514602d96bfdb67172cbe"}, + {file = "ml_dtypes-0.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a03fc861b86cc586728e3d093ba37f0cc05e65330c3ebd7688e7bae8290f8859"}, + {file = "ml_dtypes-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ee9c320bb0f9ffdf9f6fa6a696ef2e005d1f66438d6f1c1457338e00a02e8cf"}, + {file = "ml_dtypes-0.5.0.tar.gz", hash = "sha256:3e7d3a380fe73a63c884f06136f8baa7a5249cc8e9fdec677997dd78549f8128"}, ] [package.dependencies] numpy = [ {version = ">=1.21.2", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, - {version = ">1.20", markers = "python_version < \"3.10\""}, + {version = ">=1.21", markers = "python_version < \"3.10\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.23.3", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] @@ -2927,17 +2777,6 @@ files = [ {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, ] -[[package]] -name = "namex" -version = "0.0.8" -description = "A simple utility to separate the implementation of your Python package and its public API surface." -optional = false -python-versions = "*" -files = [ - {file = "namex-0.0.8-py3-none-any.whl", hash = "sha256:7ddb6c2bb0e753a311b7590f84f6da659dd0c05e65cb89d519d54c0a250c0487"}, - {file = "namex-0.0.8.tar.gz", hash = "sha256:32a50f6c565c0bb10aa76298c959507abdc0e850efe085dc38f3440fcb3aa90b"}, -] - [[package]] name = "nbclient" version = "0.10.0" @@ -3321,6 +3160,7 @@ files = [ ] [[package]] +<<<<<<< HEAD name = "optree" version = "0.13.1" description = "Optimized PyTree Utilities." @@ -3430,6 +3270,8 @@ test = ["pytest", "pytest-cov", "pytest-xdist"] torch = ["torch"] [[package]] +======= +>>>>>>> master name = "optuna" version = "4.1.0" description = "A hyperparameter optimization framework" @@ -4535,7 +4377,11 @@ develop = false [package.dependencies] numba = ">=0.59.0" psutil = "^5.9.5" +<<<<<<< HEAD qibo = {git = "https://github.com/qiboteam/qibo.git", branch = "rename_methods"} +======= +qibo = "^0.2.13" +>>>>>>> master scipy = "^1.10.1" [package.extras] @@ -4545,8 +4391,13 @@ cuquantum = ["cuquantum-python-cu12 (>=23.10.0,<24.0.0)"] [package.source] type = "git" url = "https://github.com/qiboteam/qibojit.git" +<<<<<<< HEAD reference = "rename_methods" resolved_reference = "d78c07f588eda5371ae83d119176e0157a1b69d8" +======= +reference = "HEAD" +resolved_reference = "290b1776c2ba272f1c9eb59cba1d31964a239f2a" +>>>>>>> master [[package]] name = "qiboml" @@ -4740,6 +4591,7 @@ idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} idna2008 = ["idna"] [[package]] +<<<<<<< HEAD name = "rich" version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" @@ -4759,6 +4611,8 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.1 jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] +======= +>>>>>>> master name = "rpcq" version = "3.11.0" description = "The RPC framework and message specification for Rigetti QCS." @@ -4965,6 +4819,7 @@ docs = ["ipykernel", "nbconvert", "numpydoc", "pydata_sphinx_theme (==0.10.0rc2) stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] [[package]] +<<<<<<< HEAD name = "setuptools" version = "75.5.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" @@ -4985,6 +4840,8 @@ test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>= type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] [[package]] +======= +>>>>>>> master name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" @@ -5451,138 +5308,6 @@ files = [ doc = ["reno", "sphinx"] test = ["pytest", "tornado (>=4.5)", "typeguard"] -[[package]] -name = "tensorboard" -version = "2.18.0" -description = "TensorBoard lets you watch Tensors Flow" -optional = false -python-versions = ">=3.9" -files = [ - {file = "tensorboard-2.18.0-py3-none-any.whl", hash = "sha256:107ca4821745f73e2aefa02c50ff70a9b694f39f790b11e6f682f7d326745eab"}, -] - -[package.dependencies] -absl-py = ">=0.4" -grpcio = ">=1.48.2" -markdown = ">=2.6.8" -numpy = ">=1.12.0" -packaging = "*" -protobuf = ">=3.19.6,<4.24.0 || >4.24.0" -setuptools = ">=41.0.0" -six = ">1.9" -tensorboard-data-server = ">=0.7.0,<0.8.0" -werkzeug = ">=1.0.1" - -[[package]] -name = "tensorboard-data-server" -version = "0.7.2" -description = "Fast data loading for TensorBoard" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb"}, - {file = "tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60"}, - {file = "tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530"}, -] - -[[package]] -name = "tensorflow" -version = "2.18.0" -description = "TensorFlow is an open source machine learning framework for everyone." -optional = false -python-versions = ">=3.9" -files = [ - {file = "tensorflow-2.18.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:8da90a9388a1f6dd00d626590d2b5810faffbb3e7367f9783d80efff882340ee"}, - {file = "tensorflow-2.18.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:589342fb9bdcab2e9af0f946da4ca97757677e297d934fcdc087e87db99d6353"}, - {file = "tensorflow-2.18.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eb77fae50d699442726d1b23c7512c97cd688cc7d857b028683d4535bbf3709"}, - {file = "tensorflow-2.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:46f5a8b4e6273f488dc069fc3ac2211b23acd3d0437d919349c787fa341baa8a"}, - {file = "tensorflow-2.18.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:453cb60638a02fd26316fb36c8cbcf1569d33671f17c658ca0cf2b4626f851e7"}, - {file = "tensorflow-2.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85f1e7369af6d329b117b52e86093cd1e0458dd5404bf5b665853f873dd00b48"}, - {file = "tensorflow-2.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b8dd70fa3600bfce66ab529eebb804e1f9d7c863d2f71bc8fe9fc7a1ec3976"}, - {file = "tensorflow-2.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e8b0f499ef0b7652480a58e358a73844932047f21c42c56f7f3bdcaf0803edc"}, - {file = "tensorflow-2.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ec4133a215c59314e929e7cbe914579d3afbc7874d9fa924873ee633fe4f71d0"}, - {file = "tensorflow-2.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4822904b3559d8a9c25f0fe5fef191cfc1352ceca42ca64f2a7bc7ae0ff4a1f5"}, - {file = "tensorflow-2.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfdd65ea7e064064283dd78d529dd621257ee617218f63681935fd15817c6286"}, - {file = "tensorflow-2.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:a701c2d3dca5f2efcab315b2c217f140ebd3da80410744e87d77016b3aaf53cb"}, - {file = "tensorflow-2.18.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:336cace378c129c20fee6292f6a541165073d153a9a4c9cf4f14478a81895776"}, - {file = "tensorflow-2.18.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfd32134de8f95515b2d0ced89cdae15484b787d3a21893e9291def06c10c4e"}, - {file = "tensorflow-2.18.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada1f7290c75b34748ee7378c1b77927e4044c94b8dc72dc75e7667c4fdaeb94"}, - {file = "tensorflow-2.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8c946df1cb384504578fac1c199a95322373b8e04abd88aa8ae01301df469ea"}, -] - -[package.dependencies] -absl-py = ">=1.0.0" -astunparse = ">=1.6.0" -flatbuffers = ">=24.3.25" -gast = ">=0.2.1,<0.5.0 || >0.5.0,<0.5.1 || >0.5.1,<0.5.2 || >0.5.2" -google-pasta = ">=0.1.1" -grpcio = ">=1.24.3,<2.0" -h5py = ">=3.11.0" -keras = ">=3.5.0" -libclang = ">=13.0.0" -ml-dtypes = ">=0.4.0,<0.5.0" -numpy = ">=1.26.0,<2.1.0" -opt-einsum = ">=2.3.2" -packaging = "*" -protobuf = ">=3.20.3,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" -requests = ">=2.21.0,<3" -setuptools = "*" -six = ">=1.12.0" -tensorboard = ">=2.18,<2.19" -tensorflow-io-gcs-filesystem = {version = ">=0.23.1", markers = "python_version < \"3.12\""} -termcolor = ">=1.1.0" -typing-extensions = ">=3.6.6" -wrapt = ">=1.11.0" - -[package.extras] -and-cuda = ["nvidia-cublas-cu12 (==12.5.3.2)", "nvidia-cuda-cupti-cu12 (==12.5.82)", "nvidia-cuda-nvcc-cu12 (==12.5.82)", "nvidia-cuda-nvrtc-cu12 (==12.5.82)", "nvidia-cuda-runtime-cu12 (==12.5.82)", "nvidia-cudnn-cu12 (==9.3.0.75)", "nvidia-cufft-cu12 (==11.2.3.61)", "nvidia-curand-cu12 (==10.3.6.82)", "nvidia-cusolver-cu12 (==11.6.3.83)", "nvidia-cusparse-cu12 (==12.5.1.3)", "nvidia-nccl-cu12 (==2.21.5)", "nvidia-nvjitlink-cu12 (==12.5.82)"] - -[[package]] -name = "tensorflow-io-gcs-filesystem" -version = "0.37.1" -description = "TensorFlow IO" -optional = false -python-versions = "<3.13,>=3.7" -files = [ - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:249c12b830165841411ba71e08215d0e94277a49c551e6dd5d72aab54fe5491b"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:257aab23470a0796978efc9c2bcf8b0bc80f22e6298612a4c0a50d3f4e88060c"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8febbfcc67c61e542a5ac1a98c7c20a91a5e1afc2e14b1ef0cb7c28bc3b6aa70"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9679b36e3a80921876f31685ab6f7270f3411a4cc51bc2847e80d0e4b5291e27"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:32c50ab4e29a23c1f91cd0f9ab8c381a0ab10f45ef5c5252e94965916041737c"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b02f9c5f94fd62773954a04f69b68c4d576d076fd0db4ca25d5479f0fbfcdbad"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e1f2796b57e799a8ca1b75bf47c2aaa437c968408cc1a402a9862929e104cda"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee7c8ee5fe2fd8cb6392669ef16e71841133041fee8a330eff519ad9b36e4556"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:ffebb6666a7bfc28005f4fbbb111a455b5e7d6cd3b12752b7050863ecb27d5cc"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fe8dcc6d222258a080ac3dfcaaaa347325ce36a7a046277f6b3e19abc1efb3c5"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fbb33f1745f218464a59cecd9a18e32ca927b0f4d77abd8f8671b645cc1a182f"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:286389a203a5aee1a4fa2e53718c661091aa5fea797ff4fa6715ab8436b02e6c"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:ee5da49019670ed364f3e5fb86b46420841a6c3cb52a300553c63841671b3e6d"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8943036bbf84e7a2be3705cb56f9c9df7c48c9e614bb941f0936c58e3ca89d6f"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:426de1173cb81fbd62becec2012fc00322a295326d90eb6c737fab636f182aed"}, - {file = "tensorflow_io_gcs_filesystem-0.37.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df00891669390078a003cedbdd3b8e645c718b111917535fa1d7725e95cdb95"}, -] - -[package.extras] -tensorflow = ["tensorflow (>=2.16.0,<2.17.0)"] -tensorflow-aarch64 = ["tensorflow-aarch64 (>=2.16.0,<2.17.0)"] -tensorflow-cpu = ["tensorflow-cpu (>=2.16.0,<2.17.0)"] -tensorflow-gpu = ["tensorflow-gpu (>=2.16.0,<2.17.0)"] -tensorflow-rocm = ["tensorflow-rocm (>=2.16.0,<2.17.0)"] - -[[package]] -name = "termcolor" -version = "2.5.0" -description = "ANSI color formatting for output in terminal" -optional = false -python-versions = ">=3.9" -files = [ - {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, - {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, -] - -[package.extras] -tests = ["pytest", "pytest-cov"] - [[package]] name = "threadpoolctl" version = "3.5.0" @@ -5884,6 +5609,7 @@ files = [ ] [[package]] +<<<<<<< HEAD name = "werkzeug" version = "3.1.3" description = "The comprehensive WSGI web application library." @@ -5915,6 +5641,8 @@ files = [ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [[package]] +======= +>>>>>>> master name = "widgetsnbextension" version = "4.0.13" description = "Jupyter interactive widgets for Jupyter Notebook" diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index 91902da7d5..73b139c5d9 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -8,10 +8,9 @@ from qibo.backends.clifford import CliffordBackend from qibo.backends.npmatrices import NumpyMatrices from qibo.backends.numpy import NumpyBackend -from qibo.backends.pytorch import PyTorchBackend from qibo.config import log, raise_error -QIBO_NATIVE_BACKENDS = ("numpy", "pytorch", "qulacs") +QIBO_NATIVE_BACKENDS = ("numpy", "qulacs") class MissingBackend(ValueError): @@ -40,9 +39,6 @@ def load(backend: str, **kwargs) -> Backend: + f"The native qibo backends are {QIBO_NATIVE_BACKENDS + ('clifford',)}", ) - if backend == "pytorch": - return PyTorchBackend() - if backend == "clifford": engine = kwargs.pop("platform", None) kwargs["engine"] = engine @@ -80,7 +76,7 @@ class _Global: {"backend": "qibojit", "platform": "numba"}, {"backend": "numpy"}, {"backend": "qiboml", "platform": "tensorflow"}, - {"backend": "pytorch"}, + {"backend": "qiboml", "platform": "pytorch"}, ] @classmethod @@ -136,7 +132,6 @@ def set_transpiler(cls, transpiler): def _default_transpiler(cls): from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.pipeline import Passes - from qibo.transpiler.placer import Trivial from qibo.transpiler.router import Sabre from qibo.transpiler.unroller import NativeGates, Unroller @@ -148,21 +143,12 @@ def _default_transpiler(cls): and natives is not None and connectivity_edges is not None ): - # only for q{i} naming - node_mapping = {q: i for i, q in enumerate(qubits)} - edges = [ - (node_mapping[e[0]], node_mapping[e[1]]) for e in connectivity_edges - ] - connectivity = nx.Graph() - connectivity.add_nodes_from(list(node_mapping.values())) - connectivity.add_edges_from(edges) - + connectivity = nx.Graph(connectivity_edges) return Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Trivial(connectivity), - Sabre(connectivity), + Preprocessing(), + Sabre(), Unroller(NativeGates[natives]), ], ) diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py deleted file mode 100644 index 48404248d2..0000000000 --- a/src/qibo/backends/pytorch.py +++ /dev/null @@ -1,281 +0,0 @@ -"""PyTorch backend.""" - -from typing import Union - -import numpy as np - -from qibo import __version__ -from qibo.backends.npmatrices import NumpyMatrices -from qibo.backends.numpy import NumpyBackend - - -class TorchMatrices(NumpyMatrices): - """Matrix representation of every gate as a torch Tensor. - - Args: - dtype (torch.dtype): Data type of the matrices. - """ - - def __init__(self, dtype): - import torch # pylint: disable=import-outside-toplevel # type: ignore - - super().__init__(dtype) - self.np = torch - self.dtype = dtype - - def _cast(self, x, dtype): - flattened = [item for sublist in x for item in sublist] - tensor_list = [self.np.as_tensor(i, dtype=dtype) for i in flattened] - return self.np.stack(tensor_list).reshape(len(x), len(x)) - - def Unitary(self, u): - return self._cast(u, dtype=self.dtype) - - -class PyTorchBackend(NumpyBackend): - def __init__(self): - super().__init__() - import torch # pylint: disable=import-outside-toplevel # type: ignore - - self.np = torch - - self.name = "pytorch" - self.versions = { - "qibo": __version__, - "numpy": np.__version__, - "torch": self.np.__version__, - } - - # Default data type used for the gate matrices is complex128 - self.dtype = self._torch_dtype(self.dtype) - # Default data type used for the real gate parameters is float64 - self.parameter_dtype = self._torch_dtype("float64") - self.matrices = TorchMatrices(self.dtype) - self.device = self.np.device("cuda:0" if torch.cuda.is_available() else "cpu") - self.nthreads = 0 - self.tensor_types = (self.np.Tensor, np.ndarray) - - # These functions in Torch works in a different way than numpy or have different names - self.np.transpose = self.np.permute - self.np.copy = self.np.clone - self.np.power = self.np.pow - self.np.expand_dims = self.np.unsqueeze - self.np.mod = self.np.remainder - self.np.right_shift = self.np.bitwise_right_shift - self.np.sign = self.np.sgn - self.np.flatnonzero = lambda x: self.np.nonzero(x).flatten() - - def _torch_dtype(self, dtype): - if dtype == "float": - dtype += "32" - return getattr(self.np, dtype) - - def set_device(self, device): # pragma: no cover - self.device = device - - def cast( - self, - x, - dtype=None, - copy: bool = False, - ): - """Casts input as a Torch tensor of the specified dtype. - - This method supports casting of single tensors or lists of tensors - as for the :class:`qibo.backends.PyTorchBackend`. - - Args: - x (Union[torch.Tensor, list[torch.Tensor], np.ndarray, list[np.ndarray], int, float, complex]): - Input to be casted. - dtype (Union[str, torch.dtype, np.dtype, type]): Target data type. - If ``None``, the default dtype of the backend is used. - Defaults to ``None``. - copy (bool, optional): If ``True``, the input tensor is copied before casting. - Defaults to ``False``. - """ - - if dtype is None: - dtype = self.dtype - elif isinstance(dtype, type): - dtype = self._torch_dtype(dtype.__name__) - elif not isinstance(dtype, self.np.dtype): - dtype = self._torch_dtype(str(dtype)) - - if isinstance(x, self.np.Tensor): - x = x.to(dtype) - elif ( - isinstance(x, list) - and len(x) > 0 - and all(isinstance(row, self.np.Tensor) for row in x) - ): - x = self.np.stack(x) - else: - x = self.np.tensor(x, dtype=dtype) - - if copy: - return x.clone() - return x - - def matrix_parametrized(self, gate): - """Convert a parametrized gate to its matrix representation in the computational basis.""" - name = gate.__class__.__name__ - _matrix = getattr(self.matrices, name) - if name == "GeneralizedRBS": - for parameter in ["theta", "phi"]: - if not isinstance(gate.init_kwargs[parameter], self.np.Tensor): - gate.init_kwargs[parameter] = self._cast_parameter( - gate.init_kwargs[parameter], trainable=gate.trainable - ) - - _matrix = _matrix( - qubits_in=gate.init_args[0], - qubits_out=gate.init_args[1], - theta=gate.init_kwargs["theta"], - phi=gate.init_kwargs["phi"], - ) - return _matrix - else: - new_parameters = [] - for parameter in gate.parameters: - if not isinstance(parameter, self.np.Tensor): - parameter = self._cast_parameter( - parameter, trainable=gate.trainable - ) - elif parameter.requires_grad: - gate.trainable = True - new_parameters.append(parameter) - gate.parameters = tuple(new_parameters) - _matrix = _matrix(*gate.parameters) - return _matrix - - def _cast_parameter(self, x, trainable): - """Cast a gate parameter to a torch tensor. - - Args: - x (Union[int, float, complex]): Parameter to be casted. - trainable (bool): If ``True``, the tensor requires gradient. - """ - if isinstance(x, int) and trainable: - return self.np.tensor(x, dtype=self.parameter_dtype, requires_grad=True) - if isinstance(x, float): - return self.np.tensor( - x, dtype=self.parameter_dtype, requires_grad=trainable - ) - return self.np.tensor(x, dtype=self.dtype, requires_grad=trainable) - - def is_sparse(self, x): - if isinstance(x, self.np.Tensor): - return x.is_sparse - - return super().is_sparse(x) - - def to_numpy(self, x): - if isinstance(x, list): - return np.asarray([self.to_numpy(i) for i in x]) - - if isinstance(x, self.np.Tensor): - return x.numpy(force=True) - - return x - - def _order_probabilities(self, probs, qubits, nqubits): - """Arrange probabilities according to the given ``qubits`` ordering.""" - if probs.dim() == 0: # pragma: no cover - return probs - unmeasured, reduced = [], {} - for i in range(nqubits): - if i in qubits: - reduced[i] = i - len(unmeasured) - else: - unmeasured.append(i) - return self.np.transpose(probs, [reduced.get(i) for i in qubits]) - - def calculate_probabilities(self, state, qubits, nqubits): - rtype = self.np.real(state).dtype - unmeasured_qubits = tuple(i for i in range(nqubits) if i not in qubits) - state = self.np.reshape(self.np.abs(state) ** 2, nqubits * (2,)) - if len(unmeasured_qubits) == 0: - probs = self.cast(state, dtype=rtype) - else: - probs = self.np.sum(self.cast(state, dtype=rtype), axis=unmeasured_qubits) - return self._order_probabilities(probs, qubits, nqubits).ravel() - - def set_seed(self, seed): - self.np.manual_seed(seed) - np.random.seed(seed) - - def sample_shots(self, probabilities, nshots): - return self.np.multinomial( - self.cast(probabilities, dtype="float"), nshots, replacement=True - ) - - def calculate_eigenvalues(self, matrix, k: int = 6, hermitian: bool = True): - if hermitian: - return self.np.linalg.eigvalsh(matrix) # pylint: disable=not-callable - return self.np.linalg.eigvals(matrix) # pylint: disable=not-callable - - def calculate_eigenvectors(self, matrix, k: int = 6, hermitian: int = True): - if hermitian: - return self.np.linalg.eigh(matrix) # pylint: disable=not-callable - return self.np.linalg.eig(matrix) # pylint: disable=not-callable - - def calculate_matrix_exp(self, a, matrix, eigenvectors=None, eigenvalues=None): - if eigenvectors is None or self.is_sparse(matrix): - return self.np.linalg.matrix_exp( # pylint: disable=not-callable - -1j * a * matrix - ) - expd = self.np.diag(self.np.exp(-1j * a * eigenvalues)) - ud = self.np.conj(eigenvectors).T - return self.np.matmul(eigenvectors, self.np.matmul(expd, ud)) - - def calculate_matrix_power( - self, - matrix, - power: Union[float, int], - precision_singularity: float = 1e-14, - ): - copied = self.cast(matrix, copy=True) - copied = self.to_numpy(copied) if power >= 0.0 else copied.detach() - copied = super().calculate_matrix_power(copied, power, precision_singularity) - return self.cast(copied, dtype=copied.dtype) - - def calculate_jacobian_matrix( - self, circuit, parameters=None, initial_state=None, return_complex: bool = True - ): - copied = circuit.copy(deep=True) - - def func(parameters): - """torch requires object(s) to be wrapped in a function.""" - copied.set_parameters(parameters) - state = self.execute_circuit(copied, initial_state=initial_state).state() - if return_complex: - return self.np.real(state), self.np.imag(state) - return self.np.real(state) - - return self.np.autograd.functional.jacobian(func, parameters) - - def _test_regressions(self, name): - if name == "test_measurementresult_apply_bitflips": - return [ - [0, 0, 0, 0, 2, 3, 0, 0, 0, 0], - [0, 0, 0, 0, 2, 3, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 2, 0, 0, 0, 0, 0], - ] - - if name == "test_probabilistic_measurement": - if self.device == "cuda": # pragma: no cover - return {0: 273, 1: 233, 2: 242, 3: 252} - return {1: 270, 2: 248, 3: 244, 0: 238} - - if name == "test_unbalanced_probabilistic_measurement": - if self.device == "cuda": # pragma: no cover - return {0: 196, 1: 153, 2: 156, 3: 495} - return {3: 492, 2: 176, 0: 168, 1: 164} - - if name == "test_post_measurement_bitflips_on_circuit": - return [ - {5: 30}, - {5: 17, 4: 5, 7: 4, 1: 2, 6: 2}, - {4: 9, 2: 5, 5: 5, 3: 4, 6: 4, 0: 1, 1: 1, 7: 1}, - ] diff --git a/src/qibo/gates/abstract.py b/src/qibo/gates/abstract.py index 7b24407048..2128f9dc16 100644 --- a/src/qibo/gates/abstract.py +++ b/src/qibo/gates/abstract.py @@ -273,10 +273,10 @@ def on_qubits(self, qubit_map) -> "Gate": circuit.draw() .. testoutput:: - q0: ───X───── - q1: ───|─o─X─ - q2: ─o─|─|─o─ - q3: ─X─o─X─── + 0: ───X───── + 1: ───|─o─X─ + 2: ─o─|─|─o─ + 3: ─X─o─X─── """ if self.is_controlled_by: targets = (qubit_map.get(q) for q in self.target_qubits) diff --git a/src/qibo/gates/measurements.py b/src/qibo/gates/measurements.py index 0e2ccec754..b9c075ce9e 100644 --- a/src/qibo/gates/measurements.py +++ b/src/qibo/gates/measurements.py @@ -252,9 +252,9 @@ def on_qubits(self, qubit_map) -> "Gate": circuit.draw() .. testoutput:: - q0: ─M─ - q1: ─|─ - q2: ─M─ + 0: ─M─ + 1: ─|─ + 2: ─M─ """ qubits = (qubit_map.get(q) for q in self.qubits) diff --git a/src/qibo/hamiltonians/hamiltonians.py b/src/qibo/hamiltonians/hamiltonians.py index 7bf98865bf..0016ddb05b 100644 --- a/src/qibo/hamiltonians/hamiltonians.py +++ b/src/qibo/hamiltonians/hamiltonians.py @@ -7,7 +7,6 @@ import numpy as np import sympy -from qibo.backends import PyTorchBackend, _check_backend from qibo.config import EINSUM_CHARS, log, raise_error from qibo.hamiltonians.abstract import AbstractHamiltonian from qibo.symbols import Z @@ -266,7 +265,7 @@ def __mul__(self, o): if self.backend.np.real(o) >= 0: # TODO: check for side effects K.qnp r._eigenvalues = o * self._eigenvalues elif not self.backend.is_sparse(self.matrix): - axis = (0,) if isinstance(self.backend, PyTorchBackend) else 0 + axis = (0,) if (self.backend.platform == "pytorch") else 0 r._eigenvalues = o * self.backend.np.flip(self._eigenvalues, axis) if self._eigenvectors is not None: if self.backend.np.real(o) > 0: # TODO: see above diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 4e65259719..31f8570d88 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -119,8 +119,23 @@ class Circuit: This circuit is symbolic and cannot perform calculations. A specific backend has to be used for performing calculations. + Circuits can be created with a specific number of qubits and wire names. + Example: + .. testcode:: + from qibo import Circuit + c = Circuit(5) # Default wire names are [0, 1, 2, 3, 4] + c = Circuit(["A", "B", "C", "D", "E"]) + c = Circuit(5, wire_names=["A", "B", "C", "D", "E"]) + c = Circuit(wire_names=["A", "B", "C", "D", "E"]) + Args: - nqubits (int): Total number of qubits in the circuit. + nqubits (int | list, optional): Number of qubits in the circuit or a list of wire names. + wire_names (list, optional): List of wire names. + - Either ``nqubits`` or ``wire_names`` must be provided. + - If only ``nqubits`` is provided, wire names will default to [``0``, ``1``, ..., ``nqubits - 1``]. + - If only ``wire_names`` is provided, ``nqubits`` will be set to the length of ``wire_names``. + - ``nqubits`` and ``wire_names`` must be consistent with each other. + init_kwargs (dict): a dictionary with the following keys - *nqubits* @@ -141,11 +156,6 @@ class Circuit: Defaults to ``False``. accelerators (dict, optional): Dictionary that maps device names to the number of times each device will be used. Defaults to ``None``. - wire_names (list or dict, optional): Names for qubit wires. - If ``None``, defaults to (``q0``, ``q1``... ``qn``). - If ``list`` is passed, length of ``list`` must match ``nqubits``. - If ``dict`` is passed, the keys should match the default pattern. - Defaults to ``None``. ndevices (int): Total number of devices. Defaults to ``None``. nglobal (int): Base two logarithm of the number of devices. Defaults to ``None``. nlocal (int): Total number of available qubits in each device. Defaults to ``None``. @@ -155,29 +165,20 @@ class Circuit: def __init__( self, - nqubits: int, + nqubits: Optional[Union[int, list]] = None, accelerators=None, density_matrix: bool = False, - wire_names: Optional[Union[list, dict]] = None, + wire_names: Optional[list] = None, ): - if not isinstance(nqubits, int): - raise_error( - TypeError, - f"Number of qubits must be an integer but is {nqubits}.", - ) - if nqubits < 1: - raise_error( - ValueError, - f"Number of qubits must be positive but is {nqubits}.", - ) + nqubits, wire_names = _resolve_qubits(nqubits, wire_names) self.nqubits = nqubits - self.wire_names = wire_names self.init_kwargs = { "nqubits": nqubits, "accelerators": accelerators, "density_matrix": density_matrix, "wire_names": wire_names, } + self.wire_names = wire_names self.queue = _Queue(nqubits) # Keep track of parametrized gates for the ``set_parameters`` method self.parametrized_gates = _ParametrizedGates() @@ -282,49 +283,29 @@ def __add__(self, circuit): @property def wire_names(self): + if self._wire_names is None: + return list(range(self.nqubits)) return self._wire_names @wire_names.setter - def wire_names(self, wire_names: Union[list, dict]): - if not isinstance(wire_names, (list, dict, type(None))): + def wire_names(self, wire_names: Optional[list]): + if not isinstance(wire_names, (list, type(None))): raise_error( TypeError, - f"``wire_names`` must be type ``list`` or ``dict``, but is {type(wire_names)}.", + f"``wire_names`` must be type ``list``, but is {type(wire_names)}.", ) - if isinstance(wire_names, list): + if wire_names is not None: if len(wire_names) != self.nqubits: raise_error( ValueError, "Number of wire names must be equal to the number of qubits, " f"but is {len(wire_names)}.", ) - - if any([not isinstance(name, str) for name in wire_names]): - raise_error(ValueError, "all wire names must be type ``str``.") - - self._wire_names = wire_names - elif isinstance(wire_names, dict): - if len(wire_names.keys()) > self.nqubits: - raise_error( - ValueError, - "number of elements in the ``wire_names`` dictionary " - + "cannot be bigger than ``nqubits``.", - ) - - if any([not isinstance(name, str) for name in wire_names.keys()]) or any( - [not isinstance(name, str) for name in wire_names.values()] - ): - raise_error( - ValueError, - "all keys and values in the ``wire_names`` dictionary must be type ``str``.", - ) - - self._wire_names = [ - wire_names.get(f"q{i}", f"q{i}") for i in range(self.nqubits) - ] + self._wire_names = wire_names.copy() else: - self._wire_names = [f"q{i}" for i in range(self.nqubits)] + self._wire_names = None + self.init_kwargs["wire_names"] = self._wire_names @property def repeated_execution(self): @@ -407,8 +388,8 @@ def light_cone(self, *qubits): qubit_map = {q: i for i, q in enumerate(sorted(qubits))} kwargs = dict(self.init_kwargs) kwargs["nqubits"] = len(qubits) + kwargs["wire_names"] = [self.wire_names[q] for q in sorted(qubits)] circuit = self.__class__(**kwargs) - circuit.wire_names = [self.wire_names[q] for q in list(sorted(qubits))] circuit.add(gate.on_qubits(qubit_map) for gate in reversed(list_of_gates)) return circuit, qubit_map @@ -1282,6 +1263,7 @@ def diagram(self, line_wrap: int = 70, legend: bool = False) -> str: """Build the string representation of the circuit diagram.""" # build string representation of gates matrix = [[] for _ in range(self.nqubits)] + wire_names = [str(name) for name in self.wire_names] idx = [0] * self.nqubits for gate in self.queue: @@ -1303,12 +1285,12 @@ def diagram(self, line_wrap: int = 70, legend: bool = False) -> str: matrix[row][col] += "─" * (1 + maxlen - len(matrix[row][col])) # Print to terminal - max_name_len = max(len(name) for name in self.wire_names) + max_name_len = max(len(name) for name in wire_names) output = "" for q in range(self.nqubits): output += ( - self.wire_names[q] - + " " * (max_name_len - len(self.wire_names[q])) + wire_names[q] + + " " * (max_name_len - len(wire_names[q])) + ": ─" + "".join(matrix[q]) + "\n" @@ -1350,8 +1332,8 @@ def chunkstring(string, length): loutput += ["" for _ in range(self.nqubits)] suffix = " ...\n" prefix = ( - self.wire_names[row] - + " " * (max_name_len - len(self.wire_names[row])) + wire_names[row] + + " " * (max_name_len - len(wire_names[row])) + ": " ) if i == 0: @@ -1388,3 +1370,32 @@ def draw(self, line_wrap: int = 70, legend: bool = False): String containing text circuit diagram. """ sys.stdout.write(self.diagram(line_wrap, legend) + "\n") + + +def _resolve_qubits(qubits, wire_names): + """Parse the input arguments for defining a circuit. Allows the user to initialize the circuit as follows: + + Example: + .. code-block:: python + from qibo import Circuit + c = Circuit(3) + c = Circuit(3, wire_names=["q0", "q1", "q2"]) + c = Circuit(["q0", "q1", "q2"]) + c = Circuit(wire_names=["q0", "q1", "q2"]) + """ + if qubits is None and wire_names is not None: + return len(wire_names), wire_names + if qubits is not None and wire_names is None: + if isinstance(qubits, int) and qubits > 0: + return qubits, None + if isinstance(qubits, list): + return len(qubits), qubits + if qubits is not None and wire_names is not None: + if isinstance(qubits, int) and isinstance(wire_names, list): + if qubits == len(wire_names): + return qubits, wire_names + + raise_error( + ValueError, + "Invalid input arguments for defining a circuit.", + ) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 875bf03b52..fe7b0bb307 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -328,7 +328,7 @@ def _curve_fit( Returns: ndarray: the optimal parameters. """ - if backend.name == "pytorch": + if backend.platform == "pytorch": # pytorch has some problems with the `scipy.optim.curve_fit` function # thus we use a `torch.optim` optimizer params.requires_grad = True @@ -1174,21 +1174,11 @@ def _execute_circuit(circuit, qubit_map, noise_model=None, nshots=10000, backend qibo.states.CircuitResult: The result of the circuit execution. """ from qibo.transpiler.pipeline import Passes - from qibo.transpiler.placer import Custom if backend is None: # pragma: no cover backend = get_backend() elif backend.name == "qibolab": # pragma: no cover - qubits = backend.qubits - connectivity_edges = backend.connectivity - node_mapping = {q: i for i, q in enumerate(qubits)} - edges = [(node_mapping[e[0]], node_mapping[e[1]]) for e in connectivity_edges] - connectivity = nx.Graph(edges) - transpiler = Passes( - connectivity=connectivity, - passes=[Custom(initial_map=qubit_map, connectivity=connectivity)], - ) - circuit, _ = transpiler(circuit) + circuit.wire_names = qubit_map elif noise_model is not None: circuit = noise_model.apply(circuit) diff --git a/src/qibo/optimizers.py b/src/qibo/optimizers.py index 0914800f34..65e107e9a9 100644 --- a/src/qibo/optimizers.py +++ b/src/qibo/optimizers.py @@ -282,7 +282,7 @@ def sgd( callback=callback, ) - if backend.name == "pytorch": + if backend.platform == "pytorch": if compile: log.warning( "PyTorch does not support compilation of the optimization graph." diff --git a/src/qibo/quantum_info/basis.py b/src/qibo/quantum_info/basis.py index 7efab48547..673f7e48ca 100644 --- a/src/qibo/quantum_info/basis.py +++ b/src/qibo/quantum_info/basis.py @@ -105,7 +105,7 @@ def pauli_basis( basis_full = basis_single if vectorize and sparse: - if backend.name == "pytorch": + if backend.platform == "pytorch": nonzero = lambda x: backend.np.nonzero(x, as_tuple=True) else: nonzero = backend.np.nonzero diff --git a/src/qibo/tomography/gate_set_tomography.py b/src/qibo/tomography/gate_set_tomography.py index d3b4286f32..5a8331f527 100644 --- a/src/qibo/tomography/gate_set_tomography.py +++ b/src/qibo/tomography/gate_set_tomography.py @@ -1,14 +1,13 @@ from functools import cache from inspect import signature from itertools import product -from random import Random from typing import List, Union import numpy as np from sympy import S from qibo import Circuit, gates, symbols -from qibo.backends import _check_backend +from qibo.backends import _check_backend, get_transpiler from qibo.config import raise_error from qibo.hamiltonians import SymbolicHamiltonian from qibo.transpiler.optimizer import Preprocessing @@ -261,15 +260,7 @@ def GST( backend = _check_backend(backend) if backend.name == "qibolab" and transpiler is None: # pragma: no cover - transpiler = Passes( - connectivity=backend.platform.topology, - passes=[ - Preprocessing(backend.platform.topology), - Random(backend.platform.topology), - Sabre(backend.platform.topology), - Unroller(NativeGates.default()), - ], - ) + transpiler = get_transpiler() matrices = [] empty_matrices = [] diff --git a/src/qibo/transpiler/__init__.py b/src/qibo/transpiler/__init__.py index bfe576776c..40675f0be1 100644 --- a/src/qibo/transpiler/__init__.py +++ b/src/qibo/transpiler/__init__.py @@ -1,12 +1,10 @@ from qibo.transpiler.optimizer import Preprocessing, Rearrange from qibo.transpiler.pipeline import Passes from qibo.transpiler.placer import ( - Custom, Random, ReverseTraversal, StarConnectivityPlacer, Subgraph, - Trivial, ) from qibo.transpiler.router import Sabre, ShortestPaths, StarConnectivityRouter from qibo.transpiler.unroller import NativeGates diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index c838262231..11e1641759 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -12,14 +12,11 @@ def __init__(self, connectivity: nx.Graph, *args): """A placer implements the initial logical-physical qubit mapping""" @abstractmethod - def __call__(self, circuit: Circuit, *args) -> dict: - """Find initial qubit mapping + def __call__(self, circuit: Circuit, *args): + """Find initial qubit mapping. Mapping is saved in the circuit. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be mapped. - - Returns: - (dict): dictionary containing the initial logical to physical qubit mapping. """ @@ -29,17 +26,14 @@ def __init__(self, connectivity: nx.Graph, *args): """A router implements the mapping of a circuit on a specific hardware.""" @abstractmethod - def __call__( - self, circuit: Circuit, initial_layout: dict, *args - ) -> Tuple[Circuit, dict]: + def __call__(self, circuit: Circuit, *args) -> Tuple[Circuit, dict]: """Match circuit to hardware connectivity. Args: - circuit (:class:`qibo.models.Circuit`): circuit to be routed. - initial_layout (dict): dictionary containing the initial logical to physical qubit mapping. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. Returns: - (:class:`qibo.models.circuit.Circuit`, dict): routed circuit and dictionary containing the final logical to physical qubit mapping. + (:class:`qibo.models.circuit.Circuit`): routed circuit. """ diff --git a/src/qibo/transpiler/asserts.py b/src/qibo/transpiler/asserts.py new file mode 100644 index 0000000000..d988ce6add --- /dev/null +++ b/src/qibo/transpiler/asserts.py @@ -0,0 +1,200 @@ +from typing import Optional + +import networkx as nx +import numpy as np + +from qibo import gates +from qibo.backends.numpy import NumpyBackend +from qibo.config import raise_error +from qibo.models.circuit import Circuit +from qibo.quantum_info.random_ensembles import random_statevector +from qibo.transpiler._exceptions import ( + ConnectivityError, + DecompositionError, + PlacementError, + TranspilerPipelineError, +) +from qibo.transpiler.optimizer import Preprocessing +from qibo.transpiler.unroller import NativeGates + + +def assert_transpiling( + original_circuit: Circuit, + transpiled_circuit: Circuit, + connectivity: nx.Graph, + final_layout: dict, + native_gates: NativeGates = NativeGates.default(), + check_circuit_equivalence=True, +): + """Check that all transpiler passes have been executed correctly. + + Args: + original_circuit (qibo.models.Circuit): circuit before transpiling. + transpiled_circuit (qibo.models.Circuit): circuit after transpiling. + connectivity (networkx.Graph): chip qubits connectivity. + final_layout (dict): final physical-logical qubit mapping. + native_gates (NativeGates): native gates supported by the hardware. + check_circuit_equivalence (Bool): use simulations to check if the transpiled circuit is the same as the original. + """ + assert_connectivity(circuit=transpiled_circuit, connectivity=connectivity) + assert_decomposition( + circuit=transpiled_circuit, + native_gates=native_gates, + ) + if original_circuit.nqubits != transpiled_circuit.nqubits: + qubit_matcher = Preprocessing(connectivity=connectivity) + original_circuit = qubit_matcher(circuit=original_circuit) + assert_placement(circuit=original_circuit, connectivity=connectivity) + assert_placement(circuit=transpiled_circuit, connectivity=connectivity) + if check_circuit_equivalence: + assert_circuit_equivalence( + original_circuit=original_circuit, + transpiled_circuit=transpiled_circuit, + final_map=final_layout, + ) + + +def assert_circuit_equivalence( + original_circuit: Circuit, + transpiled_circuit: Circuit, + final_map: dict, + test_states: Optional[list] = None, + ntests: int = 3, +): + """Checks that the transpiled circuit agrees with the original using simulation. + + Args: + original_circuit (:class:`qibo.models.circuit.Circuit`): Original circuit. + transpiled_circuit (:class:`qibo.models.circuit.Circuit`): Transpiled circuit. + final_map (dict): logical-physical qubit mapping after routing. + test_states (list, optional): states on which the test is performed. + If ``None``, ``ntests`` random states will be tested. Defauts to ``None``. + ntests (int, optional): number of random states tested. Defauts to :math:`3`. + """ + backend = NumpyBackend() + if transpiled_circuit.nqubits != original_circuit.nqubits: + raise_error( + ValueError, + "Transpiled and original circuit do not have the same number of qubits.", + ) + + if test_states is None: + test_states = [ + random_statevector(dims=2**original_circuit.nqubits, backend=backend) + for _ in range(ntests) + ] + + ordering = list(final_map.values()) + + for i, state in enumerate(test_states): + target_state = backend.execute_circuit( + original_circuit, initial_state=state + ).state() + final_state = backend.execute_circuit( + transpiled_circuit, + initial_state=state, + ).state() + final_state = _transpose_qubits(final_state, ordering) + fidelity = np.abs(np.dot(np.conj(target_state), final_state)) + try: + np.testing.assert_allclose(fidelity, 1.0) + except AssertionError: + raise_error(TranspilerPipelineError, "Circuit equivalence not satisfied.") + + +def _transpose_qubits(state: np.ndarray, qubits_ordering: np.ndarray): + """Reorders qubits of a given state vector. + + Args: + state (np.ndarray): final state of the circuit. + qubits_ordering (np.ndarray): final qubit ordering. + """ + original_shape = state.shape + state = np.reshape(state, len(qubits_ordering) * (2,)) + state = np.transpose(state, qubits_ordering) + return np.reshape(state, original_shape) + + +def assert_placement(circuit: Circuit, connectivity: nx.Graph): + """Check if the layout of the circuit is consistent with the circuit and connectivity graph. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): Circuit model to check. + connectivity (:class:`networkx.Graph`, optional): Chip connectivity. + """ + if connectivity is None: + raise_error( + ValueError, + "Connectivity graph is missing.", + ) + + if circuit.nqubits != len(circuit.wire_names) or circuit.nqubits != len( + connectivity.nodes + ): + raise_error( + PlacementError, + f"Number of qubits in the circuit ({circuit.nqubits}) " + + f"does not match the number of qubits in the layout ({len(circuit.wire_names)}) " + + f"or the connectivity graph ({len(connectivity.nodes)}).", + ) + if set(circuit.wire_names) != set(connectivity.nodes): + raise_error( + PlacementError, + "Some physical qubits in the layout may be missing or duplicated.", + ) + + +def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): + """Assert if a circuit can be executed on Hardware. + + No gates acting on more than two qubits. + All two-qubit operations can be performed on hardware. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. + connectivity (:class:`networkx.Graph`): chip connectivity. + """ + layout = circuit.wire_names + for gate in circuit.queue: + if len(gate.qubits) > 2 and not isinstance(gate, gates.M): + raise_error(ConnectivityError, f"{gate.name} acts on more than two qubits.") + if len(gate.qubits) == 2: + physical_qubits = (layout[gate.qubits[0]], layout[gate.qubits[1]]) + if physical_qubits not in connectivity.edges: + raise_error( + ConnectivityError, + f"The circuit does not respect the connectivity. {gate.name} acts on {physical_qubits} but only the following qubits are directly connected: {connectivity.edges}.", + ) + + +def assert_decomposition( + circuit: Circuit, + native_gates: NativeGates, +): + """Checks if a circuit has been correctly decomposed into native gates. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`): + native gates in the transpiled circuit. + """ + for gate in circuit.queue: + if isinstance(gate, gates.M): + continue + if len(gate.qubits) <= 2: + try: + native_type_gate = NativeGates.from_gate(gate) + if not native_type_gate & native_gates: + raise_error( + DecompositionError, + f"{gate.name} is not a native gate.", + ) + except ValueError: + raise_error( + DecompositionError, + f"{gate.name} is not a native gate.", + ) + else: + raise_error( + DecompositionError, f"{gate.name} acts on more than two qubits." + ) diff --git a/src/qibo/transpiler/optimizer.py b/src/qibo/transpiler/optimizer.py index 7008957627..741078e5e9 100644 --- a/src/qibo/transpiler/optimizer.py +++ b/src/qibo/transpiler/optimizer.py @@ -1,3 +1,5 @@ +from typing import Optional + import networkx as nx from qibo import gates @@ -13,10 +15,16 @@ class Preprocessing(Optimizer): connectivity (:class:`networkx.Graph`): hardware chip connectivity. """ - def __init__(self, connectivity: nx.Graph): + def __init__(self, connectivity: Optional[nx.Graph] = None): self.connectivity = connectivity def __call__(self, circuit: Circuit) -> Circuit: + if not all(qubit in self.connectivity.nodes for qubit in circuit.wire_names): + raise_error( + ValueError, + "The circuit qubits are not in the connectivity graph.", + ) + physical_qubits = self.connectivity.number_of_nodes() logical_qubits = circuit.nqubits if logical_qubits > physical_qubits: @@ -27,7 +35,10 @@ def __call__(self, circuit: Circuit) -> Circuit: ) if logical_qubits == physical_qubits: return circuit - new_circuit = Circuit(physical_qubits) + new_wire_names = circuit.wire_names + list( + self.connectivity.nodes - circuit.wire_names + ) + new_circuit = Circuit(nqubits=physical_qubits, wire_names=new_wire_names) for gate in circuit.queue: new_circuit.add(gate) return new_circuit @@ -48,7 +59,7 @@ def __init__(self, max_qubits: int = 1): def __call__(self, circuit: Circuit): fused_circuit = circuit.fuse(max_qubits=self.max_qubits) - new = circuit.__class__(circuit.nqubits) + new = circuit.__class__(nqubits=circuit.nqubits, wire_names=circuit.wire_names) for fgate in fused_circuit.queue: if isinstance(fgate, gates.FusedGate): new.add(gates.Unitary(fgate.matrix(), *fgate.qubits)) diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index c033850557..c1aae06775 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -1,147 +1,22 @@ -from typing import Optional - import networkx as nx -import numpy as np -from qibo.backends import NumpyBackend from qibo.config import raise_error from qibo.models import Circuit -from qibo.quantum_info.random_ensembles import random_statevector -from qibo.transpiler._exceptions import TranspilerPipelineError -from qibo.transpiler.abstract import Optimizer, Placer, Router -from qibo.transpiler.optimizer import Preprocessing -from qibo.transpiler.placer import StarConnectivityPlacer, Trivial, assert_placement -from qibo.transpiler.router import ( +from qibo.transpiler._exceptions import ( ConnectivityError, - StarConnectivityRouter, - assert_connectivity, + PlacementError, + TranspilerPipelineError, ) -from qibo.transpiler.unroller import ( - DecompositionError, - NativeGates, - Unroller, +from qibo.transpiler.abstract import Optimizer, Placer, Router +from qibo.transpiler.asserts import ( + assert_connectivity, assert_decomposition, + assert_placement, ) +from qibo.transpiler.unroller import DecompositionError, NativeGates, Unroller -def assert_circuit_equivalence( - original_circuit: Circuit, - transpiled_circuit: Circuit, - final_map: dict, - initial_map: Optional[dict] = None, - test_states: Optional[list] = None, - ntests: int = 3, -): - """Checks that the transpiled circuit agrees with the original using simulation. - - Args: - original_circuit (:class:`qibo.models.circuit.Circuit`): Original circuit. - transpiled_circuit (:class:`qibo.models.circuit.Circuit`): Transpiled circuit. - final_map (dict): logical-physical qubit mapping after routing. - initial_map (dict, optional): logical_physical qubit mapping before routing. - If ``None``, trivial initial map is used. Defauts to ``None``. - test_states (list, optional): states on which the test is performed. - If ``None``, ``ntests`` random states will be tested. Defauts to ``None``. - ntests (int, optional): number of random states tested. Defauts to :math:`3`. - """ - backend = NumpyBackend() - ordering = np.argsort(np.array(list(final_map.values()))) - if transpiled_circuit.nqubits != original_circuit.nqubits: - raise_error( - ValueError, - "Transpiled and original circuit do not have the same number of qubits.", - ) - - if test_states is None: - test_states = [ - random_statevector(dims=2**original_circuit.nqubits, backend=backend) - for _ in range(ntests) - ] - if initial_map is not None: - reordered_test_states = [] - initial_map = np.array(list(initial_map.values())) - reordered_test_states = [ - _transpose_qubits(initial_state, initial_map) - for initial_state in test_states - ] - else: - reordered_test_states = test_states - - for i in range(len(test_states)): - target_state = backend.execute_circuit( - original_circuit, initial_state=test_states[i] - ).state() - final_state = backend.execute_circuit( - transpiled_circuit, initial_state=reordered_test_states[i] - ).state() - final_state = _transpose_qubits(final_state, ordering) - fidelity = np.abs(np.dot(np.conj(target_state), final_state)) - try: - np.testing.assert_allclose(fidelity, 1.0) - except AssertionError: - raise_error(TranspilerPipelineError, "Circuit equivalence not satisfied.") - - -def _transpose_qubits(state: np.ndarray, qubits_ordering: np.ndarray): - """Reorders qubits of a given state vector. - - Args: - state (np.ndarray): final state of the circuit. - qubits_ordering (np.ndarray): final qubit ordering. - """ - original_shape = state.shape - state = np.reshape(state, len(qubits_ordering) * (2,)) - state = np.transpose(state, qubits_ordering) - return np.reshape(state, original_shape) - - -def assert_transpiling( - original_circuit: Circuit, - transpiled_circuit: Circuit, - connectivity: nx.Graph, - initial_layout: dict, - final_layout: dict, - native_gates: NativeGates = NativeGates.default(), - check_circuit_equivalence=True, -): - """Check that all transpiler passes have been executed correctly. - - Args: - original_circuit (:class:`qibo.models.Circuit`): circuit before transpiling. - transpiled_circuit (:class:`qibo.models.Circuit`): circuit after transpiling. - connectivity (:class:`networkx.Graph`): chip qubits connectivity. - initial_layout (dict): initial physical-logical qubit mapping. - final_layout (dict): final physical-logical qubit mapping. - native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates - supported by the hardware. Defaults to - :meth:`qibo.transpiler.unroller.NativeGates.default`. - check_circuit_equivalence (bool, optional): use simulations to check if the - transpiled circuit is the same as the original. Defaults to ``True``. - """ - assert_connectivity(circuit=transpiled_circuit, connectivity=connectivity) - assert_decomposition( - circuit=transpiled_circuit, - native_gates=native_gates, - ) - if original_circuit.nqubits != transpiled_circuit.nqubits: - qubit_matcher = Preprocessing(connectivity=connectivity) - original_circuit = qubit_matcher(circuit=original_circuit) - assert_placement( - circuit=original_circuit, layout=initial_layout, connectivity=connectivity - ) - assert_placement( - circuit=transpiled_circuit, layout=final_layout, connectivity=connectivity - ) - if check_circuit_equivalence: - assert_circuit_equivalence( - original_circuit=original_circuit, - transpiled_circuit=transpiled_circuit, - initial_map=initial_layout, - final_map=final_layout, - ) - - -def restrict_connectivity_qubits(connectivity: nx.Graph, qubits: list): +def restrict_connectivity_qubits(connectivity: nx.Graph, qubits: list[str]): """Restrict the connectivity to selected qubits. Args: @@ -177,14 +52,10 @@ class Passes: If ``None``, default transpiler will be used. Defaults to ``None``. connectivity (:class:`networkx.Graph`, optional): physical qubits connectivity. - If ``None``, :class:`` is used. - Defaults to ``None``. native_gates (:class:`qibo.transpiler.unroller.NativeGates`, optional): native gates. Defaults to :math:`qibo.transpiler.unroller.NativeGates.default`. on_qubits (list, optional): list of physical qubits to be used. If "None" all qubits are used. Defaults to ``None``. - int_qubit_name (bool, optional): if `True` the `final_layout` keys are - cast to integers. """ def __init__( @@ -193,68 +64,30 @@ def __init__( connectivity: nx.Graph = None, native_gates: NativeGates = NativeGates.default(), on_qubits: list = None, - int_qubit_names: bool = False, ): if on_qubits is not None: connectivity = restrict_connectivity_qubits(connectivity, on_qubits) self.connectivity = connectivity self.native_gates = native_gates - self.passes = self.default() if passes is None else passes - self.initial_layout = None - self.int_qubit_names = int_qubit_names - - def default(self): - """Return the default transpiler pipeline for the required hardware connectivity.""" - if not isinstance(self.connectivity, nx.Graph): - raise_error( - TranspilerPipelineError, - "Define the hardware chip connectivity to use default transpiler", - ) - default_passes = [] - # preprocessing - default_passes.append(Preprocessing(connectivity=self.connectivity)) - # default placer pass - default_passes.append(StarConnectivityPlacer()) - # default router pass - default_passes.append(StarConnectivityRouter()) - # default unroller pass - default_passes.append(Unroller(native_gates=self.native_gates)) - - return default_passes + self.passes = [] if passes is None else passes def __call__(self, circuit): """ This function returns the compiled circuits and the dictionary mapping - physical (keys) to logical (values) qubit. If `int_qubit_name` is `True` - each key `i` correspond to the `i-th` qubit in the graph. + physical (keys) to logical (values) qubit. """ - final_layout = self.initial_layout = None + + final_layout = None for transpiler_pass in self.passes: if isinstance(transpiler_pass, Optimizer): transpiler_pass.connectivity = self.connectivity circuit = transpiler_pass(circuit) elif isinstance(transpiler_pass, Placer): transpiler_pass.connectivity = self.connectivity - if self.initial_layout is None: - self.initial_layout = transpiler_pass(circuit) - final_layout = ( - self.initial_layout - ) # This way the final layout will be the same as the initial layout if no router is used - else: - raise_error( - TranspilerPipelineError, - "You are defining more than one placer pass.", - ) + final_layout = transpiler_pass(circuit) elif isinstance(transpiler_pass, Router): transpiler_pass.connectivity = self.connectivity - if self.initial_layout is not None: - circuit, final_layout = transpiler_pass( - circuit, self.initial_layout - ) - else: - raise_error( - TranspilerPipelineError, "Use a placement pass before routing." - ) + circuit, final_layout = transpiler_pass(circuit) elif isinstance(transpiler_pass, Unroller): circuit = transpiler_pass(circuit) else: @@ -262,8 +95,6 @@ def __call__(self, circuit): TranspilerPipelineError, f"Unrecognised transpiler pass: {transpiler_pass}", ) - if self.int_qubit_names and final_layout is not None: - final_layout = {int(key[1:]): value for key, value in final_layout.items()} return circuit, final_layout def is_satisfied(self, circuit: Circuit): @@ -276,14 +107,9 @@ def is_satisfied(self, circuit: Circuit): (bool): satisfiability condition. """ try: + assert_placement(circuit=circuit, connectivity=self.connectivity) assert_connectivity(circuit=circuit, connectivity=self.connectivity) assert_decomposition(circuit=circuit, native_gates=self.native_gates) return True - except ConnectivityError: + except (ConnectivityError, DecompositionError, PlacementError, ValueError): return False - except DecompositionError: - return False - - def get_initial_layout(self): - """Return initial qubit layout""" - return self.initial_layout diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 7944ea81f2..48e2cbd8ec 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -1,71 +1,17 @@ -from typing import Optional, Union +from typing import Optional import networkx as nx from qibo import gates from qibo.backends import _check_backend_and_local_state -from qibo.config import log, raise_error +from qibo.config import raise_error from qibo.models import Circuit from qibo.transpiler._exceptions import PlacementError from qibo.transpiler.abstract import Placer, Router +from qibo.transpiler.asserts import assert_placement from qibo.transpiler.router import _find_connected_qubit -def assert_placement( - circuit: Circuit, layout: dict, connectivity: nx.Graph = None -) -> bool: - """Check if layout is in the correct form and matches the number of qubits of the circuit. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): Circuit model to check. - layout (dict): physical to logical qubit mapping. - connectivity (:class:`networkx.Graph`, optional): Chip connectivity. - This argument is necessary if the layout is applied to a subset of - qubits of the original connectivity graph. Defaults to ``None``. - """ - assert_mapping_consistency(layout=layout, connectivity=connectivity) - if circuit.nqubits > len(layout): - raise_error( - PlacementError, - "Layout can't be used on circuit. The circuit requires more qubits.", - ) - if circuit.nqubits < len(layout): - raise_error( - PlacementError, - "Layout can't be used on circuit. " - + "Ancillary extra qubits need to be added to the circuit.", - ) - - -def assert_mapping_consistency(layout: dict, connectivity: nx.Graph = None): - """Check if layout is in the correct form. - - Args: - layout (dict): physical to logical qubit mapping. - connectivity (:class:`networkx.Graph`, optional): Chip connectivity. - This argument is necessary if the layout is applied to a subset of - qubits of the original connectivity graph. Defaults to ``None``. - """ - values = sorted(layout.values()) - physical_qubits = list(layout) - nodes = ( - list(range(len(values))) if connectivity is None else list(connectivity.nodes) - ) - ref_keys = ( - ["q" + str(i) for i in nodes] if isinstance(physical_qubits[0], str) else nodes - ) - if sorted(physical_qubits) != sorted(ref_keys): - raise_error( - PlacementError, - "Some physical qubits in the layout may be missing or duplicated.", - ) - if values != list(range(len(values))): - raise_error( - PlacementError, - "Some logical qubits in the layout may be missing or duplicated.", - ) - - def _find_gates_qubits_pairs(circuit: Circuit): """Helper method for :meth:`qibo.transpiler.placer`. Translate circuit into a list of pairs of qubits to be used by the router and placer. @@ -99,17 +45,12 @@ class StarConnectivityPlacer(Placer): q Args: - connectivity (:class:`networkx.Graph`): chip connectivity, not used for this transpiler. - middle_qubit (int, optional): qubit id of the qubit that is in the middle of the star. + connectivity (:class:`networkx.Graph`): star connectivity graph. """ - def __init__(self, connectivity=None, middle_qubit: int = 2): - self.middle_qubit = middle_qubit - if connectivity is not None: # pragma: no cover - log.warning( - "StarConnectivityRouter does not use the connectivity graph." - "The connectivity graph will be ignored." - ) + def __init__(self, connectivity: Optional[nx.Graph] = None): + self.connectivity = connectivity + self.middle_qubit = None def __call__(self, circuit: Circuit): """Apply the transpiler transformation on a given circuit. @@ -117,14 +58,12 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): The original Qibo circuit to transform. Only single qubit gates and two qubits gates are supported by the router. - - Returns: - dict: physical to logical qubit mapping. """ + assert_placement(circuit, self.connectivity) + self._check_star_connectivity() - # find the number of qubits for hardware circuit - nqubits = max(circuit.nqubits, self.middle_qubit + 1) - hardware_qubits = list(range(nqubits)) + middle_qubit_idx = circuit.wire_names.index(self.middle_qubit) + wire_names = circuit.wire_names.copy() for i, gate in enumerate(circuit.queue): if len(gate.qubits) > 2: @@ -133,118 +72,35 @@ def __call__(self, circuit: Circuit): "Gates targeting more than 2 qubits are not supported", ) if len(gate.qubits) == 2: - if self.middle_qubit not in gate.qubits: + if middle_qubit_idx not in gate.qubits: new_middle = _find_connected_qubit( gate.qubits, circuit.queue[i + 1 :], - hardware_qubits, error=PlacementError, + mapping=list(range(circuit.nqubits)), ) - hardware_qubits[self.middle_qubit], hardware_qubits[new_middle] = ( - new_middle, - self.middle_qubit, + + ( + wire_names[middle_qubit_idx], + wire_names[new_middle], + ) = ( + wire_names[new_middle], + wire_names[middle_qubit_idx], ) break - return dict(zip(["q" + str(i) for i in range(nqubits)], hardware_qubits)) - - -class Trivial(Placer): - """Place qubits according to the following notation: - - .. math:: - \\{\\textup{"q0"} : 0, \\textup{"q1"} : 1, ..., \\textup{"qn"} : n}. + circuit.wire_names = wire_names - Args: - connectivity (networkx.Graph, optional): chip connectivity. - """ - - def __init__(self, connectivity: nx.Graph = None): - self.connectivity = connectivity - - def __call__(self, circuit: Circuit): - """Find the trivial placement for the circuit. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (dict): physical to logical qubit mapping. - """ - if self.connectivity is not None: - if self.connectivity.number_of_nodes() != circuit.nqubits: + def _check_star_connectivity(self): + """Check if the connectivity graph is a star graph.""" + for node in self.connectivity.nodes: + if self.connectivity.degree(node) == 4: + self.middle_qubit = node + elif self.connectivity.degree(node) != 1: raise_error( - PlacementError, - "The number of nodes of the connectivity graph must match " - + "the number of qubits in the circuit", - ) - trivial_layout = dict( - zip( - ["q" + str(i) for i in list(self.connectivity.nodes())], - range(circuit.nqubits), - ) - ) - else: - trivial_layout = dict( - zip( - ["q" + str(i) for i in range(circuit.nqubits)], - range(circuit.nqubits), - ) - ) - return trivial_layout - - -class Custom(Placer): - """Define a custom initial qubit mapping. - - Args: - map (list or dict): physical to logical qubit mapping. - Examples: :math:`[1,2,0]` or - :math:`{\\textup{"q0"}: 1, \\textup{"q1"}: 2, \\textup{"q2"}:0}` - to assign the physical qubits :math:`\\{0, 1, 2\\}` - to the logical qubits :math:`[1, 2, 0]`. - connectivity (:class:`networkx.Graph`, optional): chip connectivity. - This argument is necessary if the layout applied to a subset of - qubits of the original connectivity graph. Defaults to ``None``. - """ - - def __init__(self, initial_map: Union[list, dict], connectivity: nx.Graph = None): - self.connectivity = connectivity - self.initial_map = initial_map - - def __call__(self, circuit=None): - """Return the custom placement if it can be applied to the given circuit (if given). - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (dict): physical to logical qubit mapping. - """ - if isinstance(self.initial_map, dict): - pass - elif isinstance(self.initial_map, list): - if self.connectivity is not None: - self.initial_map = dict( - zip( - ["q" + str(i) for i in self.connectivity.nodes()], - self.initial_map, - ) + ValueError, + "This connectivity graph is not a star graph.", ) - else: - self.initial_map = dict( - zip( - ["q" + str(i) for i in range(len(self.initial_map))], - self.initial_map, - ) - ) - else: - raise_error(TypeError, "Use dict or list to define mapping.") - if circuit is not None: - assert_placement(circuit, self.initial_map, connectivity=self.connectivity) - else: - assert_mapping_consistency(self.initial_map, connectivity=self.connectivity) - return self.initial_map class Subgraph(Placer): @@ -258,7 +114,7 @@ class Subgraph(Placer): connectivity (:class:`networkx.Graph`): chip connectivity. """ - def __init__(self, connectivity: nx.Graph): + def __init__(self, connectivity: Optional[nx.Graph] = None): self.connectivity = connectivity def __call__(self, circuit: Circuit): @@ -267,10 +123,8 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (dict): physical to logical qubit mapping. """ + assert_placement(circuit, self.connectivity) gates_qubits_pairs = _find_gates_qubits_pairs(circuit) if len(gates_qubits_pairs) < 3: raise_error( @@ -301,9 +155,7 @@ def __call__(self, circuit: Circuit): ): break - sorted_result = dict(sorted(result.mapping.items())) - - return {"q" + str(k): v for k, v in sorted_result.items()} + circuit.wire_names = sorted(result.mapping, key=lambda k: result.mapping[k]) class Random(Placer): @@ -320,7 +172,9 @@ class Random(Placer): initializes a generator with a random seed. Defaults to ``None``. """ - def __init__(self, connectivity, samples: int = 100, seed=None): + def __init__( + self, connectivity: Optional[nx.Graph] = None, samples: int = 100, seed=None + ): self.connectivity = connectivity self.samples = samples self.seed = seed @@ -330,15 +184,12 @@ def __call__(self, circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be transpiled. - - Returns: - (dict): physical-to-logical qubit mapping. """ + assert_placement(circuit, self.connectivity) _, local_state = _check_backend_and_local_state(self.seed, backend=None) gates_qubits_pairs = _find_gates_qubits_pairs(circuit) nodes = self.connectivity.number_of_nodes() keys = list(self.connectivity.nodes()) - dict_keys = ["q" + str(i) for i in keys] final_mapping = dict(zip(keys, range(nodes))) final_graph = nx.relabel_nodes(self.connectivity, final_mapping) @@ -351,16 +202,17 @@ def __call__(self, circuit): cost = self._cost(graph, gates_qubits_pairs) if cost == 0: - final_layout = dict(zip(dict_keys, list(mapping.values()))) - return dict(sorted(final_layout.items())) + final_layout = dict(zip(keys, list(mapping.values()))) + circuit.wire_names = sorted(final_layout, key=final_layout.get) + return if cost < final_cost: final_graph = graph final_mapping = mapping final_cost = cost - final_layout = dict(zip(dict_keys, list(final_mapping.values()))) - return dict(sorted(final_layout.items())) + final_layout = dict(zip(keys, list(final_mapping.values()))) + circuit.wire_names = sorted(final_layout, key=final_layout.get) def _cost(self, graph: nx.Graph, gates_qubits_pairs: list): """ @@ -405,8 +257,8 @@ class ReverseTraversal(Placer): def __init__( self, - connectivity: nx.Graph, routing_algorithm: Router, + connectivity: Optional[nx.Graph] = None, depth: Optional[int] = None, ): self.connectivity = connectivity @@ -418,17 +270,11 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (dict): physical to logical qubit mapping. """ - initial_placer = Trivial(self.connectivity) - initial_placement = initial_placer(circuit=circuit) + assert_placement(circuit, self.connectivity) self.routing_algorithm.connectivity = self.connectivity new_circuit = self._assemble_circuit(circuit) - final_placement = self._routing_step(initial_placement, new_circuit) - - return final_placement + self._routing_step(new_circuit) def _assemble_circuit(self, circuit: Circuit): """Assemble a single circuit to apply Reverse Traversal placement based on depth. @@ -461,20 +307,19 @@ def _assemble_circuit(self, circuit: Circuit): gates_qubits_pairs.reverse() assembled_gates_qubits_pairs += gates_qubits_pairs[0:remainder] - new_circuit = Circuit(circuit.nqubits) + new_circuit = Circuit(circuit.nqubits, wire_names=circuit.wire_names) for qubits in assembled_gates_qubits_pairs: # As only the connectivity is important here we can replace everything with CZ gates new_circuit.add(gates.CZ(qubits[0], qubits[1])) return new_circuit.invert() - def _routing_step(self, layout: dict, circuit: Circuit): + def _routing_step(self, circuit: Circuit): """Perform routing of the circuit. Args: - layout (dict): intial qubit layout. circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. """ - _, final_mapping = self.routing_algorithm(circuit, layout) + _, final_mapping = self.routing_algorithm(circuit) return final_mapping diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index b1aad8fc69..704e4435b8 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -6,62 +6,14 @@ import numpy as np from qibo import gates -from qibo.config import log, raise_error +from qibo.config import raise_error from qibo.models import Circuit from qibo.transpiler._exceptions import ConnectivityError from qibo.transpiler.abstract import Router +from qibo.transpiler.asserts import assert_placement from qibo.transpiler.blocks import Block, CircuitBlocks -def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): - """Assert if a circuit can be executed on Hardware. - - No gates acting on more than two qubits. - All two-qubit operations can be performed on hardware. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. - connectivity (:class:`networkx.Graph`): chip connectivity. - """ - if list(connectivity.nodes) != list(range(connectivity.number_of_nodes())): - node_mapping = {node: i for i, node in enumerate(connectivity.nodes)} - new_connectivity = nx.Graph() - new_connectivity.add_edges_from( - [(node_mapping[u], node_mapping[v]) for u, v in connectivity.edges] - ) - connectivity = new_connectivity - for gate in circuit.queue: - if len(gate.qubits) > 2 and not isinstance(gate, gates.M): - raise_error(ConnectivityError, f"{gate.name} acts on more than two qubits.") - if len(gate.qubits) == 2: - # physical_qubits = tuple(sorted((circuit.wire_names[gate.qubits[0]], circuit.wire_names[gate.qubits[1]]))) - physical_qubits = tuple(sorted(gate.qubits)) # for q_i naming - if physical_qubits not in connectivity.edges: - raise_error( - ConnectivityError, - f"The circuit does not respect the connectivity. {gate.name} acts on {physical_qubits} but only the following qubits are directly connected: {connectivity.edges}.", - ) - - -def _relabel_connectivity(connectivity, layout): - """Relabels the connectivity graph using the passed layout. - - Args: - connectivity (nx.Graph): input connectivity. - layout (dict): input qubit layout. - Returns: - (dict) the updated connectivity. - """ - node_mapping = {} - layout = dict( - sorted(layout.items(), key=lambda item: int(item[0][1:])) - ) # for q_i naming - for i, node in enumerate(list(layout.keys())): - node_mapping[int(node[1:])] = i # for q_i naming - new_connectivity = nx.relabel_nodes(connectivity, node_mapping) - return new_connectivity - - class StarConnectivityRouter(Router): """Transforms an arbitrary circuit to one that can be executed on hardware. @@ -76,91 +28,87 @@ class StarConnectivityRouter(Router): by adding SWAP gates when needed. Args: - connectivity (:class:`networkx.Graph`): chip connectivity, not used for this transpiler. - middle_qubit (int, optional): qubit id of the qubit that is in the middle of the star. + connectivity (:class:`networkx.Graph`): star connectivity graph. """ - def __init__(self, connectivity=None, middle_qubit: int = 2): - self.middle_qubit = middle_qubit - if connectivity is not None: # pragma: no cover - log.warning( - "StarConnectivityRouter does not use the connectivity graph." - "The connectivity graph will be ignored." - ) + def __init__(self, connectivity: Optional[nx.Graph] = None): + self.connectivity = connectivity + self.middle_qubit = None - def __call__(self, circuit: Circuit, initial_layout: dict): + def __call__(self, circuit: Circuit): """Apply the transpiler transformation on a given circuit. Args: circuit (:class:`qibo.models.circuit.Circuit`): The original Qibo circuit to transform. Only single qubit gates and two qubits gates are supported by the router. - initial_layout (dict): initial physical-to-logical qubit mapping, - use `qibo.transpiler.placer.StarConnectivityPlacer` for better performance. - - Returns: - (:class:`qibo.models.circuit.Circuit`, list): circuit that performs the same operation - as the original but respects the hardware connectivity, - and list that maps logical to hardware qubits. """ + self._check_star_connectivity() + assert_placement(circuit, self.connectivity) - middle_qubit = self.middle_qubit - nqubits = max(circuit.nqubits, middle_qubit + 1) - # new circuit object that will be compatible with hardware connectivity - new = Circuit(nqubits) - # list to maps logical to hardware qubits - hardware_qubits = list(initial_layout.values()) + middle_qubit_idx = circuit.wire_names.index(self.middle_qubit) + nqubits = circuit.nqubits + new = Circuit(nqubits=nqubits, wire_names=circuit.wire_names) + l2p = list(range(nqubits)) for i, gate in enumerate(circuit.queue): - # map gate qubits to hardware - qubits = tuple(hardware_qubits.index(q) for q in gate.qubits) + routed_qubits = [l2p[q] for q in gate.qubits] + if isinstance(gate, gates.M): - new_gate = gates.M(*qubits, **gate.init_kwargs) + new_gate = gates.M(*routed_qubits, **gate.init_kwargs) new_gate.result = gate.result new.add(new_gate) continue - if len(qubits) > 2: + if len(routed_qubits) > 2: raise_error( ConnectivityError, "Gates targeting more than two qubits are not supported.", ) - if len(qubits) == 2 and middle_qubit not in qubits: + if len(routed_qubits) == 2 and middle_qubit_idx not in routed_qubits: # find which qubit should be moved new_middle = _find_connected_qubit( - qubits, + routed_qubits, circuit.queue[i + 1 :], - hardware_qubits, error=ConnectivityError, + mapping=l2p, ) - # update hardware qubits according to the swap - hardware_qubits[middle_qubit], hardware_qubits[new_middle] = ( - hardware_qubits[new_middle], - hardware_qubits[middle_qubit], - ) - new.add(gates.SWAP(middle_qubit, new_middle)) - # update gate qubits according to the new swap - qubits = tuple(hardware_qubits.index(q) for q in gate.qubits) + + new.add(gates.SWAP(new_middle, middle_qubit_idx)) + idx1, idx2 = l2p.index(middle_qubit_idx), l2p.index(new_middle) + l2p[idx1], l2p[idx2] = l2p[idx2], l2p[idx1] + + routed_qubits = [l2p[q] for q in gate.qubits] # add gate to the hardware circuit if isinstance(gate, gates.Unitary): # gates.Unitary requires matrix as first argument matrix = gate.init_args[0] - new.add(gate.__class__(matrix, *qubits, **gate.init_kwargs)) + new.add(gate.__class__(matrix, *routed_qubits, **gate.init_kwargs)) else: - new.add(gate.__class__(*qubits, **gate.init_kwargs)) - hardware_qubits_keys = ["q" + str(i) for i in range(5)] - return new, dict(zip(hardware_qubits_keys, hardware_qubits)) + new.add(gate.__class__(*routed_qubits, **gate.init_kwargs)) + return new, {circuit.wire_names[i]: l2p[i] for i in range(nqubits)} + + def _check_star_connectivity(self): + """Check if the connectivity graph is a star graph.""" + for node in self.connectivity.nodes: + if self.connectivity.degree(node) == 4: + self.middle_qubit = node + elif self.connectivity.degree(node) != 1: + raise_error( + ValueError, + "This connectivity graph is not a star graph.", + ) -def _find_connected_qubit(qubits, queue, hardware_qubits, error): +def _find_connected_qubit(qubits, queue, error, mapping): """Helper method for :meth:`qibo.transpiler.router.StarConnectivityRouter` and :meth:`qibo.transpiler.router.StarConnectivityPlacer`. Finds which qubit should be mapped to hardware middle qubit by looking at the two-qubit gates that follow. """ - possible_qubits = set(qubits) + possible_qubits = {qubits[0], qubits[1]} for next_gate in queue: if len(next_gate.qubits) > 2: raise_error( @@ -168,11 +116,13 @@ def _find_connected_qubit(qubits, queue, hardware_qubits, error): "Gates targeting more than 2 qubits are not supported", ) if len(next_gate.qubits) == 2: - possible_qubits &= {hardware_qubits.index(q) for q in next_gate.qubits} + possible_qubits &= { + mapping[next_gate.qubits[0]], + mapping[next_gate.qubits[1]], + } - if not possible_qubits: + if len(possible_qubits) == 0: return qubits[0] - if len(possible_qubits) == 1: return possible_qubits.pop() @@ -185,7 +135,6 @@ class CircuitMap: Also implements the initial two-qubit block decompositions. Args: - initial_layout (dict): initial physical to logical qubit mapping. circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. blocks (:class:`qibo.transpiler.blocks.CircuitBlocks`, optional): circuit block representation. If ``None``, the blocks will be computed from the circuit. @@ -194,7 +143,6 @@ class CircuitMap: def __init__( self, - initial_layout: Optional[dict] = None, circuit: Optional[Circuit] = None, blocks: Optional[CircuitBlocks] = None, temp: Optional[bool] = False, @@ -204,23 +152,21 @@ def __init__( self._temporary = temp if self._temporary: return - elif circuit is None: + if circuit is None: raise_error(ValueError, "Circuit must be provided.") - if blocks is not None: self.circuit_blocks = blocks else: self.circuit_blocks = CircuitBlocks(circuit, index_names=True) - self._nqubits = circuit.nqubits - self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) + self.nqubits = circuit.nqubits + self._routed_blocks = CircuitBlocks( + Circuit(circuit.nqubits, wire_names=circuit.wire_names) + ) self._swaps = 0 - if initial_layout is None: - return - - self.wire_names = list(initial_layout.keys()) - self.physical_to_logical = list(initial_layout.values()) + self.wire_names = circuit.wire_names.copy() + self.physical_to_logical = list(range(self.nqubits)) @property def physical_to_logical(self): @@ -302,7 +248,7 @@ def routed_circuit(self, circuit_kwargs: Optional[dict] = None): def final_layout(self): """Returns the final physical-logical qubits mapping.""" - return {self.wire_names[i]: self._p2l[i] for i in range(self._nqubits)} + return {self.wire_names[i]: self._l2p[i] for i in range(self.nqubits)} def update(self, logical_swap: tuple): """Updates the qubit mapping after applying a ``SWAP`` @@ -368,7 +314,9 @@ class ShortestPaths(Router): If ``None``, defaults to :math:`42`. Defaults to ``None``. """ - def __init__(self, connectivity: nx.Graph, seed: Optional[int] = None): + def __init__( + self, connectivity: Optional[nx.Graph] = None, seed: Optional[int] = None + ): self.connectivity = connectivity self._front_layer = None self.circuit_map = None @@ -384,19 +332,19 @@ def added_swaps(self): """Returns the number of SWAP gates added to the circuit during routing.""" return self.circuit_map._swaps - def __call__(self, circuit: Circuit, initial_layout: dict): + def __call__(self, circuit: Circuit): """Circuit connectivity matching. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be matched to hardware connectivity. - initial_layout (dict): initial physical-to-logical qubit mapping Returns: (:class:`qibo.models.circuit.Circuit`, dict): circut mapped to hardware topology, and final physical-to-logical qubit mapping. """ - self._preprocessing(circuit=circuit, initial_layout=initial_layout) + assert_placement(circuit, self.connectivity) + self._preprocessing(circuit=circuit) while self._dag.number_of_nodes() != 0: execute_block_list = self._check_execution() if execute_block_list is not None: @@ -405,7 +353,6 @@ def __call__(self, circuit: Circuit, initial_layout: dict): self._find_new_mapping() circuit_kwargs = circuit.init_kwargs - circuit_kwargs["wire_names"] = list(initial_layout.keys()) routed_circuit = self.circuit_map.routed_circuit(circuit_kwargs=circuit_kwargs) if self._final_measurements is not None: routed_circuit = self._append_final_measurements( @@ -490,7 +437,7 @@ def _compute_cost(self, candidate: tuple): (list, int): best path to move qubits and qubit meeting point in the path. """ temporary_circuit = CircuitMap( - circuit=Circuit(self.circuit_map._nqubits), + circuit=Circuit(self.circuit_map.nqubits), blocks=deepcopy(self.circuit_map.circuit_blocks), ) @@ -573,7 +520,7 @@ def _update_front_layer(self): node[0] for node in self._dag.nodes(data="layer") if node[1] == 0 ] - def _preprocessing(self, circuit: Circuit, initial_layout: dict): + def _preprocessing(self, circuit: Circuit): """The following objects will be initialised: - circuit: class to represent circuit and to perform logical-physical qubit mapping. - _final_measurements: measurement gates at the end of the circuit. @@ -581,14 +528,13 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be preprocessed. - initial_layout (dict): initial physical-to-logical qubit mapping. """ - - self.connectivity = _relabel_connectivity(self.connectivity, initial_layout) - + self.connectivity = nx.relabel_nodes( + self.connectivity, {v: i for i, v in enumerate(circuit.wire_names)} + ) copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) - self.circuit_map = CircuitMap(initial_layout, copied_circuit) + self.circuit_map = CircuitMap(copied_circuit) self._dag = _create_dag(self.circuit_map.blocks_logical_qubits_pairs()) self._update_front_layer() @@ -657,7 +603,7 @@ class Sabre(Router): def __init__( self, - connectivity: nx.Graph, + connectivity: Optional[nx.Graph] = None, lookahead: int = 2, decay_lookahead: float = 0.6, delta: float = 0.001, @@ -679,17 +625,17 @@ def __init__( self._temp_added_swaps = [] random.seed(seed) - def __call__(self, circuit: Circuit, initial_layout: dict): + def __call__(self, circuit: Circuit): """Route the circuit. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. - initial_layout (dict): initial physical to logical qubit mapping. Returns: (:class:`qibo.models.circuit.Circuit`, dict): routed circuit and final layout. """ - self._preprocessing(circuit=circuit, initial_layout=initial_layout) + assert_placement(circuit, self.connectivity) + self._preprocessing(circuit=circuit) longest_path = np.max(self._dist_matrix) while self._dag.number_of_nodes() != 0: @@ -711,7 +657,6 @@ def __call__(self, circuit: Circuit, initial_layout: dict): self._shortest_path_routing() circuit_kwargs = circuit.init_kwargs - circuit_kwargs["wire_names"] = list(initial_layout.keys()) routed_circuit = self.circuit_map.routed_circuit(circuit_kwargs=circuit_kwargs) if self._final_measurements is not None: routed_circuit = self._append_final_measurements( @@ -725,7 +670,7 @@ def added_swaps(self): """Returns the number of SWAP gates added to the circuit during routing.""" return self.circuit_map._swaps - def _preprocessing(self, circuit: Circuit, initial_layout: dict): + def _preprocessing(self, circuit: Circuit): """The following objects will be initialised: - circuit: class to represent circuit and to perform logical-physical qubit mapping. - _final_measurements: measurement gates at the end of the circuit. @@ -738,14 +683,15 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be preprocessed. - initial_layout (dict): initial physical-to-logical qubit mapping. """ - self.connectivity = _relabel_connectivity(self.connectivity, initial_layout) + self.connectivity = nx.relabel_nodes( + self.connectivity, {v: i for i, v in enumerate(circuit.wire_names)} + ) copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) - self.circuit_map = CircuitMap(initial_layout, copied_circuit) + self.circuit_map = CircuitMap(copied_circuit) self._dist_matrix = nx.floyd_warshall_numpy(self.connectivity) self._dag = _create_dag(self.circuit_map.blocks_logical_qubits_pairs()) self._memory_map = [] @@ -977,7 +923,7 @@ def _create_dag(gates_qubits_pairs: list): dag = nx.DiGraph() dag.add_nodes_from(range(len(gates_qubits_pairs))) - for i in range(len(gates_qubits_pairs)): + for i, _ in enumerate(gates_qubits_pairs): dag.nodes[i]["qubits"] = gates_qubits_pairs[i] # Find all successors @@ -986,7 +932,7 @@ def _create_dag(gates_qubits_pairs: list): saturated_qubits = [] for next_idx, next_gate in enumerate(gates_qubits_pairs[idx + 1 :]): for qubit in gate: - if (qubit in next_gate) and (not qubit in saturated_qubits): + if (qubit in next_gate) and (qubit not in saturated_qubits): saturated_qubits.append(qubit) connectivity_list.append((idx, next_idx + idx + 1)) if len(saturated_qubits) >= 2: diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 17e2e385aa..2e875311fd 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -125,39 +125,6 @@ def __call__(self, circuit: Circuit): return translated_circuit -def assert_decomposition( - circuit: Circuit, - native_gates: NativeGates, -): - """Checks if a circuit has been correctly decomposed into native gates. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. - native_gates (:class:`qibo.transpiler.unroller.NativeGates`): - native gates in the transpiled circuit. - """ - for gate in circuit.queue: - if isinstance(gate, gates.M): - continue - if len(gate.qubits) <= 2: - try: - native_type_gate = NativeGates.from_gate(gate) - if not native_type_gate & native_gates: - raise_error( - DecompositionError, - f"{gate.name} is not a native gate.", - ) - except ValueError: - raise_error( - DecompositionError, - f"{gate.name} is not a native gate.", - ) - else: - raise_error( - DecompositionError, f"{gate.name} acts on more than two qubits." - ) - - def translate_gate( gate, native_gates: NativeGates, diff --git a/tests/conftest.py b/tests/conftest.py index d15e5164d1..344accf983 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ import sys +import networkx as nx import pytest from qibo.backends import _Global, construct_backend @@ -12,11 +13,11 @@ # backends to be tested BACKENDS = [ "numpy", - "pytorch", "qibojit-numba", "qibojit-cupy", "qibojit-cuquantum", "qiboml-tensorflow", + "qiboml-pytorch", ] # multigpu configurations to be tested (only with qibojit-cupy) ACCELERATORS = [ @@ -81,6 +82,40 @@ def clear(): _Global._transpiler = None +@pytest.fixture +def star_connectivity(): + def _star_connectivity(names=list(range(5)), middle_qubit_idx=2): + chip = nx.Graph() + chip.add_nodes_from(names) + graph_list = [ + (names[i], names[middle_qubit_idx]) + for i in range(len(names)) + if i != middle_qubit_idx + ] + chip.add_edges_from(graph_list) + return chip + + return _star_connectivity + + +@pytest.fixture +def grid_connectivity(): + def _grid_connectivity(names=list(range(5))): + chip = nx.Graph() + chip.add_nodes_from(names) + graph_list = [ + (names[0], names[1]), + (names[1], names[2]), + (names[2], names[3]), + (names[3], names[0]), + (names[0], names[4]), + ] + chip.add_edges_from(graph_list) + return chip + + return _grid_connectivity + + def pytest_generate_tests(metafunc): module_name = metafunc.module.__name__ diff --git a/tests/test_backends.py b/tests/test_backends.py index 9ac9ed26b6..383fdb7c17 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -113,19 +113,17 @@ def test_construct_backend(backend): def test_list_available_backends(): - tensorflow = False if platform.system() in ["Windows", "Darwin"] else True qulacs = ( False if platform.system() == "Darwin" and sys.version_info[1] == 9 else True ) available_backends = { "numpy": True, - "pytorch": True, "qulacs": qulacs, "qibojit": {"numba": True, "cupy": False, "cuquantum": False}, "qibolab": False, "qibo-cloud-backends": False, "qibotn": {"cutensornet": False, "qutensornet": True}, - "qiboml": {"tensorflow": tensorflow}, + "qiboml": {"tensorflow": False, "pytorch": True}, } assert available_backends == list_available_backends( "qibojit", "qibolab", "qibo-cloud-backends", "qibotn", "qiboml" diff --git a/tests/test_backends_clifford.py b/tests/test_backends_clifford.py index 6e0faf27cc..6cd1cd9e2c 100644 --- a/tests/test_backends_clifford.py +++ b/tests/test_backends_clifford.py @@ -4,10 +4,10 @@ import numpy as np import pytest -from qiboml.backends import TensorflowBackend +from qiboml.backends import PyTorchBackend, TensorflowBackend from qibo import Circuit, gates, get_backend, set_backend -from qibo.backends import CliffordBackend, NumpyBackend, PyTorchBackend +from qibo.backends import CliffordBackend, NumpyBackend from qibo.backends.clifford import _get_engine_name from qibo.noise import DepolarizingError, NoiseModel, PauliError from qibo.quantum_info.random_ensembles import random_clifford diff --git a/tests/test_backends_global.py b/tests/test_backends_global.py index 99c1f9d450..dc0b6dfeaa 100644 --- a/tests/test_backends_global.py +++ b/tests/test_backends_global.py @@ -128,9 +128,9 @@ def test_set_get_transpiler(): transpiler = Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Random(connectivity, seed=0), - Sabre(connectivity), + Preprocessing(), + Random(seed=0), + Sabre(), Unroller(NativeGates.default()), ], ) @@ -171,8 +171,13 @@ def natives(self): _Global._backend = backend transpiler = _Global.transpiler() - assert list(transpiler.connectivity.nodes) == [0, 1, 2, 3, 4] - assert list(transpiler.connectivity.edges) == [(0, 1), (1, 2), (2, 3), (3, 4)] + assert list(transpiler.connectivity.nodes) == ["A1", "A2", "A3", "A4", "A5"] + assert list(transpiler.connectivity.edges) == [ + ("A1", "A2"), + ("A2", "A3"), + ("A3", "A4"), + ("A4", "A5"), + ] assert ( NativeGates.CZ in transpiler.native_gates and NativeGates.GPI2 in transpiler.native_gates diff --git a/tests/test_backends_torch_gradients.py b/tests/test_backends_torch_gradients.py deleted file mode 100644 index 9dcd0f032a..0000000000 --- a/tests/test_backends_torch_gradients.py +++ /dev/null @@ -1,85 +0,0 @@ -import sys - -import numpy as np -import pytest - -from qibo import Circuit, gates -from qibo.backends import PyTorchBackend, construct_backend -from qibo.quantum_info import infidelity - - -def test_torch_gradients(): - backend = PyTorchBackend() - backend.np.manual_seed(42) - nepochs = 400 - optimizer = backend.np.optim.Adam - target_state = backend.np.rand(4, dtype=backend.np.complex128) - target_state = target_state / backend.np.norm(target_state) - params = backend.np.rand(4, dtype=backend.np.float64, requires_grad=True) - circuit = Circuit(2) - circuit.add(gates.RX(0, params[0])) - circuit.add(gates.RY(1, params[1])) - circuit.add(gates.U2(1, params[2], params[3])) - - initial_params = params.clone() - initial_loss = infidelity( - target_state, backend.execute_circuit(circuit).state(), backend=backend - ) - - optimizer = optimizer([params], lr=0.01) - for _ in range(nepochs): - optimizer.zero_grad() - circuit.set_parameters(params) - final_state = backend.execute_circuit(circuit).state() - loss = infidelity(target_state, final_state, backend=backend) - loss.backward() - grad = params.grad.clone().norm() - optimizer.step() - - assert initial_loss > loss - assert initial_params[0] != params[0] - assert grad.item() < 10e-3 - - -@pytest.mark.skipif( - sys.platform != "linux", reason="Tensorflow available only when testing on linux." -) -def test_torch_tensorflow_gradients(): - - backend = PyTorchBackend() - tf_backend = construct_backend(backend="qiboml", platform="tensorflow") - - target_state = backend.np.tensor([0.0, 1.0], dtype=backend.np.complex128) - param = backend.np.tensor([0.1], dtype=backend.np.float64, requires_grad=True) - circuit = Circuit(1) - circuit.add(gates.RX(0, param[0])) - - optimizer = backend.np.optim.SGD - optimizer = optimizer([param], lr=1) - circuit.set_parameters(param) - final_state = backend.execute_circuit(circuit).state() - loss = infidelity(target_state, final_state, backend=backend) - loss.backward() - torch_param_grad = param.grad.clone().item() - optimizer.step() - torch_param = param.clone().item() - - target_state = tf_backend.tf.constant([0.0, 1.0], dtype=tf_backend.tf.complex128) - param = tf_backend.tf.Variable([0.1], dtype=tf_backend.tf.float64) - circuit = Circuit(1) - circuit.add(gates.RX(0, param[0])) - - optimizer = tf_backend.tf.optimizers.SGD(learning_rate=1.0) - - with tf_backend.tf.GradientTape() as tape: - circuit.set_parameters(param) - final_state = tf_backend.execute_circuit(circuit).state() - loss = infidelity(target_state, final_state, backend=tf_backend) - - grads = tape.gradient(loss, [param]) - tf_param_grad = grads[0].numpy()[0] - optimizer.apply_gradients(zip(grads, [param])) - tf_param = param.numpy()[0] - - assert np.allclose(torch_param_grad, tf_param_grad, atol=1e-7, rtol=1e-7) - assert np.allclose(torch_param, tf_param, atol=1e-7, rtol=1e-7) diff --git a/tests/test_hamiltonians.py b/tests/test_hamiltonians.py index f6732c88f0..e66c824da2 100644 --- a/tests/test_hamiltonians.py +++ b/tests/test_hamiltonians.py @@ -69,10 +69,10 @@ def transformation_d(a, b, use_eye=False): H2 = hamiltonians.XXZ(nqubits=2, delta=1, backend=backend) mH1, mH2 = backend.to_numpy(H1.matrix), backend.to_numpy(H2.matrix) else: - if backend.platform == "tensorflow": - pytest.skip("Tensorflow does not support operations with sparse matrices.") - elif backend.name == "pytorch": - pytest.skip("Pytorch does not support operations with sparse matrices.") + if backend.platform in ["tensorflow", "pytorch"]: + pytest.skip( + "Tensorflow and Pytorch do not support operations with sparse matrices." + ) mH1 = random_sparse_matrix(backend, 64, sparse_type=sparse_type) mH2 = random_sparse_matrix(backend, 64, sparse_type=sparse_type) @@ -101,10 +101,10 @@ def test_hamiltonian_addition(backend, sparse_type): H1 = hamiltonians.Y(nqubits=3, backend=backend) H2 = hamiltonians.TFIM(nqubits=3, h=1.0, backend=backend) else: - if backend.platform == "tensorflow": - pytest.skip("Tensorflow does not support operations with sparse matrices.") - elif backend.name == "pytorch": - pytest.skip("Pytorch does not support operations with sparse matrices.") + if backend.platform in ["tensorflow", "pytorch"]: + pytest.skip( + "Tensorflow and Pytorch do not support operations with sparse matrices." + ) H1 = hamiltonians.Hamiltonian( 6, random_sparse_matrix(backend, 64, sparse_type=sparse_type), @@ -149,10 +149,10 @@ def test_hamiltonian_operation_errors(backend): @pytest.mark.parametrize("sparse_type", [None, "coo", "csr", "csc", "dia"]) def test_hamiltonian_matmul(backend, sparse_type): """Test matrix multiplication between Hamiltonians.""" - if backend.name == "pytorch": - pytest.skip("Pytorch does not support operations with sparse matrices.") - if backend.platform == "tensorflow": - pytest.skip("Tensorflow does not support operations with sparse matrices.") + if backend.platform in ["tensorflow", "pytorch"]: + pytest.skip( + "Tensorflow and Pytorch do not support operations with sparse matrices." + ) if sparse_type is None: nqubits = 3 H1 = hamiltonians.TFIM(nqubits, h=1.0, backend=backend) @@ -193,10 +193,10 @@ def test_hamiltonian_matmul_states(backend, sparse_type): nqubits = 3 H = hamiltonians.TFIM(nqubits, h=1.0, backend=backend) else: - if backend.platform == "tensorflow": - pytest.skip("Tensorflow does not support operations with sparse matrices.") - elif backend.name == "pytorch": - pytest.skip("Pytorch does not support operations with sparse matrices.") + if backend.platform in ["tensorflow", "pytorch"]: + pytest.skip( + "Tensorflow and Pytorch do not support operations with sparse matrices." + ) nqubits = 3 nstates = 2**nqubits matrix = random_sparse_matrix(backend, nstates, sparse_type) @@ -230,10 +230,10 @@ def test_hamiltonian_expectation(backend, dense, density_matrix, sparse_type): if sparse_type is None: h = hamiltonians.XXZ(nqubits=3, delta=0.5, dense=dense, backend=backend) else: - if backend.platform == "tensorflow": - pytest.skip("Tensorflow does not support operations with sparse matrices.") - elif backend.name == "pytorch": - pytest.skip("Pytorch does not support operations with sparse matrices.") + if backend.platform in ["tensorflow", "pytorch"]: + pytest.skip( + "Tensorflow and Pytorch do not support operations with sparse matrices." + ) h = hamiltonians.Hamiltonian( 6, random_sparse_matrix(backend, 64, sparse_type), backend=backend ) @@ -325,10 +325,10 @@ def test_hamiltonian_eigenvalues(backend, dtype, sparse_type, dense): if sparse_type is None: H1 = hamiltonians.XXZ(nqubits=2, delta=0.5, dense=dense, backend=backend) else: - if backend.platform == "tensorflow": - pytest.skip("Tensorflow does not support operations with sparse matrices.") - elif backend.name == "pytorch": - pytest.skip("Pytorch does not support operations with sparse matrices.") + if backend.platform in ["tensorflow", "pytorch"]: + pytest.skip( + "Tensorflow and Pytorch do not support operations with sparse matrices." + ) from scipy import sparse H1 = hamiltonians.XXZ(nqubits=5, delta=0.5, backend=backend) @@ -402,10 +402,10 @@ def test_hamiltonian_ground_state(backend, sparse_type, dense): if sparse_type is None: H = hamiltonians.XXZ(nqubits=2, delta=0.5, dense=dense, backend=backend) else: - if backend.platform == "tensorflow": - pytest.skip("Tensorflow does not support operations with sparse matrices.") - elif backend.name == "pytorch": - pytest.skip("Pytorch does not support operations with sparse matrices.") + if backend.platform in ["tensorflow", "pytorch"]: + pytest.skip( + "Tensorflow and Pytorch do not support operations with sparse matrices." + ) from scipy import sparse H = hamiltonians.XXZ(nqubits=5, delta=0.5, backend=backend) @@ -434,12 +434,10 @@ def construct_hamiltonian(): if sparse_type is None: return hamiltonians.XXZ(nqubits=2, delta=0.5, dense=dense, backend=backend) else: - if backend.platform == "tensorflow": + if backend.platform in ["tensorflow", "pytorch"]: pytest.skip( - "Tensorflow does not support operations with sparse matrices." + "Tensorflow and Pytorch do not support operations with sparse matrices." ) - elif backend.name == "pytorch": - pytest.skip("Pytorch does not support operations with sparse matrices.") from scipy import sparse ham = hamiltonians.XXZ(nqubits=5, delta=0.5, backend=backend) diff --git a/tests/test_measurements.py b/tests/test_measurements.py index 48151b23ab..af5344fb4f 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -434,7 +434,7 @@ def test_measurement_basis(backend, nqubits, outcome): def test_measurement_basis_list(backend): - c = Circuit(4) + c = Circuit(4, wire_names=["q0", "q1", "q2", "q3"]) c.add(gates.H(0)) c.add(gates.X(2)) c.add(gates.H(2)) diff --git a/tests/test_models_circuit.py b/tests/test_models_circuit.py index c6a72a39ff..b2b2ca904d 100644 --- a/tests/test_models_circuit.py +++ b/tests/test_models_circuit.py @@ -6,6 +6,7 @@ import pytest from qibo import Circuit, gates +from qibo.models.circuit import _resolve_qubits from qibo.models.utils import initialize @@ -48,6 +49,44 @@ def test_circuit_init(): assert c.nqubits == 2 +def test_resolve_qubits(): + nqubits, wire_names = _resolve_qubits(3, None) + assert nqubits == 3 and wire_names is None + nqubits, wire_names = _resolve_qubits(3, ["a", "b", "c"]) + assert nqubits == 3 and wire_names == ["a", "b", "c"] + nqubits, wire_names = _resolve_qubits(["a", "b", "c"], None) + assert nqubits == 3 and wire_names == ["a", "b", "c"] + nqubits, wire_names = _resolve_qubits(None, ["x", "y", "z"]) + assert nqubits == 3 and wire_names == ["x", "y", "z"] + + with pytest.raises(ValueError): + _resolve_qubits(None, None) + with pytest.raises(ValueError): + _resolve_qubits(3, ["a", "b"]) + with pytest.raises(ValueError): + _resolve_qubits(["a", "b", "c"], ["x", "y"]) + + +def test_circuit_init_resolve_qubits(): + a = Circuit(3) + assert a.nqubits == 3 and a.wire_names == [0, 1, 2] + b = Circuit(3, wire_names=["a", "b", "c"]) + assert b.nqubits == 3 and b.wire_names == ["a", "b", "c"] + c = Circuit(["a", "b", "c"]) + assert c.nqubits == 3 and c.wire_names == ["a", "b", "c"] + d = Circuit(wire_names=["x", "y", "z"]) + assert d.nqubits == 3 and d.wire_names == ["x", "y", "z"] + + +def test_circuit_init_resolve_qubits_err(): + with pytest.raises(ValueError): + a = Circuit() + with pytest.raises(ValueError): + b = Circuit(3, wire_names=["a", "b"]) + with pytest.raises(ValueError): + c = Circuit(["a", "b", "c"], wire_names=["x", "y"]) + + def test_eigenstate(backend): nqubits = 3 c = Circuit(nqubits) @@ -625,7 +664,7 @@ def test_circuit_draw(): "q3: ─────────o──|───────o──|────o──|──H─U1───|─x─\n" "q4: ────────────o──────────o───────o────o──H─x───" ) - circuit = Circuit(5) + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): @@ -636,17 +675,19 @@ def test_circuit_draw(): assert str(circuit) == ref -def test_circuit_wire_names_errors(): +def test_circuit_wire_names(): + circuit = Circuit(5) + assert circuit.wire_names == [0, 1, 2, 3, 4] + assert circuit._wire_names == None + + circuit.wire_names = ["a", "b", "c", "d", "e"] + assert circuit.wire_names == ["a", "b", "c", "d", "e"] + assert circuit._wire_names == ["a", "b", "c", "d", "e"] + with pytest.raises(TypeError): - circuit = Circuit(5, wire_names=1) - with pytest.raises(ValueError): - circuit = Circuit(5, wire_names=["a", "b", "c"]) + circuit.wire_names = 5 with pytest.raises(ValueError): - circuit = Circuit(2, wire_names={"q0": "1", "q1": "2", "q2": "3"}) - with pytest.raises(ValueError): - circuit = Circuit(2, wire_names={"q0": "1", "q1": 2}) - with pytest.raises(ValueError): - circuit = Circuit(2, wire_names=["1", 2]) + circuit.wire_names = ["a", "b", "c", "d"] def test_circuit_draw_wire_names(): @@ -669,6 +710,24 @@ def test_circuit_draw_wire_names(): assert str(circuit) == ref +def test_circuit_draw_wire_names_int(): + ref = ( + "2133: ─H─U1─U1─U1─U1───────────────────────────x───\n" + + "8 : ───o──|──|──|──H─U1─U1─U1────────────────|─x─\n" + + "2319: ──────o──|──|────o──|──|──H─U1─U1────────|─|─\n" + + "0 : ─────────o──|───────o──|────o──|──H─U1───|─x─\n" + + "1908: ────────────o──────────o───────o────o──H─x───" + ) + circuit = Circuit(5, wire_names=[2133, 8, 2319, 0, 1908]) + for i1 in range(5): + circuit.add(gates.H(i1)) + for i2 in range(i1 + 1, 5): + circuit.add(gates.CU1(i2, i1, theta=0)) + circuit.add(gates.SWAP(0, 4)) + circuit.add(gates.SWAP(1, 3)) + assert str(circuit) == ref + + def test_circuit_draw_line_wrap(capsys): """Test circuit text draw with line wrap.""" ref_line_wrap_50 = ( @@ -705,7 +764,7 @@ def test_circuit_draw_line_wrap(capsys): + "q4: ... ───" ) - circuit = Circuit(5) + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): @@ -766,7 +825,7 @@ def test_circuit_draw_line_wrap_names(capsys): + "q4: ... ───" ) - circuit = Circuit(5, wire_names={"q1": "a"}) + circuit = Circuit(5, wire_names=["q0", "a", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): @@ -795,7 +854,7 @@ def test_circuit_draw_line_wrap_names(capsys): def test_circuit_draw_channels(capsys, legend): """Check that channels are drawn correctly.""" - circuit = Circuit(2, density_matrix=True) + circuit = Circuit(2, density_matrix=True, wire_names=["q0", "q1"]) circuit.add(gates.H(0)) circuit.add(gates.PauliNoiseChannel(0, list(zip(["X", "Z"], [0.1, 0.2])))) circuit.add(gates.H(1)) @@ -832,7 +891,7 @@ def test_circuit_draw_callbacks(capsys, legend): from qibo.callbacks import EntanglementEntropy entropy = EntanglementEntropy([0]) - c = Circuit(2) + c = Circuit(2, wire_names=["q0", "q1"]) c.add(gates.CallbackGate(entropy)) c.add(gates.H(0)) c.add(gates.CallbackGate(entropy)) @@ -863,7 +922,7 @@ def test_circuit_draw_labels(): + "q3: ─────────o──|───────o──|────o──|──H─G4───|─x─\n" + "q4: ────────────o──────────o───────o────o──H─x───" ) - circuit = Circuit(5) + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): @@ -884,7 +943,7 @@ def test_circuit_draw_names(capsys): + "q3: ─────────o──|───────o──|────o──|──H─cx───|─x─\n" + "q4: ────────────o──────────o───────o────o──H─x───" ) - circuit = Circuit(5) + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): diff --git a/tests/test_models_circuit_fuse.py b/tests/test_models_circuit_fuse.py index 22e9c74f0e..ae90d42b02 100644 --- a/tests/test_models_circuit_fuse.py +++ b/tests/test_models_circuit_fuse.py @@ -228,7 +228,7 @@ def test_fused_gate_draw(): "q3: ───────────────o──|───────────o──|──[─o──H─]─|──[─U1───]─|─x─\n" "q4: ──────────────────o──────────────o───────────o──[─o──H─]─x───" ) - circuit = Circuit(5) + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): diff --git a/tests/test_models_variational.py b/tests/test_models_variational.py index 1c49f4bacf..08031b853f 100644 --- a/tests/test_models_variational.py +++ b/tests/test_models_variational.py @@ -109,13 +109,11 @@ def myloss(parameters, circuit, target): def test_vqe(backend, method, options, compile, filename): """Performs a VQE circuit minimization test.""" if (method == "sgd" or compile) and ( - (backend.name != "pytorch") or (backend.platform != "tensorflow") + backend.platform not in ["tensorflow", "pytorch"] ): pytest.skip("Skipping SGD test for unsupported backend.") - if method != "sgd" and backend.name == "pytorch": - pytest.skip("Skipping scipy optimizers for pytorch.") - if method != "sgd" and backend.platform == "tensorflow": - pytest.skip("Skipping scipy optimizers for tensorflow.") + if method != "sgd" and (backend.platform in ["tensorflow", "pytorch"]): + pytest.skip("Skipping scipy optimizers for pytorch and tensorflow.") n_threads = backend.nthreads backend.set_threads(1) nqubits = 3 @@ -138,7 +136,7 @@ def test_vqe(backend, method, options, compile, filename): initial_parameters = backend.cast( np.random.uniform(0, 2 * np.pi, 2 * nqubits * layers + nqubits), dtype="float64" ) - if backend.name == "pytorch": + if backend.platform == "pytorch": initial_parameters.requires_grad = True v = VQE(circuit, hamiltonian) @@ -281,14 +279,14 @@ def test_qaoa_errors(backend): @pytest.mark.parametrize(test_names, test_values) def test_qaoa_optimization(backend, method, options, dense, filename): - if (method == "sgd") and (backend.name not in ["tensorflow", "pytorch"]): + if (method == "sgd") and (backend.platform not in ["tensorflow", "pytorch"]): pytest.skip("Skipping SGD test for unsupported backend.") - if method != "sgd" and backend.name in ("tensorflow", "pytorch"): + if method != "sgd" and backend.platform in ("tensorflow", "pytorch"): pytest.skip("Skipping scipy optimizers for tensorflow and pytorch.") h = XXZ(3, dense=dense, backend=backend) qaoa = QAOA(h) initial_p = backend.cast([0.05, 0.06, 0.07, 0.08], dtype="float64") - if backend.name == "pytorch": + if backend.platform == "pytorch": initial_p.requires_grad = True best, params, _ = qaoa.minimize(initial_p, method=method, options=options) if filename is not None: diff --git a/tests/test_quantum_info_clifford.py b/tests/test_quantum_info_clifford.py index 0c90039c1a..e39232e264 100644 --- a/tests/test_quantum_info_clifford.py +++ b/tests/test_quantum_info_clifford.py @@ -3,10 +3,10 @@ import numpy as np import pytest -from qiboml.backends import TensorflowBackend +from qiboml.backends import PyTorchBackend, TensorflowBackend from qibo import Circuit, gates, matrices -from qibo.backends import CliffordBackend, PyTorchBackend +from qibo.backends import CliffordBackend from qibo.backends.clifford import _get_engine_name from qibo.quantum_info._clifford_utils import ( _cnot_cost, diff --git a/tests/test_quantum_info_metrics.py b/tests/test_quantum_info_metrics.py index 4e5b9ea849..a4c53dfdfa 100644 --- a/tests/test_quantum_info_metrics.py +++ b/tests/test_quantum_info_metrics.py @@ -394,7 +394,7 @@ def test_frame_potential(backend, nqubits, power_t, samples): @pytest.mark.parametrize("return_complex", [False, True]) @pytest.mark.parametrize("nqubits", [4, 8]) def test_qfim(backend, nqubits, return_complex, params_flag): - if backend.name == "pytorch" or backend.platform == "tensorflow": + if backend.platform in ["tensorflow", "pytorch"]: # QFIM from https://arxiv.org/abs/2405.20408 is known analytically data = np.random.rand(nqubits) data = backend.cast(data, dtype=data.dtype) diff --git a/tests/test_quantum_info_operations.py b/tests/test_quantum_info_operations.py index d25643f9f8..4791ff5df0 100644 --- a/tests/test_quantum_info_operations.py +++ b/tests/test_quantum_info_operations.py @@ -260,7 +260,7 @@ def test_singular_value_decomposition(backend): S_sorted = backend.np.sort(S) coeffs_sorted = backend.np.sort(coeffs) - if backend.name == "pytorch": + if backend.platform == "pytorch": S_sorted, coeffs_sorted = S_sorted[0], coeffs_sorted[0] backend.assert_allclose(S_sorted, coeffs_sorted) diff --git a/tests/test_quantum_info_random.py b/tests/test_quantum_info_random.py index 6dccab6b42..8f2054bade 100644 --- a/tests/test_quantum_info_random.py +++ b/tests/test_quantum_info_random.py @@ -174,7 +174,7 @@ def test_random_unitary(backend, measure): matrix_dagger = backend.np.conj(matrix).T matrix_inv = ( backend.np.inverse(matrix) - if backend.name == "pytorch" + if backend.platform == "pytorch" else np.linalg.inv(matrix) ) norm = float(backend.calculate_matrix_norm(matrix_inv - matrix_dagger, order=2)) diff --git a/tests/test_tomography_gate_set_tomography.py b/tests/test_tomography_gate_set_tomography.py index d6d2ffd2df..be0c4d4fc6 100644 --- a/tests/test_tomography_gate_set_tomography.py +++ b/tests/test_tomography_gate_set_tomography.py @@ -261,7 +261,7 @@ def test_GST_non_invertible_matrix(): matrices = GST(gate_set=[], pauli_liouville=True, gauge_matrix=T) -def test_GST_with_transpiler(backend): +def test_GST_with_transpiler(backend, star_connectivity): import networkx as nx target_gates = [gates.SX(0), gates.Z(0), gates.CNOT(0, 1)] @@ -276,18 +276,15 @@ def test_GST_with_transpiler(backend): transpiler=None, ) # define transpiler - connectivity = nx.Graph() - # star connectivity - connectivity.add_edges_from([(0, 2), (1, 2), (2, 3), (2, 4)]) + connectivity = star_connectivity() transpiler = Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Random(connectivity), - Sabre(connectivity), + Preprocessing(), + Random(), + Sabre(), Unroller(NativeGates.default(), backend=backend), ], - int_qubit_names=True, ) # transpiled GST T_empty_1q, T_empty_2q, *T_approx_gates = GST( diff --git a/tests/test_transpiler_asserts.py b/tests/test_transpiler_asserts.py new file mode 100644 index 0000000000..8b0b268003 --- /dev/null +++ b/tests/test_transpiler_asserts.py @@ -0,0 +1,151 @@ +import pytest + +from qibo import gates +from qibo.models.circuit import Circuit +from qibo.transpiler._exceptions import ( + ConnectivityError, + DecompositionError, + PlacementError, + TranspilerPipelineError, +) +from qibo.transpiler.asserts import ( + assert_circuit_equivalence, + assert_connectivity, + assert_decomposition, + assert_placement, +) +from qibo.transpiler.pipeline import restrict_connectivity_qubits +from qibo.transpiler.unroller import NativeGates + + +def test_assert_circuit_equivalence_equal(): + circ1 = Circuit(2) + circ2 = Circuit(2) + circ1.add(gates.X(0)) + circ1.add(gates.CZ(0, 1)) + circ2.add(gates.X(0)) + circ2.add(gates.CZ(0, 1)) + final_map = {0: 0, 1: 1} + assert_circuit_equivalence(circ1, circ2, final_map=final_map) + + +def test_assert_circuit_equivalence_swap(): + circ1 = Circuit(2) + circ2 = Circuit(2) + circ1.add(gates.X(0)) + circ2.add(gates.SWAP(0, 1)) + circ2.add(gates.X(1)) + final_map = {0: 1, 1: 0} + assert_circuit_equivalence(circ1, circ2, final_map=final_map) + + +def test_assert_circuit_equivalence_false(): + circ1 = Circuit(2) + circ2 = Circuit(2) + circ1.add(gates.X(0)) + circ2.add(gates.SWAP(0, 1)) + circ2.add(gates.X(1)) + final_map = {0: 0, 1: 1} + with pytest.raises(TranspilerPipelineError): + assert_circuit_equivalence(circ1, circ2, final_map=final_map) + + +def test_assert_placement_true(star_connectivity): + circuit = Circuit(5) + assert_placement(circuit, connectivity=star_connectivity()) + + +@pytest.mark.parametrize( + "qubits, names", [(1, ["A", "B", "C", "D", "E"]), (10, ["A", "B", "C", "D", "E"])] +) +def test_assert_placement_false(qubits, names, star_connectivity): + connectivity = star_connectivity() + circuit = Circuit(5) + circuit.nqubits = qubits + circuit._wire_names = names + with pytest.raises(PlacementError): + assert_placement(circuit, connectivity) + + connectivity = None + with pytest.raises(ValueError): + assert_placement(circuit, connectivity) + + +@pytest.mark.parametrize("qubits", [10, 1]) +def test_assert_placement_error(qubits, star_connectivity): + connectivity = star_connectivity() + circuit = Circuit(qubits) + with pytest.raises(PlacementError): + assert_placement(circuit, connectivity) + + +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_assert_placement_restricted(names, star_connectivity): + connectivity = star_connectivity(names) + on_qubit = [names[0], names[2]] + circuit = Circuit(2, wire_names=on_qubit) + restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) + assert_placement(circuit, restricted_connectivity) + + +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_assert_placement_restricted_error(names, star_connectivity): + connectivity = star_connectivity(names) + on_qubit = [names[0], names[2]] + circuit = Circuit(2, wire_names=[names[3], names[4]]) + restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) + with pytest.raises(PlacementError): + assert_placement(circuit, restricted_connectivity) + + +def test_assert_decomposition(): + circuit = Circuit(2) + circuit.add(gates.CZ(0, 1)) + circuit.add(gates.Z(0)) + circuit.add(gates.M(1)) + assert_decomposition(circuit, native_gates=NativeGates.default()) + + +def test_assert_decomposition_fail_1q(): + circuit = Circuit(1) + circuit.add(gates.X(0)) + with pytest.raises(DecompositionError): + assert_decomposition(circuit, native_gates=NativeGates.default()) + + +@pytest.mark.parametrize("gate", [gates.CNOT(0, 1), gates.iSWAP(0, 1)]) +def test_assert_decomposition_fail_2q(gate): + circuit = Circuit(2) + circuit.add(gate) + with pytest.raises(DecompositionError): + assert_decomposition(circuit, native_gates=NativeGates.default()) + + +def test_assert_decomposition_fail_3q(): + circuit = Circuit(3) + circuit.add(gates.TOFFOLI(0, 1, 2)) + with pytest.raises(DecompositionError): + assert_decomposition(circuit, native_gates=NativeGates.default()) + + +def test_assert_connectivity(star_connectivity): + names = ["A", "B", "C", "D", "E"] + circuit = Circuit(5, wire_names=names) + circuit.add(gates.CZ(0, 2)) + circuit.add(gates.CZ(1, 2)) + circuit.add(gates.CZ(2, 1)) + assert_connectivity(star_connectivity(names), circuit) + + +def test_assert_connectivity_false(star_connectivity): + circuit = Circuit(5) + circuit.add(gates.CZ(0, 1)) + with pytest.raises(ConnectivityError): + assert_connectivity(star_connectivity(), circuit) + + +def test_assert_connectivity_3q(star_connectivity): + circuit = Circuit(5) + circuit.add(gates.TOFFOLI(0, 1, 2)) + with pytest.raises(ConnectivityError): + assert_connectivity(star_connectivity(), circuit) diff --git a/tests/test_transpiler_decompositions.py b/tests/test_transpiler_decompositions.py index 44c299cc3b..e26af68841 100644 --- a/tests/test_transpiler_decompositions.py +++ b/tests/test_transpiler_decompositions.py @@ -5,7 +5,8 @@ from qibo.backends import NumpyBackend from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary -from qibo.transpiler.unroller import NativeGates, assert_decomposition, translate_gate +from qibo.transpiler.asserts import assert_decomposition +from qibo.transpiler.unroller import NativeGates, translate_gate default_natives = NativeGates.Z | NativeGates.RZ | NativeGates.M | NativeGates.I diff --git a/tests/test_transpiler_optimizer.py b/tests/test_transpiler_optimizer.py index 171c7516d4..500f507178 100644 --- a/tests/test_transpiler_optimizer.py +++ b/tests/test_transpiler_optimizer.py @@ -6,22 +6,18 @@ from qibo.transpiler.optimizer import Preprocessing, Rearrange -def star_connectivity(): - chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] - chip.add_edges_from(graph_list) - return chip - - -def test_preprocessing_error(): +def test_preprocessing_error(star_connectivity): circ = Circuit(7) preprocesser = Preprocessing(connectivity=star_connectivity()) with pytest.raises(ValueError): new_circuit = preprocesser(circuit=circ) + circ = Circuit(5, wire_names=[0, 1, 2, "q3", "q4"]) + with pytest.raises(ValueError): + new_circuit = preprocesser(circuit=circ) + -def test_preprocessing_same(): +def test_preprocessing_same(star_connectivity): circ = Circuit(5) circ.add(gates.CNOT(0, 1)) preprocesser = Preprocessing(connectivity=star_connectivity()) @@ -29,7 +25,7 @@ def test_preprocessing_same(): assert new_circuit.ngates == 1 -def test_preprocessing_add(): +def test_preprocessing_add(star_connectivity): circ = Circuit(3) circ.add(gates.CNOT(0, 1)) preprocesser = Preprocessing(connectivity=star_connectivity()) diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index bfa13f9e4d..a8e43bc6aa 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -5,23 +5,17 @@ from qibo import gates from qibo.models import Circuit from qibo.transpiler._exceptions import ConnectivityError, TranspilerPipelineError +from qibo.transpiler.asserts import assert_circuit_equivalence, assert_transpiling from qibo.transpiler.optimizer import Preprocessing -from qibo.transpiler.pipeline import ( - Passes, - assert_circuit_equivalence, - assert_transpiling, - restrict_connectivity_qubits, -) -from qibo.transpiler.placer import Random, ReverseTraversal, Trivial +from qibo.transpiler.pipeline import Passes, restrict_connectivity_qubits +from qibo.transpiler.placer import Random, ReverseTraversal from qibo.transpiler.router import Sabre, ShortestPaths from qibo.transpiler.unroller import NativeGates, Unroller -def generate_random_circuit(nqubits, ngates, seed=None): - """Generate random circuits one-qubit rotations and CZ gates.""" - if seed is not None: # pragma: no cover - np.random.seed(seed) - +def generate_random_circuit(nqubits, ngates, names=None, seed=42): + """Generate a random circuit with RX and CZ gates.""" + np.random.seed(seed) one_qubit_gates = [gates.RX, gates.RY, gates.RZ, gates.X, gates.Y, gates.Z, gates.H] two_qubit_gates = [ gates.CNOT, @@ -34,7 +28,7 @@ def generate_random_circuit(nqubits, ngates, seed=None): ] n1, n2 = len(one_qubit_gates), len(two_qubit_gates) n = n1 + n2 if nqubits > 1 else n1 - circuit = Circuit(nqubits) + circuit = Circuit(nqubits, wire_names=names) for _ in range(ngates): igate = int(np.random.randint(0, n)) if igate >= n1: @@ -47,120 +41,58 @@ def generate_random_circuit(nqubits, ngates, seed=None): gate = one_qubit_gates[igate] if issubclass(gate, gates.ParametrizedGate): theta = 2 * np.pi * np.random.random() - circuit.add(gate(*q, theta=theta)) + circuit.add(gate(*q, theta=theta, trainable=False)) else: circuit.add(gate(*q)) return circuit -def star_connectivity(): - chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] - chip.add_edges_from(graph_list) - return chip - - -def test_restrict_qubits_error_no_subset(): +def test_restrict_qubits_error_no_subset(star_connectivity): with pytest.raises(ConnectivityError) as excinfo: - restrict_connectivity_qubits(star_connectivity(), [1, 2, 6]) + restrict_connectivity_qubits(star_connectivity(), [0, 1, 5]) assert "Some qubits are not in the original connectivity." in str(excinfo.value) -def test_restrict_qubits_error_not_connected(): +def test_restrict_qubits_error_not_connected(star_connectivity): with pytest.raises(ConnectivityError) as excinfo: - restrict_connectivity_qubits(star_connectivity(), [1, 3]) + restrict_connectivity_qubits(star_connectivity(), [0, 1]) assert "New connectivity graph is not connected." in str(excinfo.value) -def test_restrict_qubits(): - new_connectivity = restrict_connectivity_qubits(star_connectivity(), [1, 2, 3]) - assert list(new_connectivity.nodes) == [1, 2, 3] - assert list(new_connectivity.edges) == [(1, 2), (2, 3)] - - -@pytest.mark.parametrize("ngates", [5, 10, 50]) -def test_pipeline_default(ngates): - circ = generate_random_circuit(nqubits=5, ngates=ngates) - default_transpiler = Passes(passes=None, connectivity=star_connectivity()) - transpiled_circ, final_layout = default_transpiler(circ) - initial_layout = default_transpiler.get_initial_layout() - assert_transpiling( - original_circuit=circ, - transpiled_circuit=transpiled_circ, - connectivity=star_connectivity(), - initial_layout=initial_layout, - final_layout=final_layout, - native_gates=NativeGates.default(), - check_circuit_equivalence=False, +def test_restrict_qubits(star_connectivity): + new_connectivity = restrict_connectivity_qubits( + star_connectivity(["A", "B", "C", "D", "E"]), ["A", "B", "C"] ) - - -def test_assert_circuit_equivalence_equal(): - circ1 = Circuit(2) - circ2 = Circuit(2) - circ1.add(gates.X(0)) - circ1.add(gates.CZ(0, 1)) - circ2.add(gates.X(0)) - circ2.add(gates.CZ(0, 1)) - final_map = {"q0": 0, "q1": 1} - assert_circuit_equivalence(circ1, circ2, final_map=final_map) - - -def test_assert_circuit_equivalence_swap(): - circ1 = Circuit(2) - circ2 = Circuit(2) - circ1.add(gates.X(0)) - circ2.add(gates.SWAP(0, 1)) - circ2.add(gates.X(1)) - final_map = {"q0": 1, "q1": 0} - assert_circuit_equivalence(circ1, circ2, final_map=final_map) - - -def test_assert_circuit_equivalence_false(): - circ1 = Circuit(2) - circ2 = Circuit(2) - circ1.add(gates.X(0)) - circ2.add(gates.SWAP(0, 1)) - circ2.add(gates.X(1)) - final_map = {"q0": 0, "q1": 1} - with pytest.raises(TranspilerPipelineError): - assert_circuit_equivalence(circ1, circ2, final_map=final_map) - - -def test_int_qubit_names(): - circ = Circuit(2) - final_map = {i: i for i in range(5)} - default_transpiler = Passes( - passes=None, connectivity=star_connectivity(), int_qubit_names=True - ) - _, final_layout = default_transpiler(circ) - assert final_map == final_layout + assert list(new_connectivity.nodes) == ["A", "B", "C"] + assert list(new_connectivity.edges) == [("A", "C"), ("B", "C")] def test_assert_circuit_equivalence_wrong_nqubits(): circ1 = Circuit(1) circ2 = Circuit(2) - final_map = {"q0": 0, "q1": 1} + final_map = {0: 0, 1: 1} with pytest.raises(ValueError): assert_circuit_equivalence(circ1, circ2, final_map=final_map) -def test_error_connectivity(): - with pytest.raises(TranspilerPipelineError): - default_transpiler = Passes(passes=None, connectivity=None) - - @pytest.mark.parametrize("qubits", [3, 5]) -def test_is_satisfied(qubits): - default_transpiler = Passes(passes=None, connectivity=star_connectivity()) - circuit = Circuit(qubits) +def test_is_satisfied(qubits, star_connectivity): + default_transpiler = Passes( + passes=None, connectivity=star_connectivity(), on_qubits=list(range(qubits)) + ) + circuit = Circuit(qubits, wire_names=list(range(qubits))) circuit.add(gates.CZ(0, 2)) circuit.add(gates.Z(0)) assert default_transpiler.is_satisfied(circuit) -def test_is_satisfied_false_decomposition(): +def test_is_satisfied_false_placement(star_connectivity): + default_transpiler = Passes(passes=None, connectivity=star_connectivity()) + circuit = Circuit(5, wire_names=["A", "B", "C", "D", "E"]) + assert not default_transpiler.is_satisfied(circuit) + + +def test_is_satisfied_false_decomposition(star_connectivity): default_transpiler = Passes(passes=None, connectivity=star_connectivity()) circuit = Circuit(5) circuit.add(gates.CZ(0, 2)) @@ -168,7 +100,7 @@ def test_is_satisfied_false_decomposition(): assert not default_transpiler.is_satisfied(circuit) -def test_is_satisfied_false_connectivity(): +def test_is_satisfied_false_connectivity(star_connectivity): default_transpiler = Passes(passes=None, connectivity=star_connectivity()) circuit = Circuit(5) circuit.add(gates.CZ(0, 1)) @@ -176,104 +108,76 @@ def test_is_satisfied_false_connectivity(): assert not default_transpiler.is_satisfied(circuit) -@pytest.mark.parametrize("qubits", [2, 5]) -@pytest.mark.parametrize("gates", [5, 20]) -@pytest.mark.parametrize("placer", [Random, Trivial, ReverseTraversal]) -@pytest.mark.parametrize("routing", [ShortestPaths, Sabre]) -def test_custom_passes(placer, routing, gates, qubits): - circ = generate_random_circuit(nqubits=qubits, ngates=gates) +@pytest.mark.parametrize("nqubits", [2, 3, 5]) +@pytest.mark.parametrize("ngates", [5, 20]) +@pytest.mark.parametrize("placer", [Random, ReverseTraversal]) +@pytest.mark.parametrize("router", [ShortestPaths, Sabre]) +def test_custom_passes(placer, router, ngates, nqubits, star_connectivity): + connectivity = star_connectivity() + circ = generate_random_circuit(nqubits=nqubits, ngates=ngates) custom_passes = [] - custom_passes.append(Preprocessing(connectivity=star_connectivity())) + custom_passes.append(Preprocessing()) if placer == ReverseTraversal: custom_passes.append( placer( - connectivity=star_connectivity(), - routing_algorithm=routing(connectivity=star_connectivity()), + routing_algorithm=router(), ) ) else: - custom_passes.append(placer(connectivity=star_connectivity())) - custom_passes.append(routing(connectivity=star_connectivity())) + custom_passes.append(placer()) + custom_passes.append(router()) custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( - custom_passes, - connectivity=star_connectivity(), + passes=custom_passes, + connectivity=connectivity, native_gates=NativeGates.default(), ) transpiled_circ, final_layout = custom_pipeline(circ) - initial_layout = custom_pipeline.get_initial_layout() assert_transpiling( original_circuit=circ, transpiled_circuit=transpiled_circ, - connectivity=star_connectivity(), - initial_layout=initial_layout, + connectivity=connectivity, final_layout=final_layout, native_gates=NativeGates.default(), ) -@pytest.mark.parametrize("gates", [5, 20]) -@pytest.mark.parametrize("placer", [Random, Trivial, ReverseTraversal]) +@pytest.mark.parametrize("ngates", [5, 20]) +@pytest.mark.parametrize("placer", [Random, ReverseTraversal]) @pytest.mark.parametrize("routing", [ShortestPaths, Sabre]) -def test_custom_passes_restrict(gates, placer, routing): - circ = generate_random_circuit(nqubits=3, ngates=gates) +@pytest.mark.parametrize("restrict_names", [[1, 2, 3], [0, 2, 4], [4, 2, 3]]) +def test_custom_passes_restrict( + ngates, placer, routing, restrict_names, star_connectivity +): + connectivity = star_connectivity() + circ = generate_random_circuit(nqubits=3, ngates=ngates, names=restrict_names) custom_passes = [] - custom_passes.append(Preprocessing(connectivity=star_connectivity())) + custom_passes.append(Preprocessing()) if placer == ReverseTraversal: custom_passes.append( placer( - connectivity=star_connectivity(), - routing_algorithm=routing(connectivity=star_connectivity()), + routing_algorithm=routing(), ) ) else: - custom_passes.append(placer(connectivity=star_connectivity())) - custom_passes.append(routing(connectivity=star_connectivity())) + custom_passes.append(placer()) + custom_passes.append(routing()) custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( - custom_passes, - connectivity=star_connectivity(), + passes=custom_passes, + connectivity=connectivity, native_gates=NativeGates.default(), - on_qubits=[1, 2, 3], + on_qubits=restrict_names, ) transpiled_circ, final_layout = custom_pipeline(circ) - initial_layout = custom_pipeline.get_initial_layout() assert_transpiling( original_circuit=circ, transpiled_circuit=transpiled_circ, - connectivity=restrict_connectivity_qubits(star_connectivity(), [1, 2, 3]), - initial_layout=initial_layout, + connectivity=restrict_connectivity_qubits(star_connectivity(), restrict_names), final_layout=final_layout, native_gates=NativeGates.default(), ) - assert transpiled_circ.wire_names == ["q1", "q2", "q3"] - - -def test_custom_passes_multiple_placer(): - custom_passes = [] - custom_passes.append(Random(connectivity=star_connectivity())) - custom_passes.append(Trivial(connectivity=star_connectivity())) - custom_pipeline = Passes( - custom_passes, - connectivity=star_connectivity(), - native_gates=NativeGates.default(), - ) - circ = generate_random_circuit(nqubits=5, ngates=20) - with pytest.raises(TranspilerPipelineError): - transpiled_circ, final_layout = custom_pipeline(circ) - - -def test_custom_passes_no_placer(): - custom_passes = [] - custom_passes.append(ShortestPaths(connectivity=star_connectivity())) - custom_pipeline = Passes( - custom_passes, - connectivity=star_connectivity(), - native_gates=NativeGates.default(), - ) - circ = generate_random_circuit(nqubits=5, ngates=20) - with pytest.raises(TranspilerPipelineError): - transpiled_circ, final_layout = custom_pipeline(circ) + assert set(transpiled_circ.wire_names) == set(restrict_names) def test_custom_passes_wrong_pass(): @@ -281,32 +185,30 @@ def test_custom_passes_wrong_pass(): custom_pipeline = Passes(passes=custom_passes, connectivity=None) circ = generate_random_circuit(nqubits=5, ngates=5) with pytest.raises(TranspilerPipelineError): - transpiled_circ, final_layout = custom_pipeline(circ) + custom_pipeline(circ) -def test_int_qubit_names(): - connectivity = star_connectivity() +def test_int_qubit_names(star_connectivity): + names = [980, 123, 45, 9, 210464] + connectivity = star_connectivity(names) transpiler = Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Random(connectivity, seed=0), - Sabre(connectivity), + Preprocessing(), + Random(seed=0), + Sabre(), Unroller(NativeGates.default()), ], - int_qubit_names=True, ) - circuit = Circuit(1) + circuit = Circuit(1, wire_names=[123]) circuit.add(gates.I(0)) circuit.add(gates.H(0)) circuit.add(gates.M(0)) transpiled_circuit, final_map = transpiler(circuit) - initial_layout = transpiler.get_initial_layout() assert_transpiling( original_circuit=circuit, transpiled_circuit=transpiled_circuit, connectivity=connectivity, - initial_layout=initial_layout, final_layout=final_map, native_gates=NativeGates.default(), ) diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index 6855a7eeac..13fef50904 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -3,98 +3,26 @@ from qibo import gates from qibo.models import Circuit -from qibo.transpiler._exceptions import PlacementError +from qibo.transpiler._exceptions import PlacementError, TranspilerPipelineError +from qibo.transpiler.asserts import assert_placement from qibo.transpiler.pipeline import restrict_connectivity_qubits from qibo.transpiler.placer import ( - Custom, Random, ReverseTraversal, StarConnectivityPlacer, Subgraph, - Trivial, _find_gates_qubits_pairs, - assert_mapping_consistency, - assert_placement, ) from qibo.transpiler.router import ShortestPaths -def star_connectivity(): - chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] - chip.add_edges_from(graph_list) - return chip - - -def star_circuit(): - circuit = Circuit(5) +def star_circuit(names=[0, 1, 2, 3, 4]): + circuit = Circuit(5, wire_names=names) for i in range(1, 5): circuit.add(gates.CNOT(i, 0)) return circuit -@pytest.mark.parametrize("connectivity", [star_connectivity(), None]) -@pytest.mark.parametrize( - "layout", - [{"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4}, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4}], -) -def test_assert_placement_true(layout, connectivity): - circuit = Circuit(5) - assert_placement(circuit, layout, connectivity=connectivity) - - -@pytest.mark.parametrize("qubits", [5, 3]) -@pytest.mark.parametrize( - "layout", [{"q0": 0, "q1": 1, "q2": 2, "q3": 3}, {"q0": 0, "q0": 1, "q2": 2}] -) -def test_assert_placement_false(qubits, layout): - circuit = Circuit(qubits) - with pytest.raises(PlacementError): - assert_placement(circuit, layout) - - -@pytest.mark.parametrize( - "layout", - [{"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4}, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4}], -) -def test_mapping_consistency(layout): - assert_mapping_consistency(layout) - - -@pytest.mark.parametrize( - "layout", - [ - {"q0": 0, "q1": 0, "q2": 1, "q3": 4, "q4": 3}, - {"q0": 0, "q1": 2, "q0": 1, "q3": 4, "q4": 3}, - ], -) -def test_mapping_consistency_error(layout): - with pytest.raises(PlacementError): - assert_mapping_consistency(layout) - - -def test_mapping_consistency_restricted(): - layout = {"q0": 0, "q2": 1} - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2]) - assert_mapping_consistency(layout, restricted_connectivity) - - -@pytest.mark.parametrize( - "layout", - [ - {"q0": 0, "q2": 2}, - {"q0": 0, "q1": 1}, - ], -) -def test_mapping_consistency_restricted_error(layout): - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2]) - with pytest.raises(PlacementError): - assert_mapping_consistency(layout, restricted_connectivity) - - def test_gates_qubits_pairs(): circuit = Circuit(5) circuit.add(gates.CNOT(0, 1)) @@ -111,99 +39,13 @@ def test_gates_qubits_pairs_error(): gates_qubits_pairs = _find_gates_qubits_pairs(circuit) -def test_trivial(): - circuit = Circuit(5) - connectivity = star_connectivity() - placer = Trivial(connectivity=connectivity) - layout = placer(circuit) - assert layout == {"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4} - assert_placement(circuit, layout) - - -def test_trivial_restricted(): - circuit = Circuit(2) - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2]) - placer = Trivial(connectivity=restricted_connectivity) - layout = placer(circuit) - assert layout == {"q0": 0, "q2": 1} - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) - - -def test_trivial_error(): - circuit = Circuit(4) - connectivity = star_connectivity() - placer = Trivial(connectivity=connectivity) - with pytest.raises(PlacementError): - layout = placer(circuit) - - -@pytest.mark.parametrize( - "custom_layout", [[4, 3, 2, 1, 0], {"q0": 4, "q1": 3, "q2": 2, "q3": 1, "q4": 0}] -) -@pytest.mark.parametrize("give_circuit", [True, False]) -@pytest.mark.parametrize("give_connectivity", [True, False]) -def test_custom(custom_layout, give_circuit, give_connectivity): - if give_circuit: - circuit = Circuit(5) - else: - circuit = None - if give_connectivity: - connectivity = star_connectivity() - else: - connectivity = None - placer = Custom(connectivity=connectivity, initial_map=custom_layout) - layout = placer(circuit) - assert layout == {"q0": 4, "q1": 3, "q2": 2, "q3": 1, "q4": 0} - - -@pytest.mark.parametrize("custom_layout", [[1, 0], {"q0": 1, "q2": 0}]) -def test_custom_restricted(custom_layout): - circuit = Circuit(2) - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2]) - placer = Custom(connectivity=restricted_connectivity, initial_map=custom_layout) - layout = placer(circuit) - assert layout == {"q0": 1, "q2": 0} - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) - - -def test_custom_error_circuit(): - circuit = Circuit(3) - custom_layout = [4, 3, 2, 1, 0] - connectivity = star_connectivity() - placer = Custom(connectivity=connectivity, initial_map=custom_layout) - with pytest.raises(PlacementError): - layout = placer(circuit) - - -def test_custom_error_no_circuit(): - connectivity = star_connectivity() - custom_layout = {"q0": 4, "q1": 3, "q2": 2, "q3": 0, "q4": 0} - placer = Custom(connectivity=connectivity, initial_map=custom_layout) - with pytest.raises(PlacementError): - layout = placer() - - -def test_custom_error_type(): - circuit = Circuit(5) - connectivity = star_connectivity() - layout = 1 - placer = Custom(connectivity=connectivity, initial_map=layout) - with pytest.raises(TypeError): - layout = placer(circuit) - - -def test_subgraph_perfect(): +def test_subgraph_perfect(star_connectivity): connectivity = star_connectivity() placer = Subgraph(connectivity=connectivity) - layout = placer(star_circuit()) - assert layout["q2"] == 0 - assert_placement(star_circuit(), layout) + circuit = star_circuit() + placer(circuit) + assert circuit.wire_names[0] == 2 + assert_placement(circuit, connectivity) def imperfect_circuit(): @@ -220,23 +62,24 @@ def imperfect_circuit(): return circuit -def test_subgraph_non_perfect(): +def test_subgraph_non_perfect(star_connectivity): connectivity = star_connectivity() placer = Subgraph(connectivity=connectivity) - layout = placer(imperfect_circuit()) - assert_placement(imperfect_circuit(), layout) + circuit = imperfect_circuit() + placer(circuit) + assert_placement(circuit, connectivity) -def test_subgraph_error(): +def test_subgraph_error(star_connectivity): connectivity = star_connectivity() placer = Subgraph(connectivity=connectivity) circuit = Circuit(5) with pytest.raises(ValueError): - layout = placer(circuit) + placer(circuit) -def test_subgraph_restricted(): - circuit = Circuit(4) +def test_subgraph_restricted(star_connectivity): + circuit = Circuit(4, wire_names=[0, 2, 3, 4]) circuit.add(gates.CNOT(0, 3)) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.CNOT(3, 2)) @@ -246,66 +89,57 @@ def test_subgraph_restricted(): connectivity = star_connectivity() restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2, 3, 4]) placer = Subgraph(connectivity=restricted_connectivity) - layout = placer(circuit) - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) + placer(circuit) + assert_placement(circuit, restricted_connectivity) @pytest.mark.parametrize("reps", [1, 10, 100]) -def test_random(reps): - connectivity = star_connectivity() +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_random(reps, names, star_connectivity): + connectivity = star_connectivity(names) placer = Random(connectivity=connectivity, samples=reps) - layout = placer(star_circuit()) - assert_placement(star_circuit(), layout) - - -def test_random_perfect(): - circ = Circuit(5) - circ.add(gates.CZ(0, 1)) - connectivity = star_connectivity() - placer = Random(connectivity=connectivity, samples=1000) - layout = placer(circ) - assert_placement(star_circuit(), layout) + circuit = star_circuit(names=names) + placer(circuit) + assert_placement(circuit, connectivity) -def test_random_restricted(): - circuit = Circuit(4) +def test_random_restricted(star_connectivity): + names = [0, 1, 2, 3, 4] + circuit = Circuit(4, wire_names=[0, 2, 3, 4]) circuit.add(gates.CNOT(1, 3)) circuit.add(gates.CNOT(2, 1)) circuit.add(gates.CNOT(3, 2)) circuit.add(gates.CNOT(2, 1)) circuit.add(gates.CNOT(1, 2)) circuit.add(gates.CNOT(3, 1)) - connectivity = star_connectivity() + connectivity = star_connectivity(names) restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2, 3, 4]) placer = Random(connectivity=restricted_connectivity, samples=100) - layout = placer(circuit) - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) + placer(circuit) + assert_placement(circuit, restricted_connectivity) -@pytest.mark.parametrize("gates", [None, 5, 13]) -def test_reverse_traversal(gates): - circuit = star_circuit() - connectivity = star_connectivity() +@pytest.mark.parametrize("ngates", [None, 5, 13]) +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_reverse_traversal(ngates, names, star_connectivity): + circuit = star_circuit(names=names) + connectivity = star_connectivity(names=names) routing = ShortestPaths(connectivity=connectivity) - placer = ReverseTraversal(connectivity, routing, depth=gates) - layout = placer(circuit) - assert_placement(circuit, layout) + placer = ReverseTraversal(routing, connectivity, depth=ngates) + placer(circuit) + assert_placement(circuit, connectivity) -def test_reverse_traversal_no_gates(): +def test_reverse_traversal_no_gates(star_connectivity): connectivity = star_connectivity() routing = ShortestPaths(connectivity=connectivity) - placer = ReverseTraversal(connectivity, routing, depth=10) + placer = ReverseTraversal(routing, connectivity, depth=10) circuit = Circuit(5) with pytest.raises(ValueError): - layout = placer(circuit) + placer(circuit) -def test_reverse_traversal_restricted(): +def test_reverse_traversal_restricted(star_connectivity): circuit = Circuit(4) circuit.add(gates.CNOT(1, 3)) circuit.add(gates.CNOT(2, 1)) @@ -314,34 +148,42 @@ def test_reverse_traversal_restricted(): circuit.add(gates.CNOT(1, 2)) circuit.add(gates.CNOT(3, 1)) connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2, 3, 4]) + restrict_names = [0, 2, 3, 4] + restricted_connectivity = restrict_connectivity_qubits(connectivity, restrict_names) + circuit.wire_names = restrict_names routing = ShortestPaths(connectivity=restricted_connectivity) placer = ReverseTraversal( connectivity=restricted_connectivity, routing_algorithm=routing, depth=5 ) - layout = placer(circuit) - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) + placer(circuit) + assert_placement(circuit, restricted_connectivity) -def test_star_connectivity_placer(): - circ = Circuit(3) +def test_star_connectivity_placer(star_connectivity): + circ = Circuit(5) circ.add(gates.CZ(0, 1)) circ.add(gates.CZ(1, 2)) circ.add(gates.CZ(0, 2)) - placer = StarConnectivityPlacer(middle_qubit=2) - layout = placer(circ) - assert_placement(circ, layout) - assert layout == {"q0": 0, "q1": 2, "q2": 1} + connectivity = star_connectivity() + placer = StarConnectivityPlacer(connectivity) + placer(circ) + assert_placement(circ, connectivity) + assert circ.wire_names == [0, 2, 1, 3, 4] @pytest.mark.parametrize("first", [True, False]) -def test_star_connectivity_placer_error(first): - circ = Circuit(3) +def test_star_connectivity_placer_error(first, star_connectivity): + circ = Circuit(5) if first: circ.add(gates.CZ(0, 1)) circ.add(gates.TOFFOLI(0, 1, 2)) - placer = StarConnectivityPlacer(middle_qubit=2) + connectivity = star_connectivity() + placer = StarConnectivityPlacer(connectivity) with pytest.raises(PlacementError): - layout = placer(circ) + placer(circ) + + chip = nx.Graph() + chip.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)]) + with pytest.raises(ValueError): + placer = StarConnectivityPlacer(chip) + placer(circ) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 80b0e72aa3..06899e78d3 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -9,61 +9,48 @@ from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler._exceptions import ConnectivityError -from qibo.transpiler.blocks import Block -from qibo.transpiler.optimizer import Preprocessing -from qibo.transpiler.pipeline import ( +from qibo.transpiler.asserts import ( + _transpose_qubits, assert_circuit_equivalence, - restrict_connectivity_qubits, -) -from qibo.transpiler.placer import ( - Custom, - Random, - StarConnectivityPlacer, - Subgraph, - Trivial, + assert_connectivity, assert_placement, ) +from qibo.transpiler.pipeline import restrict_connectivity_qubits +from qibo.transpiler.placer import Random, StarConnectivityPlacer, Subgraph from qibo.transpiler.router import ( CircuitMap, Sabre, ShortestPaths, StarConnectivityRouter, - assert_connectivity, ) -def star_connectivity(middle_qubit=2): - chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, middle_qubit) for i in range(5) if i != middle_qubit] - chip.add_edges_from(graph_list) - return chip - - -def grid_connectivity(): +def line_connectivity(n, names=None): + if names is None: + names = list(range(n)) chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 4)] + chip.add_nodes_from(names) + graph_list = [(names[i], names[i + 1]) for i in range(n - 1)] chip.add_edges_from(graph_list) return chip -def line_connectivity(n): - chip = nx.Graph() - chip.add_nodes_from(list(range(n))) - graph_list = [(i, i + 1) for i in range(n - 1)] - chip.add_edges_from(graph_list) - return chip - - -def generate_random_circuit(nqubits, ngates, seed=42): +def generate_random_circuit(nqubits, ngates, names=None, seed=42): """Generate a random circuit with RX and CZ gates.""" np.random.seed(seed) - one_qubit_gates = [gates.RX, gates.RY, gates.RZ] - two_qubit_gates = [gates.CZ, gates.CNOT, gates.SWAP] + one_qubit_gates = [gates.RX, gates.RY, gates.RZ, gates.X, gates.Y, gates.Z, gates.H] + two_qubit_gates = [ + gates.CNOT, + gates.CZ, + gates.SWAP, + gates.iSWAP, + gates.CRX, + gates.CRY, + gates.CRZ, + ] n1, n2 = len(one_qubit_gates), len(two_qubit_gates) n = n1 + n2 if nqubits > 1 else n1 - circuit = Circuit(nqubits) + circuit = Circuit(nqubits, wire_names=names) for _ in range(ngates): igate = int(np.random.randint(0, n)) if igate >= n1: @@ -89,9 +76,9 @@ def star_circuit(): return circuit -def matched_circuit(): +def matched_circuit(names): """Return a simple circuit that can be executed on star connectivity""" - circuit = Circuit(5) + circuit = Circuit(5, wire_names=names) circuit.add(gates.CZ(0, 2)) circuit.add(gates.CZ(1, 2)) circuit.add(gates.Z(1)) @@ -100,111 +87,138 @@ def matched_circuit(): return circuit -def test_assert_connectivity(): - assert_connectivity(star_connectivity(), matched_circuit()) +def test_bell_state_3q(): + circuit = Circuit(3) + circuit.add(gates.H(0)) + circuit.add(gates.CNOT(0, 2)) + circuit.add(gates.X(0)) + circuit.add(gates.M(0, 1, 2)) + c = circuit.copy() + connectivity = line_connectivity(3, None) + router = Sabre(connectivity=connectivity) + routed_circuit, final_map = router(c) -def test_assert_connectivity_false(): - circuit = Circuit(5) - circuit.add(gates.CZ(0, 1)) - with pytest.raises(ConnectivityError): - assert_connectivity(star_connectivity(), circuit) + backend = NumpyBackend() + state = np.array([1, 0, 0, 0, 0, 0, 0, 0]) + original_state = backend.execute_circuit(circuit, state).state() + target_state = backend.execute_circuit(routed_circuit, state).state() + target_state = _transpose_qubits(target_state, list(final_map.values())) + assert np.all(np.isclose(np.real(original_state), np.real(target_state))) -def test_assert_connectivity_3q(): - circuit = Circuit(5) - circuit.add(gates.TOFFOLI(0, 1, 2)) - with pytest.raises(ConnectivityError): - assert_connectivity(star_connectivity(), circuit) +@pytest.mark.parametrize("ngates", [5, 25]) +def test_random_circuits_5q(ngates, star_connectivity): + connectivity = star_connectivity() + placer = Random(connectivity) + transpiler = ShortestPaths(connectivity) + circuit = generate_random_circuit(nqubits=5, ngates=ngates) + original_circuit = circuit.copy() + placer(circuit) + transpiled_circuit, final_qubit_map = transpiler(circuit) + + assert transpiler.added_swaps >= 0 + assert_connectivity(connectivity, transpiled_circuit) + assert_placement(transpiled_circuit, connectivity) + assert ngates + transpiler.added_swaps == transpiled_circuit.ngates + assert_circuit_equivalence( + original_circuit=original_circuit, + transpiled_circuit=transpiled_circuit, + final_map=final_qubit_map, + ) + + +@pytest.mark.parametrize("ngates", [5, 25]) +def test_random_circuits_5q_grid(ngates, grid_connectivity): + connectivity = grid_connectivity() + placer = Random(connectivity) + transpiler = ShortestPaths(connectivity) + + circuit = generate_random_circuit(nqubits=5, ngates=ngates) + original_circuit = circuit.copy() + placer(circuit) + transpiled_circuit, final_qubit_map = transpiler(circuit) -@pytest.mark.parametrize("gates", [5, 25]) -@pytest.mark.parametrize("placer", [Trivial, Random]) -@pytest.mark.parametrize("connectivity", [star_connectivity(), grid_connectivity()]) -def test_random_circuits_5q(gates, placer, connectivity): - placer = placer(connectivity=connectivity) - layout_circ = Circuit(5) - initial_layout = placer(layout_circ) - transpiler = ShortestPaths(connectivity=connectivity) - circuit = generate_random_circuit(nqubits=5, ngates=gates) - transpiled_circuit, final_qubit_map = transpiler(circuit, initial_layout) assert transpiler.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) - assert gates + transpiler.added_swaps == transpiled_circuit.ngates - qubit_matcher = Preprocessing(connectivity=connectivity) - new_circuit = qubit_matcher(circuit=circuit) + assert_placement(transpiled_circuit, connectivity) + assert ngates + transpiler.added_swaps == transpiled_circuit.ngates assert_circuit_equivalence( - original_circuit=new_circuit, + original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) -def test_random_circuits_15q_50g(): - nqubits, ngates = 15, 50 - connectivity = line_connectivity(nqubits) +@pytest.mark.parametrize("nqubits", [11, 12, 13, 14, 15]) +@pytest.mark.parametrize("ngates", [30, 50]) +def test_random_circuits_15q_50g(nqubits, ngates): + connectivity = line_connectivity(nqubits, None) placer = Random(connectivity=connectivity) - layout_circ = Circuit(nqubits) - initial_layout = placer(layout_circ) transpiler = Sabre(connectivity=connectivity) circuit = generate_random_circuit(nqubits=nqubits, ngates=ngates) - transpiled_circuit, final_qubit_map = transpiler(circuit, initial_layout) + original_circuit = circuit.copy() + + placer(circuit) + transpiled_circuit, final_qubit_map = transpiler(circuit) + assert transpiler.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) + assert_placement(transpiled_circuit, connectivity) assert ngates + transpiler.added_swaps == transpiled_circuit.ngates - qubit_matcher = Preprocessing(connectivity=connectivity) - new_circuit = qubit_matcher(circuit=circuit) assert_circuit_equivalence( - original_circuit=new_circuit, + original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) -def test_star_circuit(): - placer = Subgraph(star_connectivity()) - initial_layout = placer(star_circuit()) - transpiler = ShortestPaths(connectivity=star_connectivity()) - transpiled_circuit, final_qubit_map = transpiler(star_circuit(), initial_layout) +def test_star_circuit(star_connectivity): + connectivity = star_connectivity() + circuit = star_circuit() + placer = Subgraph(connectivity=connectivity) + transpiler = ShortestPaths(connectivity=connectivity) + + placer(circuit) + transpiled_circuit, final_qubit_map = transpiler(circuit) + assert transpiler.added_swaps == 0 assert_connectivity(star_connectivity(), transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) + assert_placement(transpiled_circuit, connectivity) assert_circuit_equivalence( original_circuit=star_circuit(), transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) -def test_star_circuit_custom_map(): - placer = Custom(initial_map=[1, 0, 2, 3, 4], connectivity=star_connectivity()) - initial_layout = placer() - transpiler = ShortestPaths(connectivity=star_connectivity()) - transpiled_circuit, final_qubit_map = transpiler(star_circuit(), initial_layout) +def test_star_circuit_custom_map(star_connectivity): + connectivity = star_connectivity() + circuit = star_circuit() + circuit.wire_names = [1, 0, 2, 3, 4] + transpiler = ShortestPaths(connectivity=connectivity) + transpiled_circuit, final_qubit_map = transpiler(circuit) + assert transpiler.added_swaps == 1 assert_connectivity(star_connectivity(), transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) + assert_placement(transpiled_circuit, connectivity) assert_circuit_equivalence( original_circuit=star_circuit(), transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) -def test_routing_with_measurements(): - placer = Trivial(connectivity=star_connectivity()) +def test_routing_with_measurements(star_connectivity): + connectivity = star_connectivity() circuit = Circuit(5) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.M(0, 2, 3)) - initial_layout = placer(circuit=circuit) - transpiler = ShortestPaths(connectivity=star_connectivity()) - transpiled_circuit, final_qubit_map = transpiler(circuit, initial_layout) + + transpiler = ShortestPaths(connectivity) + transpiled_circuit, final_qubit_map = transpiler(circuit) + assert transpiled_circuit.ngates == 3 measured_qubits = transpiled_circuit.queue[2].qubits assert measured_qubits == (0, 1, 3) @@ -212,37 +226,26 @@ def test_routing_with_measurements(): original_circuit=circuit, transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) def test_sabre_looping(): # Setup where the looping occurs - # Line connectivity, gates with gate_array, Trivial placer + # Line connectivity, gates with gate_array + + connectivity = line_connectivity(10, None) gate_array = [(7, 2), (6, 0), (5, 6), (4, 8), (3, 5), (9, 1)] loop_circ = Circuit(10) for qubits in gate_array: loop_circ.add(gates.CZ(*qubits)) - chip = nx.Graph() - chip.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - chip.add_edges_from( - [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)] - ) - - placer = Trivial(connectivity=chip) - initial_layout = placer(loop_circ) router_no_threshold = Sabre( - connectivity=chip, swap_threshold=np.inf + connectivity=connectivity, swap_threshold=np.inf ) # Without reset - router_threshold = Sabre(connectivity=chip) # With reset + router_threshold = Sabre(connectivity=connectivity) # With reset - routed_no_threshold, final_mapping_no_threshold = router_no_threshold( - loop_circ, initial_layout=initial_layout - ) - routed_threshold, final_mapping_threshold = router_threshold( - loop_circ, initial_layout=initial_layout - ) + routed_no_threshold, final_mapping_no_threshold = router_no_threshold(loop_circ) + routed_threshold, final_mapping_threshold = router_threshold(loop_circ) count_no_threshold = router_no_threshold.added_swaps count_threshold = router_threshold.added_swaps @@ -252,13 +255,11 @@ def test_sabre_looping(): original_circuit=loop_circ, transpiled_circuit=routed_no_threshold, final_map=final_mapping_no_threshold, - initial_map=initial_layout, ) assert_circuit_equivalence( original_circuit=loop_circ, transpiled_circuit=routed_threshold, final_map=final_mapping_threshold, - initial_map=initial_layout, ) @@ -269,18 +270,11 @@ def test_sabre_shortest_path_routing(): for qubits in gate_array: loop_circ.add(gates.CZ(*qubits)) - # line connectivity - chip = nx.Graph() - chip.add_nodes_from(range(10)) - chip.add_edges_from( - [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)] - ) + connectivity = line_connectivity(10, None) - placer = Trivial(connectivity=chip) - initial_layout = placer(loop_circ) - router = Sabre(connectivity=chip) + router = Sabre(connectivity) - router._preprocessing(circuit=loop_circ, initial_layout=initial_layout) + router._preprocessing(circuit=loop_circ) router._shortest_path_routing() # q2 should be moved adjacent to q8 gate_28 = router.circuit_map.circuit_blocks.block_list[2] @@ -300,14 +294,15 @@ def test_circuit_map(): circ.add(gates.CZ(1, 2)) circ.add(gates.CZ(0, 1)) circ.add(gates.CZ(2, 3)) - initial_layout = {"q0": 2, "q1": 0, "q2": 1, "q3": 3} - circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) + circ.wire_names = ["q1", "q2", "q0", "q3"] + + circuit_map = CircuitMap(circuit=circ) block_list = circuit_map.circuit_blocks # test blocks_qubits_pairs assert circuit_map.blocks_logical_qubits_pairs() == [(0, 1), (1, 2), (0, 1), (2, 3)] # test execute_block and routed_circuit circuit_map.execute_block(block_list.search_by_index(0)) - routed_circuit = circuit_map.routed_circuit() + routed_circuit = circuit_map.routed_circuit(circuit_kwargs=circ.init_kwargs) assert isinstance(routed_circuit.queue[0], gates.H) assert len(routed_circuit.queue) == 4 qubits = routed_circuit.queue[2].qubits @@ -318,7 +313,7 @@ def test_circuit_map(): # test update 1 circuit_map.update((0, 2)) - routed_circuit = circuit_map.routed_circuit() + routed_circuit = circuit_map.routed_circuit(circuit_kwargs=circ.init_kwargs) assert isinstance(routed_circuit.queue[4], gates.SWAP) qubits = routed_circuit.queue[4].qubits assert ( @@ -326,12 +321,12 @@ def test_circuit_map(): and routed_circuit.wire_names[qubits[1]] == "q0" ) assert circuit_map._swaps == 1 - assert circuit_map.physical_to_logical == [0, 2, 1, 3] - assert circuit_map.logical_to_physical == [0, 2, 1, 3] + assert circuit_map.physical_to_logical == [2, 1, 0, 3] + assert circuit_map.logical_to_physical == [2, 1, 0, 3] # test update 2 circuit_map.update((1, 2)) - routed_circuit = circuit_map.routed_circuit() + routed_circuit = circuit_map.routed_circuit(circuit_kwargs=circ.init_kwargs) assert isinstance(routed_circuit.queue[5], gates.SWAP) qubits = routed_circuit.queue[5].qubits assert ( @@ -339,14 +334,14 @@ def test_circuit_map(): and routed_circuit.wire_names[qubits[1]] == "q1" ) assert circuit_map._swaps == 2 - assert circuit_map.physical_to_logical == [0, 1, 2, 3] - assert circuit_map.logical_to_physical == [0, 1, 2, 3] + assert circuit_map.physical_to_logical == [1, 2, 0, 3] + assert circuit_map.logical_to_physical == [2, 0, 1, 3] - # # test execute_block after multiple swaps + # test execute_block after multiple swaps circuit_map.execute_block(block_list.search_by_index(1)) circuit_map.execute_block(block_list.search_by_index(2)) circuit_map.execute_block(block_list.search_by_index(3)) - routed_circuit = circuit_map.routed_circuit() + routed_circuit = circuit_map.routed_circuit(circuit_kwargs=circ.init_kwargs) assert isinstance(routed_circuit.queue[6], gates.CZ) qubits = routed_circuit.queue[6].qubits @@ -366,83 +361,112 @@ def test_circuit_map(): ) assert len(circuit_map.circuit_blocks()) == 0 # test final layout - assert circuit_map.final_layout() == {"q0": 0, "q1": 1, "q2": 2, "q3": 3} + assert circuit_map.final_layout() == {"q0": 1, "q1": 2, "q2": 0, "q3": 3} -def test_sabre_matched(): - placer = Trivial() - layout_circ = Circuit(5) - initial_layout = placer(layout_circ) - router = Sabre(connectivity=star_connectivity()) - routed_circuit, final_map = router( - circuit=matched_circuit(), initial_layout=initial_layout - ) +@pytest.mark.parametrize( + "names", + [["q0", "q1", "q2", "q3", "q4"], [0, 1, 2, 3, 4], ["A", "B", "C", "D", "E"]], +) +def test_sabre_matched(names, star_connectivity): + connectivity = star_connectivity(names=names) + circuit = matched_circuit(names) + original_circuit = circuit.copy() + + router = Sabre(connectivity=connectivity) + routed_circuit, final_map = router(circuit) + assert router.added_swaps == 0 - assert final_map == {"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4} - assert_connectivity(circuit=routed_circuit, connectivity=star_connectivity()) + assert_connectivity(circuit=routed_circuit, connectivity=connectivity) assert_circuit_equivalence( - original_circuit=matched_circuit(), + original_circuit=original_circuit, transpiled_circuit=routed_circuit, final_map=final_map, - initial_map=initial_layout, ) @pytest.mark.parametrize("seed", [42]) -def test_sabre_simple(seed): - placer = Trivial() +def test_sabre_simple(seed, star_connectivity): + connectivity = star_connectivity() circ = Circuit(5) circ.add(gates.CZ(0, 1)) - initial_layout = placer(circ) - router = Sabre(connectivity=star_connectivity(), seed=seed) - routed_circuit, final_map = router(circuit=circ, initial_layout=initial_layout) + original_circuit = circ.copy() + + router = Sabre(connectivity=connectivity, seed=seed) + routed_circuit, final_map = router(circ) + assert router.added_swaps == 1 - assert final_map == {"q0": 2, "q1": 1, "q2": 0, "q3": 3, "q4": 4} + assert final_map == {0: 2, 1: 1, 2: 0, 3: 3, 4: 4} assert routed_circuit.queue[0].qubits == (0, 2) assert isinstance(routed_circuit.queue[0], gates.SWAP) assert isinstance(routed_circuit.queue[1], gates.CZ) - assert_connectivity(circuit=routed_circuit, connectivity=star_connectivity()) + assert_connectivity(circuit=routed_circuit, connectivity=connectivity) assert_circuit_equivalence( - original_circuit=circ, + original_circuit=original_circuit, transpiled_circuit=routed_circuit, final_map=final_map, - initial_map=initial_layout, ) @pytest.mark.parametrize("n_gates", [10, 40]) @pytest.mark.parametrize("look", [0, 5]) @pytest.mark.parametrize("decay", [0.5, 1.0]) -@pytest.mark.parametrize("placer", [Trivial, Random]) -@pytest.mark.parametrize("connectivity", [star_connectivity(), grid_connectivity()]) -def test_sabre_random_circuits(n_gates, look, decay, placer, connectivity): - placer = placer(connectivity=connectivity) - layout_circ = Circuit(5) - initial_layout = placer(layout_circ) - router = Sabre(connectivity=connectivity, lookahead=look, decay_lookahead=decay) +def test_sabre_random_circuits(n_gates, look, decay, star_connectivity): + connectivity = star_connectivity() circuit = generate_random_circuit(nqubits=5, ngates=n_gates) measurement = gates.M(*range(5)) circuit.add(measurement) - transpiled_circuit, final_qubit_map = router(circuit, initial_layout) + original_circuit = circuit.copy() + placer = Random(connectivity) + router = Sabre(connectivity, lookahead=look, decay_lookahead=decay) + + placer(circuit) + transpiled_circuit, final_qubit_map = router(circuit) + assert router.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) + assert_placement(transpiled_circuit, connectivity) assert n_gates + router.added_swaps + 1 == transpiled_circuit.ngates assert_circuit_equivalence( - original_circuit=circuit, + original_circuit=original_circuit, + transpiled_circuit=transpiled_circuit, + final_map=final_qubit_map, + ) + assert transpiled_circuit.queue[-1].register_name == measurement.register_name + + +@pytest.mark.parametrize("n_gates", [10, 40]) +@pytest.mark.parametrize("look", [0, 5]) +@pytest.mark.parametrize("decay", [0.5, 1.0]) +def test_sabre_random_circuits_grid(n_gates, look, decay, grid_connectivity): + connectivity = grid_connectivity() + circuit = generate_random_circuit(nqubits=5, ngates=n_gates) + measurement = gates.M(*range(5)) + circuit.add(measurement) + original_circuit = circuit.copy() + placer = Random(connectivity) + router = Sabre(connectivity, lookahead=look, decay_lookahead=decay) + + placer(circuit) + transpiled_circuit, final_qubit_map = router(circuit) + + assert router.added_swaps >= 0 + assert_connectivity(connectivity, transpiled_circuit) + assert_placement(transpiled_circuit, connectivity) + assert n_gates + router.added_swaps + 1 == transpiled_circuit.ngates + assert_circuit_equivalence( + original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) assert transpiled_circuit.queue[-1].register_name == measurement.register_name -def test_sabre_memory_map(): - placer = Trivial() +def test_sabre_memory_map(star_connectivity): + connectivity = star_connectivity() layout_circ = Circuit(5) - initial_layout = placer(layout_circ) - router = Sabre(connectivity=star_connectivity()) - router._preprocessing(circuit=star_circuit(), initial_layout=initial_layout) + router = Sabre(connectivity=connectivity) + router._preprocessing(circuit=star_circuit()) router._memory_map = [[1, 0, 2, 3, 4]] value = router._compute_cost((0, 1)) assert value == float("inf") @@ -458,53 +482,65 @@ def test_sabre_intermediate_measurements(): connectivity.add_nodes_from([0, 1, 2]) connectivity.add_edges_from([(0, 1), (1, 2)]) router = Sabre(connectivity=connectivity) - initial_layout = {"q0": 0, "q1": 1, "q2": 2} - routed_circ, _ = router(circuit=circ, initial_layout=initial_layout) + routed_circ, _ = router(circuit=circ) assert routed_circ.queue[3].register_name == measurement.register_name @pytest.mark.parametrize("router_algorithm", [Sabre, ShortestPaths]) -def test_restrict_qubits(router_algorithm): +def test_restrict_qubits(router_algorithm, star_connectivity): circ = Circuit(3) circ.add(gates.CZ(0, 1)) circ.add(gates.CZ(0, 2)) circ.add(gates.CZ(2, 1)) - initial_layout = {"q0": 0, "q2": 2, "q3": 1} - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2, 3]) + circ.wire_names = ["q0", "q3", "q2"] + connectivity = star_connectivity(["q0", "q1", "q2", "q3", "q4"]) + restricted_connectivity = restrict_connectivity_qubits( + connectivity, ["q0", "q2", "q3"] + ) router = router_algorithm(connectivity=restricted_connectivity) - routed_circ, final_layout = router(circuit=circ, initial_layout=initial_layout) + routed_circ, final_layout = router(circuit=circ) assert_circuit_equivalence( original_circuit=circ, transpiled_circuit=routed_circ, final_map=final_layout, - initial_map=initial_layout, ) assert_connectivity(restricted_connectivity, routed_circ) - assert_placement(routed_circ, final_layout, connectivity=restricted_connectivity) - assert routed_circ.wire_names == ["q0", "q2", "q3"] + assert_placement(routed_circ, connectivity=restricted_connectivity) + assert routed_circ.wire_names == ["q0", "q3", "q2"] -def test_star_error_multi_qubit(): - circuit = Circuit(3) +def test_star_error_multi_qubit(star_connectivity): + circuit = Circuit(5) circuit.add(gates.TOFFOLI(0, 1, 2)) - transpiler = StarConnectivityRouter(middle_qubit=2) + connectivity = star_connectivity(middle_qubit_idx=2) + transpiler = StarConnectivityRouter(connectivity) with pytest.raises(ConnectivityError): - transpiled, hardware_qubits = transpiler( - initial_layout={"q0": 0, "q1": 1, "q2": 2}, circuit=circuit - ) + transpiled, hardware_qubits = transpiler(circuit=circuit) + + chip = nx.Graph() + chip.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)]) + with pytest.raises(ValueError): + router = StarConnectivityRouter(chip) + _, _ = router(circuit=circuit) -@pytest.mark.parametrize("nqubits", [1, 3, 5]) +# @pytest.mark.parametrize("nqubits", [1, 3, 5]) +@pytest.mark.parametrize("nqubits", [5]) @pytest.mark.parametrize("middle_qubit", [0, 2, 4]) @pytest.mark.parametrize("depth", [2, 10]) @pytest.mark.parametrize("measurements", [True, False]) @pytest.mark.parametrize("unitaries", [True, False]) -def test_star_router(nqubits, depth, middle_qubit, measurements, unitaries): +@pytest.mark.parametrize( + "names", + [["q0", "q1", "q2", "q3", "q4"], [0, 1, 2, 3, 4], ["A", "B", "C", "D", "E"]], +) +def test_star_router( + nqubits, depth, middle_qubit, measurements, unitaries, names, star_connectivity +): unitary_dim = min(2, nqubits) - connectivity = star_connectivity(middle_qubit) + connectivity = star_connectivity(names=names, middle_qubit_idx=middle_qubit) if unitaries: - circuit = Circuit(nqubits) + circuit = Circuit(nqubits, wire_names=names) pairs = list(itertools.combinations(range(nqubits), unitary_dim)) for _ in range(depth): qubits = pairs[int(np.random.randint(len(pairs)))] @@ -514,32 +550,28 @@ def test_star_router(nqubits, depth, middle_qubit, measurements, unitaries): ) ) else: - circuit = generate_random_circuit(nqubits, depth) + circuit = generate_random_circuit(nqubits, depth, names) if measurements: circuit.add(gates.M(0)) - transpiler = StarConnectivityRouter(middle_qubit=middle_qubit) - placer = StarConnectivityPlacer(middle_qubit=middle_qubit) - initial_layout = placer(circuit=circuit) - transpiled_circuit, final_qubit_map = transpiler( - circuit=circuit, initial_layout=initial_layout - ) + original_circuit = circuit.copy() + transpiler = StarConnectivityRouter(connectivity) + placer = StarConnectivityPlacer(connectivity) + + placer(circuit) + transpiled_circuit, final_qubit_map = transpiler(circuit) assert_connectivity(connectivity, transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) - matched_original = Circuit(max(circuit.nqubits, middle_qubit + 1)) - for gate in circuit.queue: - matched_original.add(gate) + assert_placement(transpiled_circuit, connectivity) assert_circuit_equivalence( - original_circuit=matched_original, + original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) def test_undo(): circ = Circuit(4) - initial_layout = {"q0": 0, "q1": 1, "q2": 2, "q3": 3} - circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) + circ.wire_names = ["q0", "q1", "q2", "q3"] + circuit_map = CircuitMap(circuit=circ) # Two SWAP gates are added circuit_map.update((1, 2)) @@ -572,8 +604,8 @@ def test_circuitmap_no_circuit(): def test_logical_to_physical_setter(): circ = Circuit(4) - initial_layout = {"q0": 0, "q1": 3, "q2": 2, "q3": 1} - circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) + circ.wire_names = ["q0", "q3", "q2", "q1"] + circuit_map = CircuitMap(circuit=circ) circuit_map.logical_to_physical = [2, 0, 1, 3] assert circuit_map.logical_to_physical == [2, 0, 1, 3] assert circuit_map.physical_to_logical == [1, 2, 0, 3] diff --git a/tests/test_transpiler_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py index 49f8cf2fe9..25a0ec25f3 100644 --- a/tests/test_transpiler_unitary_decompositions.py +++ b/tests/test_transpiler_unitary_decompositions.py @@ -3,7 +3,6 @@ from scipy.linalg import expm from qibo import Circuit, gates, matrices -from qibo.config import PRECISION_TOL from qibo.quantum_info.linalg_operations import partial_trace from qibo.quantum_info.metrics import purity from qibo.quantum_info.random_ensembles import random_unitary diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index e09147b179..181a348e82 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -3,12 +3,8 @@ from qibo import gates from qibo.models import Circuit from qibo.transpiler._exceptions import DecompositionError -from qibo.transpiler.unroller import ( - NativeGates, - Unroller, - assert_decomposition, - translate_gate, -) +from qibo.transpiler.asserts import assert_decomposition +from qibo.transpiler.unroller import NativeGates, Unroller, translate_gate def test_native_gates_from_gatelist(): @@ -43,36 +39,6 @@ def test_translate_gate_error_2q(): translate_gate(gates.CZ(0, 1), natives) -def test_assert_decomposition(): - circuit = Circuit(2) - circuit.add(gates.CZ(0, 1)) - circuit.add(gates.Z(0)) - circuit.add(gates.M(1)) - assert_decomposition(circuit, native_gates=NativeGates.default()) - - -def test_assert_decomposition_fail_1q(): - circuit = Circuit(1) - circuit.add(gates.X(0)) - with pytest.raises(DecompositionError): - assert_decomposition(circuit, native_gates=NativeGates.default()) - - -@pytest.mark.parametrize("gate", [gates.CNOT(0, 1), gates.iSWAP(0, 1)]) -def test_assert_decomposition_fail_2q(gate): - circuit = Circuit(2) - circuit.add(gate) - with pytest.raises(DecompositionError): - assert_decomposition(circuit, native_gates=NativeGates.default()) - - -def test_assert_decomposition_fail_3q(): - circuit = Circuit(3) - circuit.add(gates.TOFFOLI(0, 1, 2)) - with pytest.raises(DecompositionError): - assert_decomposition(circuit, native_gates=NativeGates.default()) - - @pytest.mark.parametrize( "natives_2q", [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], @@ -146,8 +112,8 @@ def test_temp_cnot_decomposition(): glist = [gates.GPI2, gates.RZ, gates.Z, gates.M, gates.CNOT] native_gates = NativeGates(0).from_gatelist(glist) - custom_pipeline = Passes([Unroller(native_gates=native_gates)]) - transpiled_circuit, _ = custom_pipeline(circ) + unroller = Unroller(native_gates=native_gates) + transpiled_circuit = unroller(circ) # H assert transpiled_circuit.queue[0].name == "z"