Skip to content

Commit

Permalink
Fix example code, enable mid-circuit measurements. (#135)
Browse files Browse the repository at this point in the history
* Fix example code.

* Allow mid-circuit measurements.

* Docs updated.

* Fix routing for mid-circuit measurements.
  • Loading branch information
smite authored Sep 16, 2024
1 parent 8c447f8 commit 3480b15
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 108 deletions.
10 changes: 8 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
Changelog
=========

Version 14.4
============

* Allow mid-circuit measurements. `#135 <https://github.com/iqm-finland/cirq-on-iqm/pull/135>`_
* Broken example code fixed. `#135 <https://github.com/iqm-finland/cirq-on-iqm/pull/135>`_

Version 14.3
============

* Improved operation validation to check if it is calibrated according to the metadata rather than assuming. `#133 <https://github.com/iqm-finland/cirq-on-iqm/pull/133>`_
* Added IQMMoveGate class for Deneb architectures. `#133 <https://github.com/iqm-finland/cirq-on-iqm/pull/133>`_
* Updated IQMDevice class to support devices with resonators. `#133 <https://github.com/iqm-finland/cirq-on-iqm/pull/133>`_
* Added :class:`IQMMoveGate` class for Deneb architectures. `#133 <https://github.com/iqm-finland/cirq-on-iqm/pull/133>`_
* Updated :class:`IQMDevice` class to support devices with resonators. `#133 <https://github.com/iqm-finland/cirq-on-iqm/pull/133>`_
* Support for :class:`CircuitCompilationOptions` from ``iqm-client`` when submitting a circuit to an IQM device.
* Require iqm-client >= 18.0. `#133 <https://github.com/iqm-finland/cirq-on-iqm/pull/133>`_

Expand Down
7 changes: 4 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,16 @@
autodoc_member_order = 'bysource'

# where should signature annotations appear in the docs, function signature or parameter description?
autodoc_typehints = 'description'
autodoc_typehints = 'both'
# autodoc_typehints = 'description' puts the __init__ annotations into its docstring,
# which we thus have to include in the class documentation.
autoclass_content = 'both'
autoclass_content = 'class'

# Sphinx 3.3+: manually clean up type alias rendering in the docs
# autodoc_type_aliases = {'TypeAlias': 'exa.experiment.somemodule.TypeAlias'}

# This is required to make docs build work after the client packages were moved to iqm namespace.
autodoc_mock_imports = ["iqm_client"]
autodoc_mock_imports = []

# -- Autosummary ------------------------------------------------------------

Expand Down Expand Up @@ -162,6 +162,7 @@
'matplotlib': ('https://matplotlib.org/stable', None),
'numpy': ('https://numpy.org/doc/stable', None),
'scipy': ('https://docs.scipy.org/doc/scipy', None),
'iqm_client': ('https://iqm-finland.github.io/iqm-client', None),
}

