diff --git a/doc/source/main-documentation/qibolab.rst b/doc/source/main-documentation/qibolab.rst index c3bcec19f..5cbca4001 100644 --- a/doc/source/main-documentation/qibolab.rst +++ b/doc/source/main-documentation/qibolab.rst @@ -659,11 +659,11 @@ This procedure typically involves the following steps: 2. All gates are transpiled to native gates, which represent the universal set of gates that can be implemented (via pulses) in the chip. 3. Native gates are compiled to a pulse sequence. -The transpilation and compilation process is taken care of automatically by the :class:`qibolab.backends.QibolabBackend` when a circuit is executed, using circuit transpilers provided by Qibo and :class:`qibolab.compilers.compiler.Compiler`. The transpiler is responsible for steps 1 and 2, while the compiler for step 3 of the list above. -For more information on the transpiler please refer the `examples in the Qibo documentation `_. +To be executed in Qibolab, a circuit should be already transpiled. It possible to use the transpilers provided by Qibo to do it. For more information, please refer the `examples in the Qibo documentation `_. +On the other hand, the compilation process is taken care of automatically by the :class:`qibolab.backends.QibolabBackend`. -Once a circuit has been transpiled, it is converted to a :class:`qibolab.pulses.PulseSequence` by the :class:`qibolab.compilers.compiler.Compiler`. +Once a circuit has been compiled, it is converted to a :class:`qibolab.pulses.PulseSequence` by the :class:`qibolab.compilers.compiler.Compiler`. This is a container of rules which define how each native gate can be translated to pulses. A rule is a Python function that accepts a Qibo gate and a platform object and returns the :class:`qibolab.pulses.PulseSequence` implementing this gate and a dictionary with potential virtual-Z phases that need to be applied in later pulses. Examples of rules can be found on :py:mod:`qibolab.compilers.default`, which defines the default rules used by Qibolab. diff --git a/doc/source/tutorials/circuits.rst b/doc/source/tutorials/circuits.rst index 1afabe9a8..7982e8696 100644 --- a/doc/source/tutorials/circuits.rst +++ b/doc/source/tutorials/circuits.rst @@ -129,7 +129,7 @@ Returns the following plot: :class: only-dark .. note:: - Executing circuits using the Qibolab backend results to automatic application of the transpilation and compilation pipelines (:ref:`main_doc_compiler`) which convert the circuit to a pulse sequence that is executed by the given platform. + Executing circuits using the Qibolab backend results to automatic application of the compilation pipeline (:ref:`main_doc_compiler`) which convert the circuit to a pulse sequence that is executed by the given platform. It is possible to modify these pipelines following the instructions in the :ref:`tutorials_compiler` example. QASM Execution diff --git a/doc/source/tutorials/compiler.rst b/doc/source/tutorials/compiler.rst index 9361e53ee..a445e8e7f 100644 --- a/doc/source/tutorials/compiler.rst +++ b/doc/source/tutorials/compiler.rst @@ -1,11 +1,11 @@ .. _tutorials_compiler: -How to modify the default circuit transpilation. -================================================ +How to modify the default circuit compilation +============================================= A Qibolab platform can execute pulse sequences. As shown in :ref:`tutorials_circuits`, Qibo circuits can be executed by invoking the :class:`qibolab.backends.QibolabBackend`, which is the object integrating Qibolab to Qibo. -When a Qibo circuit is executed, the backend will automatically transpile and compile it to a pulse sequence, which will be sent to the platform for execution. +When a Qibo circuit is executed, the backend will automatically compile it to a pulse sequence, which will be sent to the platform for execution. The default compiler outlined in the :ref:`main_doc_compiler` section will be used in this process. In this tutorial we will demonstrate how the user can modify this process for custom applications. @@ -27,14 +27,14 @@ Creating an instance of the backend provides access to these objects: The transpiler is responsible for transforming any circuit to one that respects the chip connectivity and native gates. The compiler then transforms this circuit -to the equivalent pulse sequence. Note that there is no default transpiler, therefore +to the equivalent pulse sequence. Note that there is no transpiler in Qibolab, therefore the backend can only execute circuits that contain native gates by default. -The user can modify the transpilation and compilation process by changing -the ``transpiler`` and ``compiler`` attributes of the ``QibolabBackend``. +The user can modify the compilation process by changing the ``compiler`` attributes of +the ``QibolabBackend``. In this example, we executed circuits using the backend ``backend.execute_circuit`` method, unlike the previous example (:ref:`tutorials_circuits`) where circuits were executed directly using ``circuit(nshots=1000)``. -It is possible to perform transpiler and compiler manipulation in both approaches. +It is possible to perform compiler manipulation in both approaches. When using ``circuit(nshots=1000)``, Qibo is automatically initializing a ``GlobalBackend()`` singleton that is used to execute the circuit. Therefore the previous manipulations can be done as follows: @@ -52,8 +52,6 @@ Therefore the previous manipulations can be done as follows: # set backend to qibolab qibo.set_backend("qibolab", platform="dummy") - # disable the transpiler - GlobalBackend().transpiler = None # execute circuit result = circuit(nshots=1000) @@ -66,7 +64,7 @@ The compiler can be modified by adding new compilation rules or changing existin As explained in :ref:`main_doc_compiler` section, a rule is a function that accepts a Qibo gate and a Qibolab platform and returns the pulse sequence implementing this gate. -The following example shows how to modify the transpiler and compiler in order to execute a circuit containing a Pauli X gate using a single pi-pulse: +The following example shows how to modify the compiler in order to execute a circuit containing a Pauli X gate using a single pi-pulse: .. testcode:: python @@ -93,19 +91,14 @@ The following example shows how to modify the transpiler and compiler in order t # the empty dictionary is needed because the X gate does not require any virtual Z-phases backend = QibolabBackend(platform="dummy") - # disable the transpiler - backend.transpiler = None # register the new X rule in the compiler backend.compiler[gates.X] = x_rule # execute the circuit result = backend.execute_circuit(circuit, nshots=1000) -Here we completely disabled the transpiler to avoid transforming the X gate to a different gate and we added a rule that instructs the compiler how to transform the X gate. - The default set of compiler rules is defined in :py:mod:`qibolab.compilers.default`. .. note:: If the compiler receives a circuit that contains a gate for which it has no rule, an error will be raised. This means that the native gate set that the transpiler uses, should be compatible with the available compiler rules. - If the transpiler is disabled, a rule should be available for all gates in the original circuit. diff --git a/src/qibolab/backends.py b/src/qibolab/backends.py index 20c87eb0d..9513d1370 100644 --- a/src/qibolab/backends.py +++ b/src/qibolab/backends.py @@ -1,5 +1,4 @@ from collections import deque -from typing import Callable, Optional import numpy as np from qibo import __version__ as qibo_version @@ -29,7 +28,6 @@ def __init__(self, platform): "qibolab": qibolab_version, } self.compiler = Compiler.default() - self.transpiler: Optional[Callable] = None def apply_gate(self, gate, state, nqubits): # pragma: no cover raise_error(NotImplementedError, "Qibolab cannot apply gates directly.") @@ -37,20 +35,6 @@ def apply_gate(self, gate, state, nqubits): # pragma: no cover def apply_gate_density_matrix(self, gate, state, nqubits): # pragma: no cover raise_error(NotImplementedError, "Qibolab cannot apply gates directly.") - def transpile(self, circuit): - """Applies the transpiler to a single circuit. - - This transforms the circuit into proper connectivity and native - gates. - """ - # TODO: Move this method to transpilers - if self.transpiler is None or self.transpiler.is_satisfied(circuit): - native = circuit - qubit_map = {q: q for q in range(circuit.nqubits)} - else: - native, qubit_map = self.transpiler(circuit) # pylint: disable=E1102 - return native, qubit_map - def assign_measurements(self, measurement_map, readout): """Assigning measurement outcomes to :class:`qibo.states.MeasurementResult` for each gate. @@ -92,8 +76,7 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): "Hardware backend only supports circuits as initial states.", ) - native_circuit, qubit_map = self.transpile(circuit) - sequence, measurement_map = self.compiler.compile(native_circuit, self.platform) + sequence, measurement_map = self.compiler.compile(circuit, self.platform) if not self.platform.is_connected: self.platform.connect() @@ -136,12 +119,8 @@ def execute_circuits(self, circuits, initial_states=None, nshots=1000): ) # TODO: Maybe these loops can be parallelized - native_circuits, _ = zip(*(self.transpile(circuit) for circuit in circuits)) sequences, measurement_maps = zip( - *( - self.compiler.compile(circuit, self.platform) - for circuit in native_circuits - ) + *(self.compiler.compile(circuit, self.platform) for circuit in circuits) ) if not self.platform.is_connected: