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

Release/0.52.0 #319

Merged
merged 9 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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