diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index 3ca9e40f37..9bc87c8621 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -2591,61 +2591,39 @@ size circuits you may benefit from single thread per process, thus set Backends -------- -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. +:class:`qibo.backends.abstract.Backend` is the main calculation engine to execute circuits. +Qibo provides backends for quantum simulation on classical hardware, as well as quantum hardware management and control. +For a complete list of available backends, refer to the :ref:`Packages ` section. +To create new backends, inherit from :class:`qibo.backends.abstract.Backend` and implement +its abstract methods. This abstract class defines the required methods for circuit execution. -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. -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. - -We refer to :ref:`Packages ` section for a complete list of the -available computation backends and instructions on how to install each of -these libraries on top of qibo. - -Custom operators are much faster than implementations based on numpy or Tensorflow -primitives, such as ``einsum``, but do not support some features, such as -automatic differentiation for backpropagation of variational circuits which is -only supported by the native ``tensorflow`` backend. - -The user can switch backends using +The user can set the backend using the :func:`qibo.set_backend` function. .. code-block:: python import qibo qibo.set_backend("qibojit") + + # Switch to the numpy backend qibo.set_backend("numpy") -before creating any circuits or gates. The default backend is the first available -from ``qibojit``, ``pytorch``, ``tensorflow``, ``numpy``. -Some backends support different platforms. For example, the qibojit backend +If no backend is specified, the default backend is used. +The default backend is selected in the following order: ``qibojit``, ``numpy``, and ``qiboml``. +The list of default backend candidates can be changed using the ``QIBO_BACKEND`` environment variable. + +Some backends support different platforms. For example, the ``qibojit`` backend provides two platforms (``cupy`` and ``cuquantum``) when used on GPU. -The active platform can be switched using +The platform can be specified using the ``platform`` parameter in the :func:`qibo.set_backend` function. .. code-block:: python import qibo qibo.set_backend("qibojit", platform="cuquantum") - qibo.set_backend("qibojit", platform="cupy") -The default backend order is qibojit (if available), tensorflow (if available), -numpy. The default backend can be changed using the ``QIBO_BACKEND`` environment -variable. - -Qibo optionally provides an interface to `qulacs `_ through the :class:`qibo.backends.qulacs.QulacsBackend`. To use ``qulacs`` for simulating a quantum circuit you can globally set the backend as in the other cases - -.. testcode:: python - - import qibo - qibo.set_backend("qulacs") - -.. note:: - GPU simulation through ``qulacs`` is not supported yet. + # Switch to the cupy platform + qibo.set_backend("qibojit", platform="cupy") .. autoclass:: qibo.backends.abstract.Backend :members: diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 21e25e2465..a54df4ec17 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -5,13 +5,13 @@ Here are a few short advanced `how to` examples. .. _gpu-examples: -How to select hardware devices? -------------------------------- +How to select a classical hardware device for circuit execution? +---------------------------------------------------------------- -Qibo supports execution on different hardware configurations including CPU with +Qibo supports execution on different classical hardware configurations including CPU with multi-threading, single GPU and multiple GPUs. Here we provide some useful information on how to control the devices that Qibo uses for circuit execution -in order to maximize performance for the available hardware configuration. +in order to maximize performance for the available classical hardware configuration. Switching between CPU and GPU ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -145,6 +145,65 @@ however the user may create the full state as follows: # ``final_state`` is a ``tf.Tensor`` +How to select a quantum hardware device for circuit execution? +-------------------------------------------------------------- + +Qibolab is the dedicated Qibo backend for quantum hardware control. +For installation instructions, see the `Qibolab Documentation `_. +The ``Platform`` class in Qibolab represents a QPU device controlled by one or more instruments. +By specifying the platform name, the user can select the quantum hardware device for circuit execution. +When executing the circuit, it will be automatically transpiled using the :ref:`Default Transpiler `. + +.. code-block:: python + + from qibo import set_backend + + # Set the backend and platform + set_backend("qibolab", platform="dummy") + +How to select specific hardware qubits for circuit execution? +------------------------------------------------------------- + +The :class:`qibo.models.Circuit` has a ``wire_names`` property that stores the physical names of the qubits in the circuit. +The physical qubit name ``wire_names[i]`` is assigned to the ``i`` th qubit in the circuit. +Users can specify the hardware qubits to be used by setting the ``wire_names``. +During circuit execution, Qibolab uses the ``wire_names`` to find the corresponding hardware qubits. + + +.. code-block:: python + + from qibo import Circuit, gates + + # Create a circuit with 4 qubits and assign physical qubit names + circuit = Circuit(4, wire_names=["C", "A", "B", "D"]) + + # wire_names can also be set after circuit initialization + # circuit.wire_names = ["C", "A", "B", "D"] + + # Add gates to the circuit + circuit.add(gates.H(0)) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.CZ(0, 2)) + circuit.add(gates.M(2, 3)) + + # This is equivalent to: + # H gate on qubit "C" + # CNOT gate on qubits "C" and "A" + # CZ gate on qubits "C" and "B" + # M gate on qubit "B" and "D" + +For example, if the user sets ``wire_names`` to ``["C", "A", "B", "D"]``, +it means that the first qubit in the circuit is mapped to the physical qubit named ``C``, +the second qubit is mapped to ``A``, and so on. + +If the circuit with the given ``wire_names`` does not meet the hardware device's constraints +(e.g., connectivity and native gate requirements), +the :ref:`default transpiler ` will automatically modify +the circuit to satisfy these constraints. +In this case, different hardware qubits may be used to execute the circuit. +If the user disables the default transpiler, executing the circuit will result in compilation errors. + + How to use callbacks? --------------------- @@ -2129,47 +2188,28 @@ rather each term is treated separately every time. .. _tutorials_transpiler: -How to modify the transpiler? ------------------------------ - -Logical quantum circuits for quantum algorithms are hardware agnostic. Usually an all-to-all qubit connectivity -is assumed while most current hardware only allows the execution of two-qubit gates on a restricted subset of qubit -pairs. Moreover, quantum devices are restricted to executing a subset of gates, referred to as native. -This means that, in order to execute circuits on a real quantum chip, they must be transformed into an equivalent, -hardware specific, circuit. The transformation of the circuit is carried out by the transpiler through the resolution -of two key steps: connectivity matching and native gates decomposition. -In order to execute a gate between two qubits that are not directly connected SWAP gates are required. This procedure is called routing. -As on NISQ devices two-qubit gates are a large source of noise, this procedure generates an overall noisier circuit. -Therefore, the goal of an efficient routing algorithm is to minimize the number of SWAP gates introduced. -An important step to ease the connectivity problem, is finding anoptimal initial mapping between logical and physical qubits. -This step is called placement. -The native gates decomposition in the transpiling procedure is performed by the unroller. An optimal decomposition uses the least amount -of two-qubit native gates. It is also possible to reduce the number of gates of the resulting circuit by exploiting -commutation relations, KAK decomposition or machine learning techniques. -Qibo implements a built-in transpiler with customizable options for each step. The main algorithms that can -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: -- 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. -- Reverse traversal: this technique uses one or more reverse routing passes to find an optimal mapping by -starting from a trivial layout. - -The routing problem can be solved with the following algorithms: -- Shortest paths: when unconnected logical qubits have to interact, they are moved on the chip on -the shortest path connecting them. When multiple shortest paths are present, the one that also matches -the largest number of the following two-qubit gates is chosen. -- Sabre: this heuristic routing technique uses a customizable cost function to add SWAP gates -that reduce the distance between unconnected qubits involved in two-qubit gates. - -Qibolab unroller applies recursively a set of hard-coded gates decompositions in order to translate any gate into -single and two-qubit native gates. Single qubit gates are translated into U3, RX, RZ, X and Z gates. It is possible to -fuse multiple single qubit gates acting on the same qubit into a single U3 gate. For the two-qubit native gates it -is possible to use CZ and/or iSWAP. When both CZ and iSWAP gates are available the chosen decomposition is the -one that minimizes the use of two-qubit gates. - -Multiple transpilation steps can be implemented using the :class:`qibo.transpiler.pipeline.Pipeline`: +How to transpile a circuit? +--------------------------- + +Quantum hardware has a specific qubit connectivity and a set of native gates that it can execute. +Circuit transpilation is the process of converting a quantum circuit into an equivalent one +that can be executed on a given hardware configuration. This is done by applying several passes. +The main passes are: + +- Optimization: Simplifies the circuit by removing redundant gates. +- Placement: Maps logical qubits to physical qubits to minimize the number of required SWAP gates during routing. +- Routing: Makes all gates executable by adding SWAP gates where necessary. +- Unrolling: Decomposes gates into the native gates of the hardware. + +Each pass has various algorithms. Since transpilation introduces additional gates, +it is important for users to select the most efficient algorithms for their specific application. +Qibo provides a built-in transpiler :class:`qibo.transpiler.pipeline.Passes`, +which can be customized by adding the desired transpilation algorithms. + +In this example, we used :class:`qibo.transpiler.optimizer.Preprocessing`, +:class:`qibo.transpiler.placer.Random`, :class:`qibo.transpiler.router.ShortestPaths`, +and :class:`qibo.transpiler.unroller.Unroller` passes to transpile the circuit +on a star-shaped hardware connectivity and a custom set of native gates. .. testcode:: python @@ -2184,36 +2224,33 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile from qibo.transpiler.placer import Random from qibo.transpiler.asserts import assert_transpiling - # Define connectivity as nx.Graph + # Define hardware connectivity using nx.Graph def star_connectivity(): chip = nx.Graph([("q0", "q2"), ("q1", "q2"), ("q2", "q3"), ("q2", "q4")]) return chip - # Define the circuit - # 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"]) + # Define a custom set of native gates + gate_list = [gates.GPI2, gates.RZ, gates.Z, gates.CZ] + natives = NativeGates(0).from_gatelist(gate_list) + + # Create a quantum circuit with 5 qubits + # Define the hardware qubit names to be used in wire_names + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) circuit.add(gates.H(0)) - circuit.add(gates.CZ(0, 1)) - - # Define custom passes as a list - custom_passes = [] - # Preprocessing adds qubits in the original circuit to match the number of qubits in the chip - custom_passes.append(Preprocessing(connectivity=star_connectivity())) - # Placement step - custom_passes.append(Random(connectivity=star_connectivity())) - # Routing step - custom_passes.append(ShortestPaths(connectivity=star_connectivity())) - # Gate decomposition step - custom_passes.append(Unroller(native_gates=NativeGates.default())) - - # Define the general pipeline - custom_pipeline = Passes(custom_passes, connectivity=star_connectivity(), native_gates=NativeGates.default()) - - # Call the transpiler pipeline on the circuit + circuit.add(gates.CZ(0, 2)) + circuit.add(gates.CZ(3, 4)) + circuit.add(gates.X(1)) + + # Define a custom list of passes for the transpiler + custom_passes = [Preprocessing(), Random(), ShortestPaths(), Unroller(native_gates=natives)] + + # Create a transpiler pipeline with hardware configuration + custom_pipeline = Passes(custom_passes, connectivity=star_connectivity()) + + # Transpile the circuit using the custom transpiler pipeline transpiled_circ, final_layout = custom_pipeline(circuit) - # Optinally call assert_transpiling to check that the final circuit can be executed on hardware + # Verify that the transpiled circuit can be executed on the hardware (optional) assert_transpiling( original_circuit=circuit, transpiled_circuit=transpiled_circ, @@ -2222,10 +2259,69 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile native_gates=NativeGates.default() ) -In this case circuits will first be transpiled to respect the 5-qubit star connectivity, with qubit 2 as the middle qubit. This will potentially add some SWAP gates. -Then all gates will be converted to native. The :class:`qibo.transpiler.unroller.Unroller` transpiler used in this example assumes Z, RZ, GPI2 or U3 as -the single-qubit native gates, and supports CZ and iSWAP as two-qubit natives. In this case we restricted the two-qubit gate set to CZ only. -The final_layout contains the final logical-physical qubit mapping. + +In the current Qibo version, transpiler passes require that +``wire_names`` match the qubit names in the given connectivity graph. +This can be done manually or by using :class:`qibo.transpiler.optimizer.Preprocessing` +or the ``on_qubits`` parameter. + +.. note:: + + - :class:`qibo.transpiler.optimizer.Preprocessing` pads the circuit with the remaining qubits from the connectivity graph. + - The ``on_qubits`` parameter in :class:`qibo.transpiler.pipeline.Passes` restricts the connectivity graph. + + +.. _tutorials_set_transpiler: + +How to attach a transpiler to a backend? +---------------------------------------- + +The transpiler can be attached to the current backend using the ``set_transpiler`` method. +Once set, the transpiler will automatically transpile the circuit before each circuit execution. + +.. testcode:: python + + from qibo import set_transpiler + from qibo.transpiler import ( + NativeGates, + Passes, + Preprocessing, + ShortestPaths, + Unroller, + ) + + # Define hardware connectivity + def star_connectivity(): + chip = nx.Graph([("q0", "q2"), ("q1", "q2"), ("q2", "q3"), ("q2", "q4")]) + return chip + + # Define a custom set of native gates + glist = [gates.GPI2, gates.RZ, gates.Z, gates.CZ] + natives = NativeGates(0).from_gatelist(glist) + + # Define a custom transpiler pipeline + custom_passes = [Preprocessing(), Random(), ShortestPaths(), Unroller(native_gates=natives)] + custom_pipeline = Passes(custom_passes, connectivity=star_connectivity()) + + # Attach the transpiler to the current backend + set_transpiler(custom_pipeline) + +If the user does not explicitly set a transpiler, the default transpiler is used. + +* For simulator backends, the default transpiler has no passes, so no transpilation is done. + +* For hardware backends, the default transpiler includes the :class:`qibo.transpiler.optimizer.Preprocessing`, +:class:`qibo.transpiler.router.Sabre`, and :class:`qibo.transpiler.unroller.Unroller` passes, +configured with the backend's connectivity and native gates. + +Setting an empty transpiler is equivalent to disabling transpilation. + +.. testcode:: python + + from qibo import set_transpiler + from qibo.transpiler.pipeline import Passes + + set_transpiler(Passes()) .. _gst_example: diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 31f8570d88..9c70a61796 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -120,8 +120,17 @@ class Circuit: A specific backend has to be used for performing calculations. Circuits can be created with a specific number of qubits and 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. + + 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"]) @@ -130,12 +139,7 @@ class Circuit: Args: 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. - + wire_names (list, optional): List of wire names init_kwargs (dict): a dictionary with the following keys - *nqubits* @@ -299,7 +303,7 @@ def wire_names(self, wire_names: Optional[list]): if len(wire_names) != self.nqubits: raise_error( ValueError, - "Number of wire names must be equal to the number of qubits, " + f"Number of wire names must be equal to the number of qubits ({self.nqubits}), " f"but is {len(wire_names)}.", ) self._wire_names = wire_names.copy() diff --git a/src/qibo/transpiler/__init__.py b/src/qibo/transpiler/__init__.py index 40675f0be1..5a37d8818d 100644 --- a/src/qibo/transpiler/__init__.py +++ b/src/qibo/transpiler/__init__.py @@ -7,4 +7,4 @@ Subgraph, ) from qibo.transpiler.router import Sabre, ShortestPaths, StarConnectivityRouter -from qibo.transpiler.unroller import NativeGates +from qibo.transpiler.unroller import NativeGates, Unroller diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index 11e1641759..58ec054153 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -7,46 +7,60 @@ class Placer(ABC): + """Maps logical qubits to physical qubits.""" + @abstractmethod def __init__(self, connectivity: nx.Graph, *args): - """A placer implements the initial logical-physical qubit mapping""" + """Initializes the placer. + + Args: + connectivity (nx.Graph): Hardware topology. + """ @abstractmethod def __call__(self, circuit: Circuit, *args): - """Find initial qubit mapping. Mapping is saved in the circuit. + """Find initial qubit mapping. + + Method works in-place. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be mapped. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be placed. """ class Router(ABC): + """Makes the circuit executable on the given topology.""" + @abstractmethod def __init__(self, connectivity: nx.Graph, *args): - """A router implements the mapping of a circuit on a specific hardware.""" + """Initializes the router. + + Args: + connectivity (nx.Graph): Hardware topology. + """ @abstractmethod def __call__(self, circuit: Circuit, *args) -> Tuple[Circuit, dict]: """Match circuit to hardware connectivity. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be routed. Returns: - (:class:`qibo.models.circuit.Circuit`): routed circuit. + (:class:`qibo.models.circuit.Circuit`, dict): Routed circuit and final logical-physical qubit mapping. """ class Optimizer(ABC): - """An optimizer tries to reduce the number of gates during transpilation.""" + """Reduces the number of gates in the circuit.""" @abstractmethod def __call__(self, circuit: Circuit, *args) -> Circuit: """Optimize transpiled circuit. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be optimized + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be optimized. Returns: - (:class:`qibo.models.circuit.Circuit`): circuit with optimized number of gates. + (:class:`qibo.models.circuit.Circuit`): Optimized circuit. """ diff --git a/src/qibo/transpiler/asserts.py b/src/qibo/transpiler/asserts.py index d988ce6add..d47168b586 100644 --- a/src/qibo/transpiler/asserts.py +++ b/src/qibo/transpiler/asserts.py @@ -29,12 +29,14 @@ def assert_transpiling( """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. + original_circuit (:class:`qibo.models.circuit.Circuit`): Circuit before transpiling. + transpiled_circuit (:class:`qibo.models.circuit.Circuit`): Circuit after transpiling. + connectivity (:class:`networkx.Graph`): Hardware connectivity. + final_layout (dict): Final logical-physical qubit mapping. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`, optional): Native gates supported by the hardware. + Defaults to :class:`qibo.transpiler.unroller.NativeGates.default()`. + check_circuit_equivalence (bool, optional): Check if the transpiled circuit is equivalent to the original one. + Defaults to :math:`True`. """ assert_connectivity(circuit=transpiled_circuit, connectivity=connectivity) assert_decomposition( @@ -50,32 +52,33 @@ def assert_transpiling( assert_circuit_equivalence( original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, - final_map=final_layout, + final_layout=final_layout, ) def assert_circuit_equivalence( original_circuit: Circuit, transpiled_circuit: Circuit, - final_map: dict, + final_layout: dict, test_states: Optional[list] = None, ntests: int = 3, ): - """Checks that the transpiled circuit agrees with the original using simulation. + """Checks that the transpiled circuit is equivalent to the original one. 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. + original_circuit (:class:`qibo.models.circuit.Circuit`): Circuit before transpiling. + transpiled_circuit (:class:`qibo.models.circuit.Circuit`): Circuit after transpiling. + final_layout (dict): Final logical-physical qubit mapping. + test_states (list, optional): List of states to test the equivalence. If ``None``, ``ntests`` random states will be tested. Defauts to ``None``. - ntests (int, optional): number of random states tested. Defauts to :math:`3`. + ntests (int, optional): Number of random states to test the equivalence. Defaults 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.", + f"Transpiled circuit ({transpiled_circuit.nqubits}) and original circuit " + + f"({original_circuit.nqubits}) do not have the same number of qubits.", ) if test_states is None: @@ -84,7 +87,7 @@ def assert_circuit_equivalence( for _ in range(ntests) ] - ordering = list(final_map.values()) + ordering = list(final_layout.values()) for i, state in enumerate(test_states): target_state = backend.execute_circuit( @@ -106,8 +109,8 @@ 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. + state (np.ndarray): State vector to reorder. + qubits_ordering (np.ndarray): Final qubit ordering. """ original_shape = state.shape state = np.reshape(state, len(qubits_ordering) * (2,)) @@ -119,13 +122,13 @@ 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. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to check. + connectivity (:class:`networkx.Graph`, optional): Hardware connectivity. """ if connectivity is None: raise_error( ValueError, - "Connectivity graph is missing.", + "Connectivity graph is not provided", ) if circuit.nqubits != len(circuit.wire_names) or circuit.nqubits != len( @@ -151,8 +154,8 @@ def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): 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. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to check. + connectivity (:class:`networkx.Graph`): Hardware connectivity. """ layout = circuit.wire_names for gate in circuit.queue: @@ -174,9 +177,8 @@ def assert_decomposition( """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. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to check. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`): Native gates supported by the hardware. """ for gate in circuit.queue: if isinstance(gate, gates.M): diff --git a/src/qibo/transpiler/optimizer.py b/src/qibo/transpiler/optimizer.py index 741078e5e9..4f46325c16 100644 --- a/src/qibo/transpiler/optimizer.py +++ b/src/qibo/transpiler/optimizer.py @@ -9,10 +9,10 @@ class Preprocessing(Optimizer): - """Match the number of qubits of the circuit with the number of qubits of the chip if possible. + """Pad the circuit with unused qubits to match the number of physical qubits. Args: - connectivity (:class:`networkx.Graph`): hardware chip connectivity. + connectivity (:class:`networkx.Graph`): Hardware connectivity. """ def __init__(self, connectivity: Optional[nx.Graph] = None): @@ -22,7 +22,7 @@ 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.", + "Some wire_names in the circuit are not in the connectivity graph.", ) physical_qubits = self.connectivity.number_of_nodes() @@ -30,8 +30,8 @@ def __call__(self, circuit: Circuit) -> Circuit: if logical_qubits > physical_qubits: raise_error( ValueError, - "The number of qubits in the circuit can't be greater " - + "than the number of physical qubits.", + f"The number of qubits in the circuit ({logical_qubits}) " + + f"can't be greater than the number of physical qubits ({physical_qubits}).", ) if logical_qubits == physical_qubits: return circuit @@ -50,7 +50,7 @@ class Rearrange(Optimizer): but this has not been tested. Args: - max_qubits (int, optional): maximum number of qubits to fuse gates. + max_qubits (int, optional): Maximum number of qubits to fuse. Defaults to :math:`1`. """ diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index c1aae06775..689d026aef 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -20,11 +20,11 @@ def restrict_connectivity_qubits(connectivity: nx.Graph, qubits: list[str]): """Restrict the connectivity to selected qubits. Args: - connectivity (:class:`networkx.Graph`): chip connectivity. - qubits (list): list of physical qubits to be used. + connectivity (:class:`networkx.Graph`): Hardware connectivity. + qubits (list): List of qubits to select. Returns: - (:class:`networkx.Graph`): restricted connectivity. + (:class:`networkx.Graph`): New restricted connectivity. """ if not set(qubits).issubset(set(connectivity.nodes)): raise_error( @@ -48,14 +48,13 @@ class Passes: """Define a transpiler pipeline consisting of smaller transpiler steps that are applied sequentially: Args: - passes (list, optional): list of passes to be applied sequentially. - If ``None``, default transpiler will be used. - Defaults to ``None``. - connectivity (:class:`networkx.Graph`, optional): physical qubits connectivity. - 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``. + passes (list, optional): List of transpiler passes to be applied sequentially. + If ``None``, default transpiler will be used. Defaults to ``None``. + connectivity (:class:`networkx.Graph`, optional): Hardware connectivity. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`, optional): Native gates supported by the hardware. + Defaults to :class:`qibo.transpiler.unroller.NativeGates.default()`. + on_qubits (list, optional): List of qubits to be used in the transpiler. + If ``None``, all qubits in the connectivity will be used. Defaults to ``None``. """ def __init__( @@ -72,9 +71,13 @@ def __init__( 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. + """Apply the transpiler pipeline to the circuit. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be transpiled. + + Returns: + (:class:`qibo.models.circuit.Circuit`, dict): Transpiled circuit and final logical-physical qubit mapping. """ final_layout = None @@ -98,13 +101,13 @@ def __call__(self, circuit): return circuit, final_layout def is_satisfied(self, circuit: Circuit): - """Returns ``True`` if the circuit respects the hardware connectivity and native gates, ``False`` otherwise. + """Check if the circuit respects the hardware connectivity and native gates. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be checked. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be checked. Returns: - (bool): satisfiability condition. + (bool): ``True`` if the circuit respects the hardware connectivity and native gates, ``False`` otherwise. """ try: assert_placement(circuit=circuit, connectivity=self.connectivity) diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 48e2cbd8ec..1399f85ea2 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -17,7 +17,7 @@ def _find_gates_qubits_pairs(circuit: Circuit): Translate circuit into a list of pairs of qubits to be used by the router and placer. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be translated. Returns: (list): Pairs of qubits targeted by two qubits gates. @@ -45,7 +45,7 @@ class StarConnectivityPlacer(Placer): q Args: - connectivity (:class:`networkx.Graph`): star connectivity graph. + connectivity (:class:`networkx.Graph`): Star connectivity graph. """ def __init__(self, connectivity: Optional[nx.Graph] = None): @@ -111,7 +111,7 @@ class Subgraph(Placer): This initialization method may fail for very short circuits. Attributes: - connectivity (:class:`networkx.Graph`): chip connectivity. + connectivity (:class:`networkx.Graph`): Hardware connectivity. """ def __init__(self, connectivity: Optional[nx.Graph] = None): @@ -122,7 +122,7 @@ def __call__(self, circuit: Circuit): Circuit must contain at least two two-qubit gates to implement subgraph placement. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be transpiled. """ assert_placement(circuit, self.connectivity) gates_qubits_pairs = _find_gates_qubits_pairs(circuit) @@ -164,8 +164,8 @@ class Random(Placer): gates can be applied without introducing any SWAP gate. Attributes: - connectivity (:class:`networkx.Graph`): chip connectivity. - samples (int, optional): number of initial random layouts tested. + connectivity (:class:`networkx.Graph`): Hardware connectivity. + samples (int, optional): Number of random initializations to try. Defaults to :math:`100`. seed (int or :class:`numpy.random.Generator`, optional): Either a generator of random numbers or a fixed seed to initialize a generator. If ``None``, @@ -219,8 +219,8 @@ def _cost(self, graph: nx.Graph, gates_qubits_pairs: list): Compute the cost associated to an initial layout as the lengh of the reduced circuit. Args: - graph (:class:`networkx.Graph`): current hardware qubit mapping. - gates_qubits_pairs (list): circuit representation. + graph (:class:`networkx.Graph`): Hardware connectivity. + gates_qubits_pairs (list): Circuit representation. Returns: (int): lengh of the reduced circuit. @@ -239,9 +239,9 @@ class ReverseTraversal(Placer): Compatible with all the available ``Router``s. Args: - connectivity (:class:`networkx.Graph`): chip connectivity. - routing_algorithm (:class:`qibo.transpiler.abstract.Router`): routing algorithm. - depth (int, optional): number of two-qubit gates considered before finding initial layout. + connectivity (:class:`networkx.Graph`): Hardware connectivity. + routing_algorithm (:class:`qibo.transpiler.abstract.Router`): Router to be used. + depth (int, optional): Number of two-qubit gates to be considered for routing. If ``None`` just one backward step will be implemented. If depth is greater than the number of two-qubit gates in the circuit, the circuit will be routed more than once. @@ -269,7 +269,7 @@ def __call__(self, circuit: Circuit): """Find the initial layout of the given circuit using Reverse Traversal placement. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be transpiled. """ assert_placement(circuit, self.connectivity) self.routing_algorithm.connectivity = self.connectivity @@ -283,11 +283,10 @@ def _assemble_circuit(self, circuit: Circuit): using depth :math:`d = 6`, the function will return the circuit :math:`C-D-D-C-B-A`. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be assembled. Returns: - (:class:`qibo.models.circuit.Circuit`): assembled circuit to perform - Reverse Traversal placement. + (:class:`qibo.models.circuit.Circuit`): Assembled circuit to perform Reverse Traversal placement. """ if self.depth is None: @@ -318,7 +317,7 @@ def _routing_step(self, circuit: Circuit): """Perform routing of the circuit. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be routed. """ _, final_mapping = self.routing_algorithm(circuit) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 704e4435b8..1798381bf2 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -28,7 +28,7 @@ class StarConnectivityRouter(Router): by adding SWAP gates when needed. Args: - connectivity (:class:`networkx.Graph`): star connectivity graph. + connectivity (:class:`networkx.Graph`): Star connectivity graph. """ def __init__(self, connectivity: Optional[nx.Graph] = None): @@ -135,10 +135,9 @@ class CircuitMap: Also implements the initial two-qubit block decompositions. Args: - 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. - Defaults to ``None``. + 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. Defaults to ``None``. """ def __init__( @@ -183,7 +182,7 @@ def physical_to_logical(self, p2l_map: list): """Sets the physical to logical qubit mapping and updates the logical to physical mapping. Args: - p2l_map (list): physical to logical mapping. + p2l_map (list): Physical to logical mapping. """ self._p2l = p2l_map.copy() self._l2p = [0] * len(self._p2l) @@ -195,7 +194,7 @@ def logical_to_physical(self, l2p_map: list): """Sets the logical to physical qubit mapping and updates the physical to logical mapping. Args: - l2p_map (list): logical to physical mapping. + l2p_map (list): Logical to physical mapping. """ self._l2p = l2p_map.copy() self._p2l = [0] * len(self._l2p) @@ -206,8 +205,8 @@ def _update_mappings_swap(self, logical_swap: tuple, physical_swap: tuple): """Updates the qubit mappings after applying a SWAP gate. Args: - logical_swap (tuple[int]): the indices of the logical qubits to be swapped. - physical_swap (tuple[int]): the indices of the corresponding physical qubits to be swapped. + logical_swap (tuple[int]): The indices of the logical qubits to be swapped. + physical_swap (tuple[int]): The indices of the corresponding physical qubits to be swapped. """ self._p2l[physical_swap[0]], self._p2l[physical_swap[1]] = ( logical_swap[1], @@ -229,7 +228,7 @@ def execute_block(self, block: Block): Method works in-place. Args: - block (:class:`qibo.transpiler.blocks.Block`): block to be removed. + block (:class:`qibo.transpiler.blocks.Block`): Block to be removed. """ self._routed_blocks.add_block(block.on_qubits(self.get_physical_qubits(block))) self.circuit_blocks.remove_block(block) @@ -238,7 +237,7 @@ def routed_circuit(self, circuit_kwargs: Optional[dict] = None): """Returns the routed circuit. Args: - circuit_kwargs (dict): original circuit ``init_kwargs``. + circuit_kwargs (dict): Original circuit init_kwargs. Returns: :class:`qibo.models.circuit.Circuit`: Routed circuit. @@ -257,7 +256,7 @@ def update(self, logical_swap: tuple): Method works in-place. Args: - swap (tuple): tuple containing the logical qubits to be swapped. + swap (tuple): Tuple containing the logical qubits to be swapped. """ physical_swap = self.logical_pair_to_physical(logical_swap) @@ -283,10 +282,10 @@ def get_physical_qubits(self, block: Union[int, Block]): """Returns the physical qubits where a block is acting on. Args: - block (int or :class:`qibo.transpiler.blocks.Block`): block to be analysed. + block (int or :class:`qibo.transpiler.blocks.Block`): Block to be analysed. Returns: - tuple: physical qubit numbers where a block is acting on. + tuple: Physical qubit numbers where a block is acting on. """ if isinstance(block, int): block = self.circuit_blocks.search_by_index(block) @@ -297,10 +296,10 @@ def logical_pair_to_physical(self, logical_qubits: tuple): """Returns the physical qubits associated to the logical qubit pair. Args: - logical_qubits (tuple): logical qubit pair. + logical_qubits (tuple): Logical qubit pair. Returns: - tuple: physical qubit numbers associated to the logical qubit pair. + tuple: Physical qubit numbers associated to the logical qubit pair. """ return self._l2p[logical_qubits[0]], self._l2p[logical_qubits[1]] @@ -309,8 +308,8 @@ class ShortestPaths(Router): """A class to perform initial qubit mapping and connectivity matching. Args: - connectivity (:class:`networkx.Graph`): chip connectivity. - seed (int, optional): seed for the random number generator. + connectivity (:class:`networkx.Graph`): Chip connectivity. + seed (int, optional): Seed for the random number generator. If ``None``, defaults to :math:`42`. Defaults to ``None``. """ @@ -336,12 +335,10 @@ def __call__(self, circuit: Circuit): """Circuit connectivity matching. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be matched - to hardware connectivity. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be matched to hardware connectivity. Returns: - (:class:`qibo.models.circuit.Circuit`, dict): circut mapped to hardware topology, - and final physical-to-logical qubit mapping. + (:class:`qibo.models.circuit.Circuit`, dict): Routed circuit and final logical-physical qubit mapping. """ assert_placement(circuit, self.connectivity) self._preprocessing(circuit=circuit) @@ -403,8 +400,8 @@ def _add_swaps(candidate: tuple, circuitmap: CircuitMap): Method works in-place. Args: - candidate (tuple): contains path to move qubits and qubit meeting point in the path. - circuitmap (CircuitMap): representation of the circuit. + candidate (tuple): Contains path to move qubits and qubit meeting point in the path. + circuitmap (CircuitMap): Representation of the circuit. """ path = candidate[0] meeting_point = candidate[1] @@ -431,10 +428,10 @@ def _compute_cost(self, candidate: tuple): The cost is computed as minus the number of successive gates that can be executed. Args: - candidate (tuple): contains path to move qubits and qubit meeting point in the path. + candidate (tuple): Contains path to move qubits and qubit meeting point in the path. Returns: - (list, int): best path to move qubits and qubit meeting point in the path. + (list, int): Best path to move qubits and qubit meeting point in the path. """ temporary_circuit = CircuitMap( circuit=Circuit(self.circuit_map.nqubits), @@ -477,7 +474,7 @@ def _check_execution(self): """Check if some blocks in the front layer can be executed in the current configuration. Returns: - (list): executable blocks if there are, ``None`` otherwise. + (list): Executable blocks if there are, ``None`` otherwise. """ executable_blocks = [] for block in self._front_layer: @@ -500,7 +497,7 @@ def _execute_blocks(self, blocklist: list): Method works in-place. Args: - blocklist (list): list of blocks. + blocklist (list): List of blocks. """ for block_id in blocklist: block = self.circuit_map.circuit_blocks.search_by_index(block_id) @@ -527,7 +524,7 @@ def _preprocessing(self, circuit: Circuit): - _front_layer: list containing the blocks to be executed. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be preprocessed. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be preprocessed. """ self.connectivity = nx.relabel_nodes( self.connectivity, {v: i for i, v in enumerate(circuit.wire_names)} @@ -542,10 +539,10 @@ def _detach_final_measurements(self, circuit: Circuit): """Detaches measurement gates at the end of the circuit for separate handling. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuits to be processed. + circuit (:class:`qibo.models.circuit.Circuit`): Circuits to be processed. Returns: - (NoneType or list): list of measurements. If no measurements, returns ``None``. + (NoneType or list): List of measurements. If no measurements, returns ``None``. """ final_measurements = [] for gate in circuit.queue[::-1]: @@ -578,18 +575,18 @@ class Sabre(Router): """Routing algorithm proposed in Ref [1]. Args: - connectivity (:class:`networkx.Graph`): hardware chip connectivity. - lookahead (int, optional): lookahead factor, how many dag layers will be considered + connectivity (:class:`networkx.Graph`): Hardware chip connectivity. + lookahead (int, optional): Lookahead factor, how many dag layers will be considered in computing the cost. Defaults to :math:`2`. - decay_lookahead (float, optional): value in interval :math:`[0, 1]`. + decay_lookahead (float, optional): Value in interval :math:`[0, 1]`. How the weight of the distance in the dag layers decays in computing the cost. Defaults to :math:`0.6`. - delta (float, optional): defines the number of SWAPs vs depth trade-off by deciding + delta (float, optional): Defines the number of SWAPs vs depth trade-off by deciding how the algorithm tends to select non-overlapping SWAPs. Defaults to math:`10^{-3}`. - seed (int, optional): seed for the candidate random choice as tiebraker. + seed (int, optional): Seed for the candidate random choice as tiebraker. Defaults to ``None``. - swap_threshold (float, optional): limits the number of added SWAPs in every routing iteration. + swap_threshold (float, optional): Limits the number of added SWAPs in every routing iteration. This threshold is multiplied by the length of the longest path in the circuit connectivity. If the number of added SWAPs exceeds the threshold before a gate is routed, shortestpath routing is applied. @@ -629,10 +626,10 @@ def __call__(self, circuit: Circuit): """Route the circuit. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be routed. Returns: - (:class:`qibo.models.circuit.Circuit`, dict): routed circuit and final layout. + (:class:`qibo.models.circuit.Circuit`, dict): Routed circuit and final logical-physical qubit mapping. """ assert_placement(circuit, self.connectivity) self._preprocessing(circuit=circuit) @@ -682,7 +679,7 @@ def _preprocessing(self, circuit: Circuit): to prevent overlapping swaps. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be preprocessed. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be preprocessed. """ self.connectivity = nx.relabel_nodes( @@ -716,10 +713,10 @@ def _append_final_measurements(self, routed_circuit: Circuit): """Appends final measurment gates on the correct qubits conserving the measurement register. Args: - routed_circuit (:class:`qibo.models.circuit.Circuit`): original circuit. + routed_circuit (:class:`qibo.models.circuit.Circuit`): Original circuit. Returns: - (:class:`qibo.models.circuit.Circuit`) routed circuit. + (:class:`qibo.models.circuit.Circuit`) Routed circuit. """ for measurement in self._final_measurements: original_qubits = measurement.qubits @@ -751,12 +748,12 @@ def _get_dag_layer(self, n_layer, qubits=False): """Return the :math:`n`-topological layer of the dag. Args: - n_layer (int): layer number. - qubits (bool, optional): if ``True``, return the target qubits of the blocks in the layer. + n_layer (int): Layer number. + qubits (bool, optional): If ``True``, return the target qubits of the blocks in the layer. If ``False``, return the block numbers. Defaults to ``False``. Returns: - (list): list of block numbers or target qubits. + (list): List of block numbers or target qubits. """ if qubits: @@ -821,7 +818,7 @@ def _swap_candidates(self): with a block in the front layer. Returns: - (list): list of candidates. + (list): List of candidates. """ candidates = [] for block in self._front_layer: @@ -844,7 +841,7 @@ def _check_execution(self): """Check if some blocks in the front layer can be executed in the current configuration. Returns: - (list): executable blocks if there are, ``None`` otherwise. + (list): Executable blocks if there are, ``None`` otherwise. """ executable_blocks = [] for block in self._front_layer: @@ -869,7 +866,7 @@ def _execute_blocks(self, blocklist: list): Method works in-place. Args: - blocklist (list): list of blocks. + blocklist (list): List of blocks. """ for block_id in blocklist: block = self.circuit_map.circuit_blocks.search_by_index(block_id) @@ -915,10 +912,10 @@ def _create_dag(gates_qubits_pairs: list): commutativity relations. Args: - gates_qubits_pairs (list): list of qubits tuples where gates/blocks acts. + gates_qubits_pairs (list): List of qubits tuples where gates/blocks acts. Returns: - (:class:`networkx.DiGraph`): adjoint of the circuit. + (:class:`networkx.DiGraph`): Adjoint of the circuit. """ dag = nx.DiGraph() dag.add_nodes_from(range(len(gates_qubits_pairs))) @@ -948,10 +945,10 @@ def _remove_redundant_connections(dag: nx.DiGraph): Remove redundant connection from a DAG using transitive reduction. Args: - dag (:class:`networkx.DiGraph`): dag to be reduced. + dag (:class:`networkx.DiGraph`): DAG to be reduced. Returns: - (:class:`networkx.DiGraph`): reduced dag. + (:class:`networkx.DiGraph`): Reduced DAG. """ new_dag = nx.DiGraph() new_dag.add_nodes_from(dag.nodes(data=True)) diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 2e875311fd..602728edde 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -85,16 +85,7 @@ def from_gate(cls, gate): # TODO: Make setting single-qubit native gates more flexible class Unroller: - """Translates a circuit to native gates. - - Args: - native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates to use - in the transpiled circuit. - backend (:class:`qibo.backends.Backend`): backend to use for gate matrix. - - Returns: - (:class:`qibo.models.circuit.Circuit`): equivalent circuit with native gates. - """ + """Decomposes a circuit to native gates.""" def __init__( self, @@ -103,15 +94,21 @@ def __init__( ): self.native_gates = native_gates self.backend = backend + """Initializes the unroller. + + Args: + native_gates (:class:`qibo.transpiler.unroller.NativeGates`): Native gates to use in the transpiled circuit. + backend (:class:`qibo.backends.Backend`): Backend to use for gate matrix. + """ def __call__(self, circuit: Circuit): - """Decomposes a circuit into native gates. + """Decomposes a circuit to native gates. Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit model to decompose. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be decomposed. Returns: - (:class:`qibo.models.circuit.Circuit`): equivalent circuit with native gates. + (:class:`qibo.models.circuit.Circuit`): Decomposed circuit. """ translated_circuit = Circuit(**circuit.init_kwargs) for gate in circuit.queue: @@ -133,10 +130,9 @@ def translate_gate( """Maps gates to a hardware-native implementation. Args: - gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates - to use in the decomposition. - backend (:class:`qibo.backends.Backend`): backend to use for gate matrix. + gate (:class:`qibo.gates.abstract.Gate`): Gate to be decomposed. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`): Native gates supported by the hardware. + backend (:class:`qibo.backends.Backend`): Backend to use for gate matrix. Returns: list: List of native gates that decompose the input gate. @@ -174,10 +170,9 @@ def _translate_single_qubit_gates( Maps single qubit gates to a hardware-native implementation. Args: - gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - single_qubit_natives (:class:`qibo.transpiler.unroller.NativeGates`): single - qubit native gates. - backend (:class:`qibo.backends.Backend`): backend to use for gate matrix. + gate (:class:`qibo.gates.abstract.Gate`): Gate to be decomposed. + single_qubit_natives (:class:`qibo.transpiler.unroller.NativeGates`): Single qubit native gates supported by the hardware. + backend (:class:`qibo.backends.Backend`): Backend to use for gate matrix. Returns: list: List of native gates that decompose the input gate. @@ -197,10 +192,9 @@ def _translate_two_qubit_gates(gate: gates.Gate, native_gates: NativeGates, back Maps two qubit gates to a hardware-native implementation. Args: - gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates - supported by the quantum hardware. - backend (:class:`qibo.backends.Backend`): backend to use for gate matrix. + gate (:class:`qibo.gates.abstract.Gate`): Gate to be decomposed. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`): Native gates supported by the hardware. + backend (:class:`qibo.backends.Backend`): Backend to use for gate matrix. Returns: list: List of native gates that decompose the input gate. @@ -212,7 +206,7 @@ def _translate_two_qubit_gates(gate: gates.Gate, native_gates: NativeGates, back if gate.__class__ in opt_dec.decompositions: return opt_dec(gate, backend) # Check if the gate has a CZ decomposition - if not gate.__class__ in iswap_dec.decompositions: + if gate.__class__ not in iswap_dec.decompositions: return cz_dec(gate, backend) # Check the decomposition with less 2 qubit gates. diff --git a/tests/test_transpiler_asserts.py b/tests/test_transpiler_asserts.py index 8b0b268003..9b59befd96 100644 --- a/tests/test_transpiler_asserts.py +++ b/tests/test_transpiler_asserts.py @@ -25,8 +25,8 @@ def test_assert_circuit_equivalence_equal(): 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) + final_layout = {0: 0, 1: 1} + assert_circuit_equivalence(circ1, circ2, final_layout=final_layout) def test_assert_circuit_equivalence_swap(): @@ -35,8 +35,8 @@ def test_assert_circuit_equivalence_swap(): 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) + final_layout = {0: 1, 1: 0} + assert_circuit_equivalence(circ1, circ2, final_layout=final_layout) def test_assert_circuit_equivalence_false(): @@ -45,9 +45,9 @@ def test_assert_circuit_equivalence_false(): circ1.add(gates.X(0)) circ2.add(gates.SWAP(0, 1)) circ2.add(gates.X(1)) - final_map = {0: 0, 1: 1} + final_layout = {0: 0, 1: 1} with pytest.raises(TranspilerPipelineError): - assert_circuit_equivalence(circ1, circ2, final_map=final_map) + assert_circuit_equivalence(circ1, circ2, final_layout=final_layout) def test_assert_placement_true(star_connectivity): diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index a8e43bc6aa..e624825059 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -70,9 +70,9 @@ def test_restrict_qubits(star_connectivity): def test_assert_circuit_equivalence_wrong_nqubits(): circ1 = Circuit(1) circ2 = Circuit(2) - final_map = {0: 0, 1: 1} + final_layout = {0: 0, 1: 1} with pytest.raises(ValueError): - assert_circuit_equivalence(circ1, circ2, final_map=final_map) + assert_circuit_equivalence(circ1, circ2, final_layout=final_layout) @pytest.mark.parametrize("qubits", [3, 5]) @@ -204,11 +204,11 @@ def test_int_qubit_names(star_connectivity): circuit.add(gates.I(0)) circuit.add(gates.H(0)) circuit.add(gates.M(0)) - transpiled_circuit, final_map = transpiler(circuit) + transpiled_circuit, final_layout = transpiler(circuit) assert_transpiling( original_circuit=circuit, transpiled_circuit=transpiled_circuit, connectivity=connectivity, - final_layout=final_map, + final_layout=final_layout, native_gates=NativeGates.default(), ) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 06899e78d3..26afeb3280 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -97,13 +97,13 @@ def test_bell_state_3q(): c = circuit.copy() connectivity = line_connectivity(3, None) router = Sabre(connectivity=connectivity) - routed_circuit, final_map = router(c) + routed_circuit, final_layout = router(c) 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())) + target_state = _transpose_qubits(target_state, list(final_layout.values())) assert np.all(np.isclose(np.real(original_state), np.real(target_state))) @@ -116,7 +116,7 @@ def test_random_circuits_5q(ngates, star_connectivity): circuit = generate_random_circuit(nqubits=5, ngates=ngates) original_circuit = circuit.copy() placer(circuit) - transpiled_circuit, final_qubit_map = transpiler(circuit) + transpiled_circuit, final_layout = transpiler(circuit) assert transpiler.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) @@ -125,7 +125,7 @@ def test_random_circuits_5q(ngates, star_connectivity): assert_circuit_equivalence( original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, - final_map=final_qubit_map, + final_layout=final_layout, ) @@ -138,7 +138,7 @@ def test_random_circuits_5q_grid(ngates, grid_connectivity): circuit = generate_random_circuit(nqubits=5, ngates=ngates) original_circuit = circuit.copy() placer(circuit) - transpiled_circuit, final_qubit_map = transpiler(circuit) + transpiled_circuit, final_layout = transpiler(circuit) assert transpiler.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) @@ -147,7 +147,7 @@ def test_random_circuits_5q_grid(ngates, grid_connectivity): assert_circuit_equivalence( original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, - final_map=final_qubit_map, + final_layout=final_layout, ) @@ -161,7 +161,7 @@ def test_random_circuits_15q_50g(nqubits, ngates): original_circuit = circuit.copy() placer(circuit) - transpiled_circuit, final_qubit_map = transpiler(circuit) + transpiled_circuit, final_layout = transpiler(circuit) assert transpiler.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) @@ -170,7 +170,7 @@ def test_random_circuits_15q_50g(nqubits, ngates): assert_circuit_equivalence( original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, - final_map=final_qubit_map, + final_layout=final_layout, ) @@ -181,7 +181,7 @@ def test_star_circuit(star_connectivity): transpiler = ShortestPaths(connectivity=connectivity) placer(circuit) - transpiled_circuit, final_qubit_map = transpiler(circuit) + transpiled_circuit, final_layout = transpiler(circuit) assert transpiler.added_swaps == 0 assert_connectivity(star_connectivity(), transpiled_circuit) @@ -189,7 +189,7 @@ def test_star_circuit(star_connectivity): assert_circuit_equivalence( original_circuit=star_circuit(), transpiled_circuit=transpiled_circuit, - final_map=final_qubit_map, + final_layout=final_layout, ) @@ -198,7 +198,7 @@ def test_star_circuit_custom_map(star_connectivity): circuit = star_circuit() circuit.wire_names = [1, 0, 2, 3, 4] transpiler = ShortestPaths(connectivity=connectivity) - transpiled_circuit, final_qubit_map = transpiler(circuit) + transpiled_circuit, final_layout = transpiler(circuit) assert transpiler.added_swaps == 1 assert_connectivity(star_connectivity(), transpiled_circuit) @@ -206,7 +206,7 @@ def test_star_circuit_custom_map(star_connectivity): assert_circuit_equivalence( original_circuit=star_circuit(), transpiled_circuit=transpiled_circuit, - final_map=final_qubit_map, + final_layout=final_layout, ) @@ -217,7 +217,7 @@ def test_routing_with_measurements(star_connectivity): circuit.add(gates.M(0, 2, 3)) transpiler = ShortestPaths(connectivity) - transpiled_circuit, final_qubit_map = transpiler(circuit) + transpiled_circuit, final_layout = transpiler(circuit) assert transpiled_circuit.ngates == 3 measured_qubits = transpiled_circuit.queue[2].qubits @@ -225,7 +225,7 @@ def test_routing_with_measurements(star_connectivity): assert_circuit_equivalence( original_circuit=circuit, transpiled_circuit=transpiled_circuit, - final_map=final_qubit_map, + final_layout=final_layout, ) @@ -244,8 +244,8 @@ def test_sabre_looping(): ) # Without reset router_threshold = Sabre(connectivity=connectivity) # With reset - routed_no_threshold, final_mapping_no_threshold = router_no_threshold(loop_circ) - routed_threshold, final_mapping_threshold = router_threshold(loop_circ) + routed_no_threshold, final_layout_no_threshold = router_no_threshold(loop_circ) + routed_threshold, final_layout_threshold = router_threshold(loop_circ) count_no_threshold = router_no_threshold.added_swaps count_threshold = router_threshold.added_swaps @@ -254,12 +254,12 @@ def test_sabre_looping(): assert_circuit_equivalence( original_circuit=loop_circ, transpiled_circuit=routed_no_threshold, - final_map=final_mapping_no_threshold, + final_layout=final_layout_no_threshold, ) assert_circuit_equivalence( original_circuit=loop_circ, transpiled_circuit=routed_threshold, - final_map=final_mapping_threshold, + final_layout=final_layout_threshold, ) @@ -374,14 +374,14 @@ def test_sabre_matched(names, star_connectivity): original_circuit = circuit.copy() router = Sabre(connectivity=connectivity) - routed_circuit, final_map = router(circuit) + routed_circuit, final_layout = router(circuit) assert router.added_swaps == 0 assert_connectivity(circuit=routed_circuit, connectivity=connectivity) assert_circuit_equivalence( original_circuit=original_circuit, transpiled_circuit=routed_circuit, - final_map=final_map, + final_layout=final_layout, ) @@ -393,10 +393,10 @@ def test_sabre_simple(seed, star_connectivity): original_circuit = circ.copy() router = Sabre(connectivity=connectivity, seed=seed) - routed_circuit, final_map = router(circ) + routed_circuit, final_layout = router(circ) assert router.added_swaps == 1 - assert final_map == {0: 2, 1: 1, 2: 0, 3: 3, 4: 4} + assert final_layout == {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) @@ -404,7 +404,7 @@ def test_sabre_simple(seed, star_connectivity): assert_circuit_equivalence( original_circuit=original_circuit, transpiled_circuit=routed_circuit, - final_map=final_map, + final_layout=final_layout, ) @@ -421,7 +421,7 @@ def test_sabre_random_circuits(n_gates, look, decay, star_connectivity): router = Sabre(connectivity, lookahead=look, decay_lookahead=decay) placer(circuit) - transpiled_circuit, final_qubit_map = router(circuit) + transpiled_circuit, final_layout = router(circuit) assert router.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) @@ -430,7 +430,7 @@ def test_sabre_random_circuits(n_gates, look, decay, star_connectivity): assert_circuit_equivalence( original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, - final_map=final_qubit_map, + final_layout=final_layout, ) assert transpiled_circuit.queue[-1].register_name == measurement.register_name @@ -448,7 +448,7 @@ def test_sabre_random_circuits_grid(n_gates, look, decay, grid_connectivity): router = Sabre(connectivity, lookahead=look, decay_lookahead=decay) placer(circuit) - transpiled_circuit, final_qubit_map = router(circuit) + transpiled_circuit, final_layout = router(circuit) assert router.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) @@ -457,7 +457,7 @@ def test_sabre_random_circuits_grid(n_gates, look, decay, grid_connectivity): assert_circuit_equivalence( original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, - final_map=final_qubit_map, + final_layout=final_layout, ) assert transpiled_circuit.queue[-1].register_name == measurement.register_name @@ -502,7 +502,7 @@ def test_restrict_qubits(router_algorithm, star_connectivity): assert_circuit_equivalence( original_circuit=circ, transpiled_circuit=routed_circ, - final_map=final_layout, + final_layout=final_layout, ) assert_connectivity(restricted_connectivity, routed_circ) assert_placement(routed_circ, connectivity=restricted_connectivity) @@ -558,13 +558,13 @@ def test_star_router( placer = StarConnectivityPlacer(connectivity) placer(circuit) - transpiled_circuit, final_qubit_map = transpiler(circuit) + transpiled_circuit, final_layout = transpiler(circuit) assert_connectivity(connectivity, transpiled_circuit) assert_placement(transpiled_circuit, connectivity) assert_circuit_equivalence( original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, - final_map=final_qubit_map, + final_layout=final_layout, )