extlinks = {
Expand Down
108 changes: 79 additions & 29 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ and convert it into a :class:`cirq.Circuit` object using :func:`.circuit_from_qa

:func:`.circuit_from_qasm` uses the OpenQASM 2.0 parser in :mod:`cirq.contrib.qasm_import`.

After a circuit has been constructed, it can be decomposed and routed against a particular ``IQMDevice``.
After a circuit has been constructed, it can be decomposed and routed against a particular :class:`.IQMDevice`.


Decomposition
Expand Down Expand Up @@ -205,9 +205,23 @@ If you have gates involving more than two qubits you need to decompose them befo
Since routing may add some SWAP gates to the circuit, you will need to decompose the circuit
again after the routing, unless SWAP is a native gate for the target device.

To ensure that the transpiler is restricted to a specific subset of qubits, you can provide a list of qubits in the ``qubit_subset`` argument such that ancillary qubits will not be added during routing. This is particularly useful when running Quantum Volume benchmarks.
To ensure that the transpiler is restricted to a specific subset of qubits, you can provide a list
of qubits in the ``qubit_subset`` argument such that ancillary qubits will not be added during
routing. This is particularly useful when running Quantum Volume benchmarks.

Additionally, if the target device supports MOVE gates (e.g. IQM Deneb), a final MOVE gate insertion step is performed. Under the hood, this uses the :meth:`transpile_insert_moves`method of the iqm_client library. This method is exposed through :meth:`transpile_insert_moves_into_circuit` which can also be used by advanced users to transpile circuits that have already some MOVE gates in them, or to remove existing MOVE gates from a circuit so the circuit can be reused on a device that does not support them.
IQM Star architecture
^^^^^^^^^^^^^^^^^^^^^

Devices that have the IQM Star architecture (e.g. IQM Deneb) support MOVE gates that are used
to move quantum states between qubits and computational resonators.
For these devices a final MOVE gate insertion step must be performed, which introduces
the computational resonators to the circuit, and routes the two-qubit gates through them using MOVEs.

Under the hood, this uses the :func:`~iqm.iqm_client.transpile.transpile_insert_moves` function of the
:mod:`~iqm.iqm_client` library. This method is exposed through :func:`.transpile_insert_moves_into_circuit` which
can also be used by advanced users to transpile circuits that have already some MOVE gates in them,
or to remove existing MOVE gates from a circuit so the circuit can be reused on a device that does
not support them.

Optimization
------------
Expand Down Expand Up @@ -246,12 +260,13 @@ Running on a real quantum computer
.. note::

At the moment IQM does not provide a quantum computing service open to the general public.
Please contact our `sales team <https://www.meetiqm.com/contact/>`_ to set up your access to an IQM quantum computer.
Please contact our `sales team <https://www.meetiqm.com/contact-us/>`_ to set up your access to
an IQM quantum computer.

Cirq contains various simulators which you can use to simulate the circuits constructed above.
In this subsection we demonstrate how to run them on an IQM quantum computer.

Cirq on IQM implements :class:`.IQMSampler`, a subclass of :class:`cirq.work.Sampler`, which is used
Cirq on IQM provides :class:`.IQMSampler`, a subclass of :class:`cirq.work.Sampler`, which is used
to execute quantum circuits. Once you have access to an IQM server you can create an :class:`.IQMSampler`
instance and use its :meth:`~.IQMSampler.run` method to send a circuit for execution and retrieve the results:

Expand All @@ -277,22 +292,42 @@ The below table summarises the currently available options:
- Type
- Example value
- Description
* - `calibration_set_id`
- str
* - :attr:`calibration_set_id`
- :class:`uuid.UUID`
- "f7d9642e-b0ca-4f2d-af2a-30195bd7a76d"
- Indicates the calibration set to use. Defaults to `None`, which means the IQM server will use the best
available calibration set automatically.
* - `max_circuit_duration_over_t2`
- float
- Indicates the calibration set to use. Defaults to ``None``, which means the IQM server will use the
current default calibration set automatically.
* - :attr:`compiler_options`
- :class:`~iqm.iqm_client.models.CircuitCompilationOptions`
- see below
- Contains various options that affect the compilation of the quantum circuit into an
instruction schedule.

The :class:`~iqm.iqm_client.models.CircuitCompilationOptions` class contains the following attributes (in addition to some
advanced options described in the API documentation):

.. list-table::
:widths: 25 20 25 100
:header-rows: 1

* - Name
- Type
- Example value
- Description
* - :attr:`max_circuit_duration_over_t2`
- :class:`float` | :class:`None`
- 1.0
- Set server-side circuit disqualification threshold. If any circuit in a job is estimated to take longer than the
shortest T2 time of any qubit used in the circuit multiplied by this value, the server will reject the job.
Setting this value to ``0.0`` will disable circuit duration check.
The default value ``None`` means the server default value will be used.
* - `heralding_mode`
- :py:class:`~iqm_client.models.HeraldingMode`
* - :attr:`heralding_mode`
- :class:`~iqm.iqm_client.models.HeraldingMode`
- "zeros"
- Heralding mode to use during execution. The default value is "none", "zeros" enables heralding.
- Heralding mode to use during execution. The default value is "none", "zeros" enables
all-zeros heralding where the circuit qubits are measured before the circuit begins, and the
server post-selects and returns only those shots where the heralding measurement yields zeros
for all the qubits.

For example if you would like to use a particular calibration set, you can provide it as follows:

Expand All @@ -301,23 +336,38 @@ For example if you would like to use a particular calibration set, you can provi
sampler = IQMSampler(iqm_server_url, calibration_set_id="f7d9642e-b0ca-4f2d-af2a-30195bd7a76d")
The same applies for `heralding_mode` and `max_circuit_duration_over_t2`. The sampler will by default use an
:class:`.IQMDevice` created based on architecture data obtained from the server, which is then available in the
:attr:`.IQMSampler.device` property. Alternatively, the device can be specified directly with the ``device`` argument.

If the IQM server you are connecting to requires authentication, you will also have to use
`Cortex CLI <https://github.com/iqm-finland/cortex-cli>`_ to retrieve and automatically refresh access tokens,
then set the ``IQM_TOKENS_FILE`` environment variable to use those tokens.
See Cortex CLI's `documentation <https://iqm-finland.github.io/cortex-cli/readme.html>`_ for details.
Alternatively, you can authenticate yourself using the ``IQM_AUTH_SERVER``, ``IQM_AUTH_USERNAME``
and ``IQM_AUTH_PASSWORD`` environment variables, or pass them as arguments to the constructor of
:class:`.IQMProvider`, but this approach is less secure and considered deprecated.
The sampler will by default use an :class:`.IQMDevice` created based on architecture data obtained
from the server, which is then available in the :attr:`.IQMSampler.device` property. Alternatively,
the device can be specified directly with the :attr:`device` argument.

When executing a circuit that uses something other than the device qubits, you need to route it first,
as explained in the :ref:`routing` section above.

Multiple circuits can be submitted to the IQM quantum computer at once using the :meth:`~.IQMSampler.run_iqm_batch` method of :class:`.IQMSampler`.
This is often faster than executing the circuits individually. Circuits submitted in a batch are still executed sequentially.

Authentication
^^^^^^^^^^^^^^

If the IQM server you are connecting to requires authentication, you may use
`Cortex CLI <https://github.com/iqm-finland/cortex-cli>`_ to retrieve and automatically refresh access tokens,
then set the :envvar:`IQM_TOKENS_FILE` environment variable, as instructed, to point to the tokens file.
See Cortex CLI's `documentation <https://iqm-finland.github.io/cortex-cli/readme.html>`__ for details.

Alternatively, you may authenticate yourself using the :envvar:`IQM_AUTH_SERVER`,
:envvar:`IQM_AUTH_USERNAME` and :envvar:`IQM_AUTH_PASSWORD` environment variables, or pass them as
arguments to :class:`.IQMSampler`, but this approach is less secure and
considered deprecated.

Finally, if you are using ``IQM Resonance``, authentication is handled differently.
Use the :envvar:`IQM_TOKEN` environment variable to provide the API Token obtained
from the server dashboard.


Batch execution
^^^^^^^^^^^^^^^

Multiple circuits can be submitted to the IQM quantum computer at once using the
:meth:`~.IQMSampler.run_iqm_batch` method of :class:`.IQMSampler`. This is often faster than
executing the circuits individually. Circuits submitted in a batch are still executed sequentially.

.. code-block:: python
Expand All @@ -333,7 +383,7 @@ This is often faster than executing the circuits individually. Circuits submitte
Inspecting the final circuits before submitting them for execution
------------------------------------------------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

It is possible to inspect the final circuits that would be submitted for execution before actually submitting them,
which can be useful for debugging purposes. This can be done using :meth:`.IQMSampler.create_run_request`, which returns
Expand All @@ -353,7 +403,7 @@ way as those functions.
It is also possible to print a run request when it is actually submitted by setting the environment variable
``IQM_CLIENT_DEBUG=1``.
:envvar:`IQM_CLIENT_DEBUG=1`.


More examples
Expand Down
2 changes: 1 addition & 1 deletion examples/demo_adonis.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def demo_adonis(use_qsim: bool = False) -> None:
qasm_program = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[3];
qreg q[3];
creg a[2];
creg b[1];
Expand Down
2 changes: 1 addition & 1 deletion examples/demo_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def demo(device: IQMDevice, circuit: cirq.Circuit, *, use_qsim: bool = False) ->
pause()

# map the circuit qubits to device qubits
circuit_mapped = device.route_circuit(circuit_simplified)
circuit_mapped, initial_mapping, final_mapping = device.route_circuit(circuit_simplified)

print('\nRouted simplified circuit:')
print(circuit_mapped)
Expand Down
50 changes: 11 additions & 39 deletions src/iqm/cirq_iqm/devices/iqm_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@
from itertools import zip_longest
from math import pi as PI
from typing import Optional, Sequence, cast
import uuid

import cirq
from cirq import InsertStrategy, MeasurementGate, devices, ops, protocols
from cirq import devices, ops, protocols
from cirq.contrib.routing.router import nx

from iqm.cirq_iqm.iqm_gates import IQMMoveGate
Expand All @@ -47,11 +46,6 @@ def _verify_unique_measurement_keys(operations: ca.Iterable[cirq.Operation]) ->
seen_keys.add(key)


def _validate_for_routing(circuit: cirq.AbstractCircuit) -> None:
if not circuit.are_all_measurements_terminal():
raise ValueError('Non-terminal measurements are not supported')


class IQMDevice(devices.Device):
"""ABC for the properties of a specific IQM quantum architecture.
Expand Down Expand Up @@ -213,36 +207,24 @@ def route_circuit(
Note that only gates of one or two qubits, and measurement operations of arbitrary size are supported.
Args:
circuit: circuit to route
circuit: Circuit to route.
initial_mapper: Initial mapping from ``circuit`` qubits to device qubits, to serve as
the starting point of the routing. ``None`` means it will be generated automatically.
qubit_subset: Restrict the routing to this subset of the device qubits. If ``None``,
use the entire device.
Returns:
The routed circuit.
The initial mapping before inserting SWAP gates, see docstring of :func:`cirq.RouterCQC.route_circuit`
The final mapping from physical qubits to physical qubits,
see docstring of :func:`cirq.RouterCQC.route_circuit`
routed circuit, initial mapping before inserting SWAP gates (see :func:`cirq.RouterCQC.route_circuit`),
final mapping from physical qubits to physical qubits (see :func:`cirq.RouterCQC.route_circuit`)
Raises:
ValueError: routing is impossible
"""
_validate_for_routing(circuit)

# Remove all measurement gates and replace them with 1-qubit identity gates so they don't
# disappear from the final swap network if no other operations remain. We will add them back after routing the
# rest of the network. This is done to prevent measurements becoming non-terminal during routing.
measurement_ops = list(circuit.findall_operations_with_gate_type(MeasurementGate))
measurement_qubits = set().union(*[op.qubits for _, op, _ in measurement_ops])

modified_circuit = circuit.copy()
modified_circuit.batch_remove([(ind, op) for ind, op, _ in measurement_ops])
i_tag = uuid.uuid4()
for q in measurement_qubits:
modified_circuit.append(cirq.I(q).with_tags(i_tag))

if self.metadata.resonator_set:
# If the device has computational resonators, we use a modified connection graph for routing
# by adding edges between all qubits connected to the same resonator.
move_routing = True
graph = nx.Graph()
for edge in self.metadata.nx_graph.edges:
Expand All @@ -266,19 +248,10 @@ def route_circuit(
modified_circuit, initial_mapper=initial_mapper
)
routed_circuit = cast(cirq.Circuit, routed_circuit)
# TODO routing can apply SWAP gates even after formerly terminal single-qubit measurements.
# It would make sense to commute single-qubit measurements through SWAPs as far towards the
# end of the circuit as possible.

# Return measurements to the circuit with potential qubit swaps.
new_measurements = []
for _, op, gate in measurement_ops:
new_qubits = [final_mapping[initial_mapping[q]] for q in op.qubits]
new_measurement = cirq.measure(*new_qubits, key=gate.key)
new_measurements.append(new_measurement)

routed_circuit.append(new_measurements, InsertStrategy.NEW_THEN_INLINE)

# Remove additional identity gates.
identity_gates = routed_circuit.findall_operations(lambda op: i_tag in op.tags)
routed_circuit.batch_remove(identity_gates)
if move_routing:
# Decompose the SWAP gates to the native gate set.
routed_circuit = self.decompose_circuit(routed_circuit)
Expand Down Expand Up @@ -306,7 +279,6 @@ def decompose_circuit(self, circuit: cirq.Circuit) -> cirq.Circuit:
def validate_circuit(self, circuit: cirq.AbstractCircuit) -> None:
super().validate_circuit(circuit)
_verify_unique_measurement_keys(circuit.all_operations())
_validate_for_routing(circuit)
self.validate_moves(circuit)

def validate_operation(self, operation: cirq.Operation) -> None:
Expand Down
4 changes: 3 additions & 1 deletion src/iqm/cirq_iqm/devices/iqm_device_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ class IQMDeviceMetadata(devices.DeviceMetadata):
"""Hardware metadata for IQM devices.
Args:
qubits: qubits that exist on the device
qubits: qubits on the device
connectivity: qubit connectivity graph of the device
operations: Supported quantum operations of the device, mapping op types to their possible loci.
gateset: Native gateset of the device. If None, a default IQM device gateset will be used.
resonators: computational resonators of the device
"""

QUBIT_NAME_PREFIX: str = 'QB'
Expand Down
6 changes: 3 additions & 3 deletions src/iqm/cirq_iqm/iqm_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ class IQMSampler(cirq.work.Sampler):
Args:
url: Endpoint for accessing the server interface. Has to start with http or https.
device: Device to execute the circuits on. If ``None``, the device will be created based
on the quantum architecture obtained from :class:`.IQMClient`.
on the quantum architecture obtained from :class:`~iqm.iqm_client.iqm_client.IQMClient`.
calibration_set_id:
ID of the calibration set to use. If ``None``, use the latest one.
run_sweep_timeout:
timeout to poll sweep results in seconds.
compiler_options: The compilation options to use for the circuits as defined by IQM Client.
Timeout for polling sweep results, in seconds. If ``None``, use the client default value.
compiler_options: The compilation options to use for the circuits, as defined by IQM Client.
Keyword Args:
auth_server_url (str): URL of user authentication server, if required by the IQM Cortex server.
Expand Down
Loading

0 comments on commit 3480b15

Please sign in to comment.