Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Documentation for wire_names and _Global #1531

Merged
merged 37 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c2ba133
fix: setter error msg
Nov 28, 2024
0078f76
fix: docstrings abstract asserts
Nov 28, 2024
3d8d98e
fix: transpiler docstrings
Nov 28, 2024
8d201f7
fix: transpiler doc
Nov 28, 2024
2e42e31
fix: final_map -> final_layout
Nov 28, 2024
8f167e7
fix: backend API ref
Nov 28, 2024
9aaa1b2
fix: minor change
Nov 28, 2024
5bda771
fix: newlines
Nov 28, 2024
dc9bece
fix: add hw backend, transpiler, default transpiler
Nov 28, 2024
e485312
fix: circuit docstring
Nov 28, 2024
da15138
Merge branch 'master' into wire_names_docs
csookim Nov 28, 2024
6321b3c
fix: eq circuit
Nov 30, 2024
bb10676
fix: add desc wire_names default transpiler
Nov 30, 2024
35be2c4
fix: add expln Preprocessing on_qubits
Dec 1, 2024
ebfe3ae
Update doc/source/api-reference/qibo.rst
csookim Dec 5, 2024
e96c953
Update doc/source/api-reference/qibo.rst
csookim Dec 5, 2024
58ed798
fix: specify def backend order
Dec 7, 2024
510a79c
Merge branch 'wire_names_docs' of github.com:qiboteam/qibo into wire_…
Dec 7, 2024
b71d9ab
Update doc/source/code-examples/advancedexamples.rst
csookim Dec 11, 2024
7fd6d35
Update doc/source/code-examples/advancedexamples.rst
csookim Dec 11, 2024
e19f044
Update doc/source/code-examples/advancedexamples.rst
csookim Dec 11, 2024
5e7f883
Update doc/source/code-examples/advancedexamples.rst
csookim Dec 11, 2024
9961855
Update src/qibo/transpiler/pipeline.py
csookim Dec 11, 2024
04009a3
Update src/qibo/transpiler/optimizer.py
csookim Dec 11, 2024
647134b
Update src/qibo/transpiler/optimizer.py
csookim Dec 11, 2024
f60ebdc
Update src/qibo/transpiler/asserts.py
csookim Dec 11, 2024
ce90ae1
Update src/qibo/transpiler/asserts.py
csookim Dec 11, 2024
723b523
Update src/qibo/transpiler/asserts.py
csookim Dec 11, 2024
558a709
Update doc/source/code-examples/advancedexamples.rst
csookim Dec 11, 2024
14e37df
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 11, 2024
8e903f0
fix: unroller docstring / replace qw5q -> dummy / add wire_names init…
Dec 11, 2024
c0f5551
fix: unroller docstring / replace qw5q -> dummy / add wire_names init…
Dec 11, 2024
1373b13
fix: unroller docstring / replace qw5q -> dummy / add wire_names init…
Dec 11, 2024
d9ef9fe
fix doc test
renatomello Dec 12, 2024
e11d77d
Update doc/source/code-examples/advancedexamples.rst
csookim Dec 12, 2024
d240da8
Update doc/source/code-examples/advancedexamples.rst
csookim Dec 12, 2024
9ef4b40
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 16 additions & 38 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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 <backend-drivers>`),
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 <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 <https://github.com/qulacs/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:
Expand Down
233 changes: 161 additions & 72 deletions doc/source/code-examples/advancedexamples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -145,6 +145,63 @@ 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 <https://qibo.science/qibolab/stable/>`_.
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 <tutorials_set_transpiler>`.

.. code-block:: python

import qibo

# Set the backend and platform
qibo.set_backend("qibolab", platform="qw5q_platinum")
csookim marked this conversation as resolved.
Show resolved Hide resolved

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

# Define the circuit with 4 qubits
circuit = Circuit(4)
circuit.add(gates.H(0))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.CZ(0, 2))
circuit.add(gates.M(2, 3))

# Set the physical qubit names
circuit.wire_names = ["C", "A", "B", "D"]

# 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"
csookim marked this conversation as resolved.
Show resolved Hide resolved

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 <tutorials_set_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?
---------------------

Expand Down Expand Up @@ -2129,47 +2186,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

Expand All @@ -2184,36 +2222,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
glist = [gates.GPI2, gates.RZ, gates.Z, gates.CZ]
natives = NativeGates(0).from_gatelist(glist)
csookim marked this conversation as resolved.
Show resolved Hide resolved

# 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,
Expand All @@ -2222,10 +2257,64 @@ 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

import qibo
csookim marked this conversation as resolved.
Show resolved Hide resolved
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
csookim marked this conversation as resolved.
Show resolved Hide resolved

# 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
qibo.set_transpiler(custom_pipeline)
csookim marked this conversation as resolved.
Show resolved Hide resolved

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.
csookim marked this conversation as resolved.
Show resolved Hide resolved

Setting an empty transpiler is equivalent to disabling transpilation.

.. testcode:: python

import qibo
from qibo.transpiler.pipeline import Passes

qibo.set_transpiler(Passes())
csookim marked this conversation as resolved.
Show resolved Hide resolved

.. _gst_example:

Expand Down
18 changes: 11 additions & 7 deletions src/qibo/models/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand All @@ -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*
Expand Down Expand Up @@ -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()
Expand Down
Loading
Loading