Skip to content

Commit 47fb896

Browse files
authored
Merge pull request #319 from CQCL/release/0.52.0
Release/0.52.0
2 parents 770a267 + 9c43d89 commit 47fb896

File tree

8 files changed

+157
-54
lines changed

8 files changed

+157
-54
lines changed

.github/workflows/build_and_test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,4 @@ jobs:
159159
steps:
160160
- name: Deploy to GitHub Pages
161161
id: deployment
162-
uses: actions/deploy-pages@v4.0.4
162+
uses: actions/deploy-pages@v4.0.5

_metadata.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__extension_version__ = "0.51.0"
1+
__extension_version__ = "0.52.0"
22
__extension_name__ = "pytket-qiskit"

docs/changelog.rst

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
Changelog
22
~~~~~~~~~
33

4+
0.52.0 (April 2024)
5+
-------------------
6+
7+
* Update pytket version requirement to 1.26.
8+
* Update qiskit-aer version requirement to 0.14.
9+
* Update conversion to qiskit to use symengine for symbolic circuits
10+
* Add `IBMQBackend.default_compilation_pass_offline` for offline compilation given config and props objects.
11+
* Add `DirectednessPredicate` to IBMQBackend
12+
* Default compilation pass of IBMQBackend will keep ECR gates in the direction required by the backend.
13+
414
0.51.0 (March 2024)
515
-------------------
616

pytket/extensions/qiskit/backends/ibm.py

