Skip to content

Commit

Permalink
Internal/narrow types required for compilation (#310)
Browse files Browse the repository at this point in the history
* Narrow args for _tk_gate_set

* Narrow types for _get_backend_info args

* Shadow methods for default_compilation_pass and rebase_pass

* Use config for backend_name

* Update changelog.rst

---------

Co-authored-by: Matthew Burke <matthew.burke@quantinuum.com>
  • Loading branch information
cqc-melf and mwpb authored Apr 10, 2024
1 parent b98d535 commit 6ce0b3c
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 37 deletions.
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
~~~~~~~~~

Unreleased
----------

* Add `IBMQBackend.default_compilation_pass_static` for offline compilation given config and props objects.

0.51.1rc0 (April 2024)
----------------------

Expand Down
97 changes: 69 additions & 28 deletions pytket/extensions/qiskit/backends/ibm.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@
from pytket.backends.backendinfo import BackendInfo
from pytket.backends.backendresult import BackendResult
from pytket.backends.resulthandle import _ResultIdTuple
from pytket.extensions.qiskit.qiskit_convert import (
process_characterisation,
from ..qiskit_convert import (
get_avg_characterisation,
process_characterisation_from_config,
)
from pytket.extensions.qiskit._metadata import __extension_version__
from .._metadata import __extension_version__
from pytket.passes import (
BasePass,
auto_rebase_pass,
Expand All @@ -83,7 +83,9 @@
MaxNQubitsPredicate,
Predicate,
)
from pytket.extensions.qiskit.qiskit_convert import tk_to_qiskit, _tk_gate_set
from qiskit.providers.models import BackendProperties, QasmBackendConfiguration # type: ignore

from ..qiskit_convert import tk_to_qiskit, _tk_gate_set
from pytket.architecture import FullyConnected
from pytket.placement import NoiseAwarePlacement
from pytket.utils import prepare_circuit
Expand Down Expand Up @@ -190,11 +192,12 @@ def __init__(
else provider
)
self._backend: "_QiskIBMBackend" = self._provider.get_backend(backend_name)
config = self._backend.configuration()
config: QasmBackendConfiguration = self._backend.configuration()
self._max_per_job = getattr(config, "max_experiments", 1)

gate_set = _tk_gate_set(self._backend)
self._backend_info = self._get_backend_info(self._backend)
gate_set = _tk_gate_set(config)
props: Optional[BackendProperties] = self._backend.properties()
self._backend_info = self._get_backend_info(config, props)

self._service = QiskitRuntimeService(
channel="ibm_quantum", token=token, instance=instance
Expand Down Expand Up @@ -239,9 +242,21 @@ def backend_info(self) -> BackendInfo:
return self._backend_info

@classmethod
def _get_backend_info(cls, backend: "_QiskIBMBackend") -> BackendInfo:
config = backend.configuration()
characterisation = process_characterisation(backend)
def _get_backend_info(
cls,
config: QasmBackendConfiguration,
props: Optional[BackendProperties],
) -> BackendInfo:
"""Construct a BackendInfo from data returned by the IBMQ API.
:param config: The configuration of this backend.
:type config: QasmBackendConfiguration
:param props: The measured properties of this backend (not required).
:type props: Optional[BackendProperties]
:return: Information about the backend.
:rtype: BackendInfo
"""
characterisation = process_characterisation_from_config(config, props)
averaged_errors = get_avg_characterisation(characterisation)
characterisation_keys = [
"t1times",
Expand Down Expand Up @@ -270,10 +285,10 @@ def _get_backend_info(cls, backend: "_QiskIBMBackend") -> BackendInfo:
hasattr(config, "supported_instructions")
and "reset" in config.supported_instructions
)
gate_set = _tk_gate_set(backend)
gate_set = _tk_gate_set(config)
backend_info = BackendInfo(
cls.__name__,
backend.name,
config.backend_name,
__extension_version__,
arch,
(
Expand Down Expand Up @@ -310,9 +325,12 @@ def available_devices(cls, **kwargs: Any) -> List[BackendInfo]:
else:
provider = IBMProvider()

backend_info_list = [
cls._get_backend_info(backend) for backend in provider.backends()
]
backend_info_list = []
for backend in provider.backends():
config = backend.configuration()
props = backend.properties()
backend_info_list.append(cls._get_backend_info(config, props))

return backend_info_list

@property
Expand Down Expand Up @@ -376,40 +394,57 @@ def default_compilation_pass(
:return: Compilation pass guaranteeing required predicates.
:rtype: BasePass
"""
config: QasmBackendConfiguration = self._backend.configuration()
props: Optional[BackendProperties] = self._backend.properties()
return IBMQBackend.default_compilation_pass_offline(
config, props, optimisation_level, placement_options
)

@staticmethod
def default_compilation_pass_offline(
config: QasmBackendConfiguration,
props: Optional[BackendProperties],
optimisation_level: int = 2,
placement_options: Optional[Dict] = None,
) -> BasePass:
backend_info = IBMQBackend._get_backend_info(config, props)
primitive_gates = _get_primitive_gates(_tk_gate_set(config))
supports_rz = OpType.Rz in primitive_gates

assert optimisation_level in range(3)
passlist = [DecomposeBoxes()]
# If you make changes to the default_compilation_pass,
# then please update this page accordingly
# https://tket.quantinuum.com/extensions/pytket-qiskit/index.html#default-compilation
# Edit this docs source file -> pytket-qiskit/docs/intro.txt
if optimisation_level == 0:
if self._supports_rz:
if supports_rz:
# If the Rz gate is unsupported then the rebase should be skipped
# This prevents an error when compiling to the stabilizer backend
# where no TK1 replacement can be found for the rebase.
passlist.append(self.rebase_pass())
passlist.append(IBMQBackend.rebase_pass_offline(primitive_gates))
elif optimisation_level == 1:
passlist.append(SynthesiseTket())
elif optimisation_level == 2:
passlist.append(FullPeepholeOptimise())
mid_measure = self._backend_info.supports_midcircuit_measurement
arch = self._backend_info.architecture
mid_measure = backend_info.supports_midcircuit_measurement
arch = backend_info.architecture
assert arch is not None
if not isinstance(arch, FullyConnected):
if placement_options is not None:
noise_aware_placement = NoiseAwarePlacement(
arch,
self._backend_info.averaged_node_gate_errors, # type: ignore
self._backend_info.averaged_edge_gate_errors, # type: ignore
self._backend_info.averaged_readout_errors, # type: ignore
backend_info.averaged_node_gate_errors, # type: ignore
backend_info.averaged_edge_gate_errors, # type: ignore
backend_info.averaged_readout_errors, # type: ignore
**placement_options,
)
else:
noise_aware_placement = NoiseAwarePlacement(
arch,
self._backend_info.averaged_node_gate_errors, # type: ignore
self._backend_info.averaged_edge_gate_errors, # type: ignore
self._backend_info.averaged_readout_errors, # type: ignore
backend_info.averaged_node_gate_errors, # type: ignore
backend_info.averaged_edge_gate_errors, # type: ignore
backend_info.averaged_readout_errors, # type: ignore
)

passlist.append(
Expand All @@ -432,8 +467,10 @@ def default_compilation_pass(
]
)

if self._supports_rz:
passlist.extend([self.rebase_pass(), RemoveRedundancies()])
if supports_rz:
passlist.extend(
[IBMQBackend.rebase_pass_offline(primitive_gates), RemoveRedundancies()]
)
return SequencePass(passlist)

@property
Expand All @@ -442,7 +479,11 @@ def _result_id_type(self) -> _ResultIdTuple:
return (str, int, int, str)

def rebase_pass(self) -> BasePass:
return auto_rebase_pass(self._primitive_gates)
return IBMQBackend.rebase_pass_offline(self._primitive_gates)

@staticmethod
def rebase_pass_offline(primitive_gates: set[OpType]) -> BasePass:
return auto_rebase_pass(primitive_gates)

def process_circuits(
self,
Expand Down
29 changes: 20 additions & 9 deletions pytket/extensions/qiskit/qiskit_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,13 @@
from pytket.pauli import Pauli, QubitPauliString
from pytket.architecture import Architecture, FullyConnected
from pytket.utils import QubitPauliOperator, gen_term_sequence_circuit
from qiskit.providers.models import BackendProperties, QasmBackendConfiguration # type: ignore

from pytket.passes import RebaseCustom

if TYPE_CHECKING:
from qiskit.providers.backend import BackendV1 as QiskitBackend # type: ignore
from qiskit.providers.models.backendproperties import ( # type: ignore
BackendProperties,
Nduv,
)
from qiskit.providers.models.backendproperties import Nduv # type: ignore
from qiskit.circuit.quantumcircuitdata import QuantumCircuitData # type: ignore
from pytket.circuit import Op, UnitID

Expand Down Expand Up @@ -209,9 +207,8 @@
_gate_str_2_optype_rev[OpType.Unitary1qBox] = "unitary"


def _tk_gate_set(backend: "QiskitBackend") -> Set[OpType]:
def _tk_gate_set(config: QasmBackendConfiguration) -> Set[OpType]:
"""Set of tket gate types supported by the qiskit backend"""
config = backend.configuration()
if config.simulator:
gate_set = {
_gate_str_2_optype[gate_str]
Expand Down Expand Up @@ -872,10 +869,25 @@ def process_characterisation(backend: "QiskitBackend") -> Dict[str, Any]:
:return: A dictionary containing device characteristics
:rtype: dict
"""
config = backend.configuration()
props = backend.properties()
return process_characterisation_from_config(config, props)

# TODO explicitly check for and separate 1 and 2 qubit gates
properties = cast("BackendProperties", backend.properties())

def process_characterisation_from_config(
config: QasmBackendConfiguration, properties: Optional[BackendProperties]
) -> Dict[str, Any]:
"""Obtain a dictionary containing device Characteristics given config and props.
:param config: A IBMQ configuration object
:type config: QasmBackendConfiguration
:param properties: An optional IBMQ properties object
:type properties: Optional[BackendProperties]
:return: A dictionary containing device characteristics
:rtype: dict
"""

# TODO explicitly check for and separate 1 and 2 qubit gates
def return_value_if_found(iterator: Iterable["Nduv"], name: str) -> Optional[Any]:
try:
first_found = next(filter(lambda item: item.name == name, iterator))
Expand All @@ -885,7 +897,6 @@ def return_value_if_found(iterator: Iterable["Nduv"], name: str) -> Optional[Any
return first_found.value
return None

config = backend.configuration()
coupling_map = config.coupling_map
n_qubits = config.n_qubits
if coupling_map is None:
Expand Down

0 comments on commit 6ce0b3c

Please sign in to comment.