Skip to content

Commit

Permalink
Merge pull request #319 from CQCL/release/0.52.0
Browse files Browse the repository at this point in the history
Release/0.52.0
  • Loading branch information
cqc-melf authored Apr 11, 2024
2 parents 770a267 + 9c43d89 commit 47fb896
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 54 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4.0.4
uses: actions/deploy-pages@v4.0.5
2 changes: 1 addition & 1 deletion _metadata.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__extension_version__ = "0.51.0"
__extension_version__ = "0.52.0"
__extension_name__ = "pytket-qiskit"
10 changes: 10 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
Changelog
~~~~~~~~~

0.52.0 (April 2024)
-------------------

* Update pytket version requirement to 1.26.
* Update qiskit-aer version requirement to 0.14.
* Update conversion to qiskit to use symengine for symbolic circuits
* Add `IBMQBackend.default_compilation_pass_offline` for offline compilation given config and props objects.
* Add `DirectednessPredicate` to IBMQBackend
* Default compilation pass of IBMQBackend will keep ECR gates in the direction required by the backend.

0.51.0 (March 2024)
-------------------

Expand Down
115 changes: 78 additions & 37 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 @@ -82,9 +82,12 @@
NoFastFeedforwardPredicate,
MaxNQubitsPredicate,
Predicate,
DirectednessPredicate,
)
from pytket.extensions.qiskit.qiskit_convert import tk_to_qiskit, _tk_gate_set
from pytket.architecture import FullyConnected
from qiskit.providers.models import BackendProperties, QasmBackendConfiguration # type: ignore

from ..qiskit_convert import tk_to_qiskit, _tk_gate_set
from pytket.architecture import FullyConnected, Architecture
from pytket.placement import NoiseAwarePlacement
from pytket.utils import prepare_circuit
from pytket.utils.outcomearray import OutcomeArray
Expand Down Expand Up @@ -190,11 +193,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 +243,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 +286,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 +326,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 All @@ -328,17 +347,16 @@ def required_predicates(self) -> List[Predicate]:
)
),
]
if isinstance(self.backend_info.architecture, Architecture):
predicates.append(DirectednessPredicate(self.backend_info.architecture))

mid_measure = self._backend_info.supports_midcircuit_measurement
fast_feedforward = self._backend_info.supports_fast_feedforward
if not mid_measure:
predicates = [
NoClassicalControlPredicate(),
NoMidMeasurePredicate(),
] + predicates
predicates.append(NoClassicalControlPredicate())
predicates.append(NoMidMeasurePredicate())
if not fast_feedforward:
predicates = [
NoFastFeedforwardPredicate(),
] + predicates
predicates.append(NoFastFeedforwardPredicate())
return predicates

def default_compilation_pass(
Expand Down Expand Up @@ -376,47 +394,64 @@ 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(
CXMappingPass(
arch,
noise_aware_placement,
directed_cx=False,
directed_cx=True,
delay_measures=(not mid_measure),
)
)
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
36 changes: 24 additions & 12 deletions pytket/extensions/qiskit/qiskit_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from uuid import UUID

import numpy as np
from symengine import sympify # type: ignore

import sympy
import qiskit.circuit.library.standard_gates as qiskit_gates # type: ignore
Expand Down Expand Up @@ -81,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 @@ -208,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 @@ -580,7 +578,7 @@ def param_to_qiskit(
if len(ppi.free_symbols) == 0:
return float(ppi.evalf())
else:
return ParameterExpression(symb_map, ppi)
return ParameterExpression(symb_map, sympify(ppi))


def _get_params(
Expand Down Expand Up @@ -724,7 +722,7 @@ def append_tk_command_to_qiskit(

if optype == OpType.TK1:
params = _get_params(op, symb_map)
half = ParameterExpression(symb_map, sympy.pi / 2)
half = ParameterExpression(symb_map, sympify(sympy.pi / 2))
qcirc.global_phase += -params[0] / 2 - params[2] / 2
return qcirc.append(
qiskit_gates.UGate(params[1], params[0] - half, params[2] + half),
Expand All @@ -749,7 +747,7 @@ def append_tk_command_to_qiskit(
if type(phase) == float:
qcirc.global_phase += phase * np.pi
else:
qcirc.global_phase += phase * sympy.pi
qcirc.global_phase += sympify(phase * sympy.pi)
return qcirc.append(g, qargs=qargs)


Expand Down Expand Up @@ -871,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 @@ -884,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
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@
packages=find_namespace_packages(include=["pytket.*"]),
include_package_data=True,
install_requires=[
"pytket ~= 1.25",
"pytket ~= 1.26",
"qiskit ~= 1.0",
"qiskit-algorithms ~= 0.3.0",
"qiskit-ibm-runtime ~= 0.22.0",
"qiskit-aer ~= 0.13.3",
"qiskit-aer ~= 0.14.0",
"qiskit-ibm-provider ~= 0.10.0",
"numpy",
],
Expand Down
18 changes: 17 additions & 1 deletion tests/backend_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,8 @@ def test_nshots(
circuit = Circuit(1).X(0)
circuit.measure_all()
n_shots = [1, 2, 3]
results = b.get_results(b.process_circuits([circuit] * 3, n_shots=n_shots))
circ_comp = b.get_compiled_circuit(circuit)
results = b.get_results(b.process_circuits([circ_comp] * 3, n_shots=n_shots))
assert [sum(r.get_counts().values()) for r in results] == n_shots


Expand Down Expand Up @@ -1330,6 +1331,21 @@ def test_crosstalk_noise_model() -> None:
res.get_counts()


@pytest.mark.skipif(skip_remote_tests, reason=REASON)
def test_ecr(ibm_brisbane_backend: IBMQBackend) -> None:
ghz5 = Circuit(5)
ghz5.H(0).CX(0, 1).CX(0, 2).CX(0, 3).CX(0, 4)
ghz5.measure_all()
ibm_ghz5 = ibm_brisbane_backend.get_compiled_circuit(ghz5)

compiled_ghz5 = ibm_brisbane_backend.get_compiled_circuit(ibm_ghz5)

ibm_brisbane_backend.valid_circuit(compiled_ghz5)

handle = ibm_brisbane_backend.process_circuit(compiled_ghz5, n_shots=1000)
ibm_brisbane_backend.cancel(handle)


# helper function for testing
def _get_qiskit_statevector(qc: QuantumCircuit) -> np.ndarray:
"""Given a QuantumCircuit, use aer_simulator_statevector to compute its
Expand Down
Loading

0 comments on commit 47fb896

Please sign in to comment.