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

Draft: Simplify the transpilation code. #156

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
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
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
Changelog
=========

Version 21.0
============

* Simplify the transpilation code, make its details private, improve the docs.
`#156 <https://github.com/iqm-finland/iqm-client/pull/156>`_
* By default :func:`.transpile_insert_moves` now keeps any existing MOVE gates in the circuit.
`#156 <https://github.com/iqm-finland/iqm-client/pull/156>`_

Version 20.13
=============

Expand Down
60 changes: 20 additions & 40 deletions INTEGRATION_GUIDE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,55 +131,35 @@ IQM does not provide an open source circuit transpilation library, so this will
by the quantum computing framework or a third party library. To obtain the necessary information
for circuit transpilation, :meth:`.IQMClient.get_dynamic_quantum_architecture` returns the names of the
QPU components (qubits and computational resonators), and the native operations available
in the given calibration set. This information should enable circuit transpilation for IQM quantum architectures.

The notable exception is the transpilation of MOVE gates for IQM quantum computers with
computational resonators, for which some specialized transpilation logic is provided. The MOVE gate
moves the state of a qubit to an empty computational resonator, and vice versa, so that the qubit
can interact with other qubits connected to the resonator. For this, we provide users with two
transpilation functions: :func:`.transpile_insert_moves` and :func:`.transpile_remove_moves`. These
functions can be used to insert or remove MOVE gates from the circuit, respectively.

:func:`.transpile_insert_moves` is a transpiler pass for inserting MOVE gates into a circuit for
devices with a computational resonator. It assumes that the circuit is already transpiled by third
party software to an architecture where the computational resonator has been abstracted away. To
abstract away the computational resonator, the connectivity graph is modified such that all the
qubits connected to a common resonator are instead connected directly to each other. The function
can take a ``qubit_mapping`` to rename the qubits in the circuit to match the physical qubit names.
Additionally, the function can take the optional argument ``existing_moves`` to specify how this
transpiler pass should handle the case where some MOVE gates are already present in the circuit. The
options are specified by the enum :class:`.ExistingMoveHandlingOptions`. By default the function
warns the user if MOVE gates are already present in the circuit but the ``existing_moves`` argument
is not given, before proceeding to remove the existing MOVE gates and inserting new ones.

:func:`.transpile_remove_moves` is a helper function for :func:`.transpile_insert_moves` to remove
existing MOVE gates from a quantum circuit. It can be also used standalone to remove the MOVE gates
from an existing circuit such that it can be used on a device without a computational resonator, or
optimized by third party software that does not support the MOVE gate. For example, a user might
want to run a circuit that was originally transpiled for a device with a computational resonator on
a device without a computational resonator. This function allows the user to remove the MOVE gates
from the circuit before transpiling it to another quantum architecture.
in the given calibration set. This information should enable circuit transpilation for the
IQM Crystal quantum architectures.

The notable exception is the transpilation for the IQM Star quantum architectures, which have
computational resonators in addition to qubits. Some specialized transpilation logic involving
the MOVE gates specific to these architectures is provided, in the form of the fuctions
:func:`.transpile_insert_moves` and :func:`.transpile_remove_moves`.
See :mod:`iqm.iqm_client.transpile` for the details.

A typical Star architecture use case would look something like this:

.. code-block:: python

from iqm.iqm_client import Circuit, IQMClient, transpile_insert_moves, transpile_remove_moves
from iqm.iqm_client import Circuit, IQMClient, simplified_architecture, transpile_insert_moves, transpile_remove_moves

client = IQMClient(URL_TO_STAR_SERVER)
dqa = client.get_dynamic_quantum_architecture()
simplified_dqa = simplified_architecture(dqa)

# circuit valid for simplified_dqa
circuit = Circuit(name="quantum_circuit", instructions=[...])
backend_with_resonator = IQMClient("url_to_backend_with_resonator")
backend_without_resonator = IQMClient("url_to_backend_without_resonator")

# intended use
circuit_with_moves = transpile_insert_moves(circuit, backend_with_resonator.get_dynamic_quantum_architecture())
circuit_without_moves = transpile_remove_moves(circuit_with_moves)
circuit_with_moves = transpile_insert_moves(circuit, dqa)
client.submit_circuits([circuit_with_moves])