+78-37
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@
5555
from pytket.backends.backendinfo import BackendInfo
5656
from pytket.backends.backendresult import BackendResult
5757
from pytket.backends.resulthandle import _ResultIdTuple
58-
from pytket.extensions.qiskit.qiskit_convert import (
59-
process_characterisation,
58+
from ..qiskit_convert import (
6059
get_avg_characterisation,
60+
process_characterisation_from_config,
6161
)
62-
from pytket.extensions.qiskit._metadata import __extension_version__
62+
from .._metadata import __extension_version__
6363
from pytket.passes import (
6464
BasePass,
6565
auto_rebase_pass,
@@ -82,9 +82,12 @@
8282
NoFastFeedforwardPredicate,
8383
MaxNQubitsPredicate,
8484
Predicate,
85+
DirectednessPredicate,
8586
)
86-
from pytket.extensions.qiskit.qiskit_convert import tk_to_qiskit, _tk_gate_set
87-
from pytket.architecture import FullyConnected
87+
from qiskit.providers.models import BackendProperties, QasmBackendConfiguration # type: ignore
88+
89+
from ..qiskit_convert import tk_to_qiskit, _tk_gate_set
90+
from pytket.architecture import FullyConnected, Architecture
8891
from pytket.placement import NoiseAwarePlacement
8992
from pytket.utils import prepare_circuit
9093
from pytket.utils.outcomearray import OutcomeArray
@@ -190,11 +193,12 @@ def __init__(
190193
else provider
191194
)
192195
self._backend: "_QiskIBMBackend" = self._provider.get_backend(backend_name)
193-
config = self._backend.configuration()
196+
config: QasmBackendConfiguration = self._backend.configuration()
194197
self._max_per_job = getattr(config, "max_experiments", 1)
195198

196-
gate_set = _tk_gate_set(self._backend)
197-
self._backend_info = self._get_backend_info(self._backend)
199+
gate_set = _tk_gate_set(config)
200+
props: Optional[BackendProperties] = self._backend.properties()
201+
self._backend_info = self._get_backend_info(config, props)
198202

199203
self._service = QiskitRuntimeService(
200204
channel="ibm_quantum", token=token, instance=instance
@@ -239,9 +243,21 @@ def backend_info(self) -> BackendInfo:
239243
return self._backend_info
240244

241245
@classmethod
242-
def _get_backend_info(cls, backend: "_QiskIBMBackend") -> BackendInfo:
243-
config = backend.configuration()
244-
characterisation = process_characterisation(backend)
246+
def _get_backend_info(
247+
cls,
248+
config: QasmBackendConfiguration,
249+
props: Optional[BackendProperties],
250+
) -> BackendInfo:
251+
"""Construct a BackendInfo from data returned by the IBMQ API.
252+
253+
:param config: The configuration of this backend.
254+
:type config: QasmBackendConfiguration
255+
:param props: The measured properties of this backend (not required).
256+
:type props: Optional[BackendProperties]
257+
:return: Information about the backend.
258+
:rtype: BackendInfo
259+
"""
260+
characterisation = process_characterisation_from_config(config, props)
245261
averaged_errors = get_avg_characterisation(characterisation)
246262
characterisation_keys = [
247263
"t1times",
@@ -270,10 +286,10 @@ def _get_backend_info(cls, backend: "_QiskIBMBackend") -> BackendInfo:
270286
hasattr(config, "supported_instructions")
271287
and "reset" in config.supported_instructions
272288
)
273-
gate_set = _tk_gate_set(backend)
289+
gate_set = _tk_gate_set(config)
274290
backend_info = BackendInfo(
275291
cls.__name__,
276-
backend.name,
292+
config.backend_name,
277293
__extension_version__,
278294
arch,
279295
(
@@ -310,9 +326,12 @@ def available_devices(cls, **kwargs: Any) -> List[BackendInfo]:
310326
else:
311327
provider = IBMProvider()
312328

313-
backend_info_list = [
314-
cls._get_backend_info(backend) for backend in provider.backends()
315-
]
329+
backend_info_list = []
330+
for backend in provider.backends():
331+
config = backend.configuration()
332+
props = backend.properties()
333+
backend_info_list.append(cls._get_backend_info(config, props))
334+
316335
return backend_info_list
317336

318337
@property
@@ -328,17 +347,16 @@ def required_predicates(self) -> List[Predicate]:
328347
)
329348
),
330349
]
350+
if isinstance(self.backend_info.architecture, Architecture):
351+
predicates.append(DirectednessPredicate(self.backend_info.architecture))
352+
331353
mid_measure = self._backend_info.supports_midcircuit_measurement
332354
fast_feedforward = self._backend_info.supports_fast_feedforward
333355
if not mid_measure:
334-
predicates = [
335-
NoClassicalControlPredicate(),
336-
NoMidMeasurePredicate(),
337-
] + predicates
356+
predicates.append(NoClassicalControlPredicate())
357+
predicates.append(NoMidMeasurePredicate())
338358
if not fast_feedforward:
339-
predicates = [
340-
NoFastFeedforwardPredicate(),
341-
] + predicates
359+
predicates.append(NoFastFeedforwardPredicate())
342360
return predicates
343361

344362
def default_compilation_pass(
@@ -376,47 +394,64 @@ def default_compilation_pass(
376394
:return: Compilation pass guaranteeing required predicates.
377395
:rtype: BasePass
378396
"""
397+
config: QasmBackendConfiguration = self._backend.configuration()
398+
props: Optional[BackendProperties] = self._backend.properties()
399+
return IBMQBackend.default_compilation_pass_offline(
400+
config, props, optimisation_level, placement_options
401+
)
402+
403+
@staticmethod
404+
def default_compilation_pass_offline(
405+
config: QasmBackendConfiguration,
406+
props: Optional[BackendProperties],
407+
optimisation_level: int = 2,
408+
placement_options: Optional[Dict] = None,
409+
) -> BasePass:
410+
backend_info = IBMQBackend._get_backend_info(config, props)
411+
primitive_gates = _get_primitive_gates(_tk_gate_set(config))
412+
supports_rz = OpType.Rz in primitive_gates
413+
379414
assert optimisation_level in range(3)
380415
passlist = [DecomposeBoxes()]
381416
# If you make changes to the default_compilation_pass,
382417
# then please update this page accordingly
383418
# https://tket.quantinuum.com/extensions/pytket-qiskit/index.html#default-compilation
384419
# Edit this docs source file -> pytket-qiskit/docs/intro.txt
385420
if optimisation_level == 0:
386-
if self._supports_rz:
421+
if supports_rz:
387422
# If the Rz gate is unsupported then the rebase should be skipped
388423
# This prevents an error when compiling to the stabilizer backend
389424
# where no TK1 replacement can be found for the rebase.
390-
passlist.append(self.rebase_pass())
425+
passlist.append(IBMQBackend.rebase_pass_offline(primitive_gates))
391426
elif optimisation_level == 1:
392427
passlist.append(SynthesiseTket())
393428
elif optimisation_level == 2:
394429
passlist.append(FullPeepholeOptimise())
395-
mid_measure = self._backend_info.supports_midcircuit_measurement
396-
arch = self._backend_info.architecture
430+
mid_measure = backend_info.supports_midcircuit_measurement
431+
arch = backend_info.architecture
397432
assert arch is not None
398433
if not isinstance(arch, FullyConnected):
399434
if placement_options is not None:
400435
noise_aware_placement = NoiseAwarePlacement(
401436
arch,
402-
self._backend_info.averaged_node_gate_errors, # type: ignore
403-
self._backend_info.averaged_edge_gate_errors, # type: ignore
404-
self._backend_info.averaged_readout_errors, # type: ignore
437+
backend_info.averaged_node_gate_errors, # type: ignore
438+
backend_info.averaged_edge_gate_errors, # type: ignore
439+
backend_info.averaged_readout_errors, # type: ignore
405440
**placement_options,
406441
)
407442
else:
408443
noise_aware_placement = NoiseAwarePlacement(
409444
arch,
410-
self._backend_info.averaged_node_gate_errors, # type: ignore
411-
self._backend_info.averaged_edge_gate_errors, # type: ignore
412-
self._backend_info.averaged_readout_errors, # type: ignore
445+
backend_info.averaged_node_gate_errors, # type: ignore
446+
backend_info.averaged_edge_gate_errors, # type: ignore
447+
backend_info.averaged_readout_errors, # type: ignore
413448
)
414449

415450
passlist.append(
416451
CXMappingPass(
417452
arch,
418453
noise_aware_placement,
419-
directed_cx=False,
454+
directed_cx=True,
420455
delay_measures=(not mid_measure),
421456
)
422457
)
@@ -432,8 +467,10 @@ def default_compilation_pass(
432467
]
433468
)
434469

435-
if self._supports_rz:
436-
passlist.extend([self.rebase_pass(), RemoveRedundancies()])
470+
if supports_rz:
471+
passlist.extend(
472+
[IBMQBackend.rebase_pass_offline(primitive_gates), RemoveRedundancies()]
473+
)
437474
return SequencePass(passlist)
438475

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

444481
def rebase_pass(self) -> BasePass:
445-
return auto_rebase_pass(self._primitive_gates)
482+
return IBMQBackend.rebase_pass_offline(self._primitive_gates)
483+
484+
@staticmethod
485+
def rebase_pass_offline(primitive_gates: set[OpType]) -> BasePass:
486+
return auto_rebase_pass(primitive_gates)
446487

447488
def process_circuits(
448489
self,

pytket/extensions/qiskit/qiskit_convert.py

+24-12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from uuid import UUID
3535

3636
import numpy as np
37+
from symengine import sympify # type: ignore
3738

3839
import sympy
3940
import qiskit.circuit.library.standard_gates as qiskit_gates # type: ignore
@@ -81,15 +82,13 @@
8182
from pytket.pauli import Pauli, QubitPauliString
8283
from pytket.architecture import Architecture, FullyConnected
8384
from pytket.utils import QubitPauliOperator, gen_term_sequence_circuit
85+
from qiskit.providers.models import BackendProperties, QasmBackendConfiguration # type: ignore
8486

8587
from pytket.passes import RebaseCustom
8688

8789
if TYPE_CHECKING:
8890
from qiskit.providers.backend import BackendV1 as QiskitBackend # type: ignore
89-
from qiskit.providers.models.backendproperties import ( # type: ignore
90-
BackendProperties,
91-
Nduv,
92-
)
91+
from qiskit.providers.models.backendproperties import Nduv # type: ignore
9392
from qiskit.circuit.quantumcircuitdata import QuantumCircuitData # type: ignore
9493
from pytket.circuit import Op, UnitID
9594

@@ -208,9 +207,8 @@
208207
_gate_str_2_optype_rev[OpType.Unitary1qBox] = "unitary"
209208

210209

211-
def _tk_gate_set(backend: "QiskitBackend") -> Set[OpType]:
210+
def _tk_gate_set(config: QasmBackendConfiguration) -> Set[OpType]:
212211
"""Set of tket gate types supported by the qiskit backend"""
213-
config = backend.configuration()
214212
if config.simulator:
215213
gate_set = {
216214
_gate_str_2_optype[gate_str]
@@ -580,7 +578,7 @@ def param_to_qiskit(
580578
if len(ppi.free_symbols) == 0:
581579
return float(ppi.evalf())
582580
else:
583-
return ParameterExpression(symb_map, ppi)
581+
return ParameterExpression(symb_map, sympify(ppi))
584582

585583

586584
def _get_params(
@@ -724,7 +722,7 @@ def append_tk_command_to_qiskit(
724722

725723
if optype == OpType.TK1:
726724
params = _get_params(op, symb_map)
727-
half = ParameterExpression(symb_map, sympy.pi / 2)
725+
half = ParameterExpression(symb_map, sympify(sympy.pi / 2))
728726
qcirc.global_phase += -params[0] / 2 - params[2] / 2
729727
return qcirc.append(
730728
qiskit_gates.UGate(params[1], params[0] - half, params[2] + half),
@@ -749,7 +747,7 @@ def append_tk_command_to_qiskit(
749747
if type(phase) == float:
750748
qcirc.global_phase += phase * np.pi
751749
else:
752-
qcirc.global_phase += phase * sympy.pi
750+
qcirc.global_phase += sympify(phase * sympy.pi)
753751
return qcirc.append(g, qargs=qargs)
754752

755753

@@ -871,10 +869,25 @@ def process_characterisation(backend: "QiskitBackend") -> Dict[str, Any]:
871869
:return: A dictionary containing device characteristics
872870
:rtype: dict
873871
"""
872+
config = backend.configuration()
873+
props = backend.properties()
874+
return process_characterisation_from_config(config, props)
874875

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

877+
def process_characterisation_from_config(
878+
config: QasmBackendConfiguration, properties: Optional[BackendProperties]
879+
) -> Dict[str, Any]:
880+
"""Obtain a dictionary containing device Characteristics given config and props.
881+
882+
:param config: A IBMQ configuration object
883+
:type config: QasmBackendConfiguration
884+
:param properties: An optional IBMQ properties object
885+
:type properties: Optional[BackendProperties]
886+
:return: A dictionary containing device characteristics
887+
:rtype: dict
888+
"""
889+
890+
# TODO explicitly check for and separate 1 and 2 qubit gates
878891
def return_value_if_found(iterator: Iterable["Nduv"], name: str) -> Optional[Any]:
879892
try:
880893
first_found = next(filter(lambda item: item.name == name, iterator))
@@ -884,7 +897,6 @@ def return_value_if_found(iterator: Iterable["Nduv"], name: str) -> Optional[Any
884897
return first_found.value
885898
return None
886899

887-
config = backend.configuration()
888900
coupling_map = config.coupling_map
889901
n_qubits = config.n_qubits
890902
if coupling_map is None:

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@
4444
packages=find_namespace_packages(include=["pytket.*"]),
4545
include_package_data=True,
4646
install_requires=[
47-
"pytket ~= 1.25",
47+
"pytket ~= 1.26",
4848
"qiskit ~= 1.0",
4949
"qiskit-algorithms ~= 0.3.0",
5050
"qiskit-ibm-runtime ~= 0.22.0",
51-
"qiskit-aer ~= 0.13.3",
51+
"qiskit-aer ~= 0.14.0",
5252
"qiskit-ibm-provider ~= 0.10.0",
5353
"numpy",
5454
],

tests/backend_test.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,8 @@ def test_nshots(
476476
circuit = Circuit(1).X(0)
477477
circuit.measure_all()
478478
n_shots = [1, 2, 3]
479-
results = b.get_results(b.process_circuits([circuit] * 3, n_shots=n_shots))
479+
circ_comp = b.get_compiled_circuit(circuit)
480+
results = b.get_results(b.process_circuits([circ_comp] * 3, n_shots=n_shots))
480481
assert [sum(r.get_counts().values()) for r in results] == n_shots
481482

482483

@@ -1330,6 +1331,21 @@ def test_crosstalk_noise_model() -> None:
13301331
res.get_counts()
13311332

13321333

1334+
@pytest.mark.skipif(skip_remote_tests, reason=REASON)
1335+
def test_ecr(ibm_brisbane_backend: IBMQBackend) -> None:
1336+
ghz5 = Circuit(5)
1337+
ghz5.H(0).CX(0, 1).CX(0, 2).CX(0, 3).CX(0, 4)
1338+
ghz5.measure_all()
1339+
ibm_ghz5 = ibm_brisbane_backend.get_compiled_circuit(ghz5)
1340+
1341+
compiled_ghz5 = ibm_brisbane_backend.get_compiled_circuit(ibm_ghz5)
1342+
1343+
ibm_brisbane_backend.valid_circuit(compiled_ghz5)
1344+
1345+
handle = ibm_brisbane_backend.process_circuit(compiled_ghz5, n_shots=1000)
1346+
ibm_brisbane_backend.cancel(handle)
1347+
1348+
13331349
# helper function for testing
13341350
def _get_qiskit_statevector(qc: QuantumCircuit) -> np.ndarray:
13351351
"""Given a QuantumCircuit, use aer_simulator_statevector to compute its

0 commit comments

Comments
 (0)