backend_with_resonator.submit_circuits([circuit_with_moves])
backend_without_resonator.submit_circuits([circuit_without_moves])

# Using the transpile_insert_moves on a device that does not support MOVE gates does nothing.
assert circuit == transpile_insert_moves(circuit, backend_without_resonator.get_dynamic_quantum_architecture())
# Unless the circuit had MOVE gates, then it can remove them with the existing_moves argument.
alt_circuit_without_moves = transpile_insert_moves(circuit, backend_without_resonator.get_dynamic_quantum_architecture(), existing_moves=ExistingMoveHandlingOptions.REMOVE)
# back to simplified dqa
circuit_without_moves = transpile_remove_moves(circuit_with_moves)
assert circuit == circuit_without_moves


Note on qubit mapping
Expand Down
16 changes: 8 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ Documentation = "https://iqm-finland.github.io/iqm-client"

[project.optional-dependencies]
testing = [
"black == 24.3.0",
"isort == 5.12.0",
"pylint == 3.0.2",
"mypy == 1.7.1",
"black == 24.10.0",
"isort == 5.13.2",
"pylint == 3.3.3",
"mypy == 1.14.1",
"mockito == 1.4.0",
"pytest == 7.4.3",
"pytest-cov == 4.1.0",
"pytest-isort == 3.1.0",
"pytest == 8.3.4",
"pytest-cov == 6.0.0",
"pytest-isort == 4.0.0",
"pytest-pylint == 0.21.0",
"pylint-pydantic == 0.3.0",
"pylint-pydantic == 0.3.5",
"types-requests == 2.28.9",
"jsons == 1.6.1",
"freezegun == 1.5.1",
Expand Down
1 change: 1 addition & 0 deletions src/iqm/iqm_client/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def __init__(
username: Optional[str] = None,
password: Optional[str] = None,
):
# pylint: disable=too-many-positional-arguments
def _format_names(variable_names: list[str]) -> str:
"""Format a list of variable names"""
return ', '.join(f'"{name}"' for name in variable_names)
Expand Down
26 changes: 21 additions & 5 deletions src/iqm/iqm_client/iqm_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,11 @@ def create_run_request(

# validate the circuit against the calibration-dependent dynamic quantum architecture
self._validate_circuit_instructions(
architecture, circuits, qubit_mapping, validate_moves=options.move_gate_validation
architecture,
circuits,
qubit_mapping,
validate_moves=options.move_gate_validation,
must_return_states=False,
)
return RunRequest(
qubit_mapping=serialized_qubit_mapping,
Expand Down Expand Up @@ -356,6 +360,8 @@ def _validate_circuit_instructions(
circuits: CircuitBatch,
qubit_mapping: Optional[dict[str, str]] = None,
validate_moves: MoveGateValidationMode = MoveGateValidationMode.STRICT,
*,
must_return_states: bool = True,
) -> None:
"""Validate the given circuits against the given quantum architecture.

Expand All @@ -366,6 +372,7 @@ def _validate_circuit_instructions(
Can be set to ``None`` if all ``circuits`` already use physical qubit names.
Note that the ``qubit_mapping`` is used for all ``circuits``.
validate_moves: determines how MOVE gate validation works
must_return_states: Iff True, MOVE sandwiches cannot be left open when the circuit ends.

Raises:
CircuitValidationError: validation failed
Expand All @@ -380,7 +387,13 @@ def _validate_circuit_instructions(
if key in measurement_keys:
raise CircuitValidationError(f'Circuit {index}: {instr!r} has a non-unique measurement key.')
measurement_keys.add(key)
IQMClient._validate_circuit_moves(architecture, circuit, qubit_mapping, validate_moves=validate_moves)
IQMClient._validate_circuit_moves(
architecture,
circuit,
qubit_mapping,
validate_moves=validate_moves,
must_return_states=must_return_states,
)

@staticmethod
def _validate_instruction(
Expand Down Expand Up @@ -461,7 +474,7 @@ def check_locus_components(allowed_components: Iterable[str], msg: str) -> None:
raise CircuitValidationError(
f"{instruction.qubits} = {tuple(mapped_qubits)} is not allowed as locus for '{instruction_name}'"
if qubit_mapping
else f"'{instruction.qubits} is not allowed as locus for '{instruction_name}'"
else f"{instruction.qubits} is not allowed as locus for '{instruction_name}'"
)

@staticmethod
Expand All @@ -470,6 +483,8 @@ def _validate_circuit_moves(
circuit: Circuit,
qubit_mapping: Optional[dict[str, str]] = None,
validate_moves: MoveGateValidationMode = MoveGateValidationMode.STRICT,
*,
must_return_states: bool = True,
) -> None:
"""Raises an error if the MOVE gates in the circuit are not valid in the given architecture.

Expand All @@ -479,6 +494,7 @@ def _validate_circuit_moves(
qubit_mapping: Mapping of logical qubit names to physical qubit names.
Can be set to ``None`` if the ``circuit`` already uses physical qubit names.
validate_moves: Option for bypassing full or partial MOVE gate validation.
must_return_states: Iff True, MOVE sandwiches cannot be left open when the circuit ends.
Raises:
CircuitValidationError: validation failed
"""
Expand Down Expand Up @@ -543,8 +559,8 @@ def _validate_circuit_moves(
f'are in a resonator. Current resonator occupation: {resonator_occupations}.'
)

# Finally validate that all moves have been ended before the circuit ends
if resonator_occupations:
# Finally validate that all MOVE sandwiches have been ended before the circuit ends
if must_return_states and resonator_occupations:
raise CircuitValidationError(
f'Circuit ends while qubit state(s) are still in a resonator: {resonator_occupations}.'
)
Expand Down
20 changes: 10 additions & 10 deletions src/iqm/iqm_client/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from enum import Enum
from functools import cached_property
import re
from typing import Any, Final, Optional, Union
from typing import Any, Optional, Union
from uuid import UUID

from pydantic import BaseModel, Field, StrictStr, field_validator
Expand Down Expand Up @@ -591,7 +591,7 @@ class DynamicQuantumArchitecture(BaseModel):

@cached_property
def components(self) -> tuple[str, ...]:
"""Returns all locus components (qubits and computational resonators) sorted.
"""All locus components (qubits and computational resonators) sorted.

The components are first sorted alphabetically based on their non-numeric part, and then
components with the same non-numeric part are sorted numerically. An example of components
Expand All @@ -618,33 +618,33 @@ class HeraldingMode(str, Enum):
class MoveGateValidationMode(str, Enum):
"""MOVE gate validation mode for circuit compilation. This options is meant for advanced users."""

STRICT: Final[str] = 'strict'
Aerylia marked this conversation as resolved.
Show resolved Hide resolved
STRICT = 'strict'
"""Perform standard MOVE gate validation: MOVE gates must only appear in sandwiches, with no gates acting on the
MOVE qubit inside the sandwich."""
ALLOW_PRX: Final[str] = 'allow_prx'
ALLOW_PRX = 'allow_prx'
"""Allow PRX gates on the MOVE qubit inside MOVE sandwiches during validation."""
NONE: Final[str] = 'none'
NONE = 'none'
"""Do not perform any MOVE gate validation."""


class MoveGateFrameTrackingMode(str, Enum):
"""MOVE gate frame tracking mode for circuit compilation. This option is meant for advanced users."""

FULL: Final[str] = 'full'
FULL = 'full'
"""Perform complete MOVE gate frame tracking."""
NO_DETUNING_CORRECTION: Final[str] = 'no_detuning_correction'
NO_DETUNING_CORRECTION = 'no_detuning_correction'
"""Do not add the phase detuning corrections to the pulse schedule for the MOVE gate. The user is expected to do
these manually."""
NONE: Final[str] = 'none'
NONE = 'none'
"""Do not perform any MOVE gate frame tracking. The user is expected to do these manually."""


class DDMode(str, Enum):
"""Dynamical Decoupling (DD) mode for circuit execution."""

DISABLED: Final[str] = 'disabled'
DISABLED = 'disabled'
"""Do not apply dynamical decoupling."""
ENABLED: Final[str] = 'enabled'
ENABLED = 'enabled'
"""Apply dynamical decoupling."""


Expand Down
Loading
Loading