Skip to content

Commit

Permalink
🚨 adapt for Qiskit 0.46.0 (#422)
Browse files Browse the repository at this point in the history
## Description

This PR updates the project to work with the latest Qiskit version. In
turn, and in preparation for the 1.0 release of Qiskit, it adapts the
project to the expected removals already. While this raises the minimum
supported Qiskit version to 0.46.0, it should make switching to 1.0 as
easy as possible.

## Checklist:

<!---
This checklist serves as a reminder of a couple of things that ensure
your pull request will be merged swiftly.
-->

- [x] The pull request only contains commits that are related to it.
- [x] I have added appropriate tests and documentation.
- [x] I have made sure that all CI jobs on GitHub pass.
- [x] The pull request introduces no new warnings and follows the
project's style guidelines.

---------

Signed-off-by: burgholzer <[email protected]>
  • Loading branch information
burgholzer authored Feb 5, 2024
1 parent 414accd commit 752a66a
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 114 deletions.
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ classifiers = [
]
requires-python = ">=3.8"
dependencies = [
"qiskit[qasm3-import]>=0.45.0",
"qiskit[qasm3-import]>=0.46.0",
"rustworkx[all]>=0.13.0",
"importlib_resources>=5.0; python_version < '3.10'",
"typing_extensions>=4.0"
Expand Down Expand Up @@ -146,6 +146,9 @@ filterwarnings = [
"ignore:.*qiskit.utils.algorithm_globals.QiskitAlgorithmGlobals*:DeprecationWarning:qiskit",
"ignore:.*qiskit.extensions module is pending deprecation*:PendingDeprecationWarning:qiskit",
'ignore:.*datetime\.datetime\.utcfromtimestamp.*:DeprecationWarning:',
'ignore:.*qiskit.utils.parallel*:DeprecationWarning:qiskit',
'ignore:.*qiskit.tools.events*:DeprecationWarning:qiskit',
'ignore:.*qiskit.qasm*:DeprecationWarning:qiskit',
]

[tool.coverage]
Expand Down
7 changes: 1 addition & 6 deletions src/mqt/qmap/load_calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@

from typing import TYPE_CHECKING

from qiskit.providers.models import BackendProperties
from qiskit.transpiler.target import Target

if TYPE_CHECKING:
from .pyqmap import Architecture


def load_calibration(architecture: Architecture, calibration: str | BackendProperties | Target | None = None) -> None:
def load_calibration(architecture: Architecture, calibration: str | Target | None = None) -> None:
"""Load a calibration from a string, BackendProperties, or Target.
Args:
Expand All @@ -23,10 +22,6 @@ def load_calibration(architecture: Architecture, calibration: str | BackendPrope

if isinstance(calibration, str):
architecture.load_properties(calibration)
elif isinstance(calibration, BackendProperties):
from mqt.qmap.qiskit.backend import import_backend_properties

architecture.load_properties(import_backend_properties(calibration))
elif isinstance(calibration, Target):
from mqt.qmap.qiskit.backend import import_target

Expand Down
49 changes: 2 additions & 47 deletions src/mqt/qmap/qiskit/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

from typing import TYPE_CHECKING

from qiskit.providers import Backend, BackendV1, BackendV2
from qiskit.providers import Backend, BackendV1, BackendV2, BackendV2Converter

if TYPE_CHECKING:
from qiskit.providers.models import BackendProperties
from qiskit.transpiler import Target

from mqt.qmap import Architecture
Expand All @@ -24,57 +23,13 @@ def import_backend(backend: Backend) -> Architecture:
"""
if isinstance(backend, BackendV1):
return import_backend_v1(backend)
return import_backend_v2(BackendV2Converter(backend))
if isinstance(backend, BackendV2):
return import_backend_v2(backend)
msg = f"Backend type {type(backend)} not supported."
raise TypeError(msg)


def import_backend_properties(backend_properties: BackendProperties) -> Architecture.Properties:
"""Import backend properties from qiskit.providers.models.BackendProperties.
Args:
backend_properties: The backend properties to import.
Returns:
The imported backend properties as an Architecture.Properties object.
"""
props = Architecture.Properties()
props.name = backend_properties.backend_name
props.num_qubits = len(backend_properties.qubits)
for qubit in range(props.num_qubits):
props.set_t1(qubit, backend_properties.t1(qubit))
props.set_t2(qubit, backend_properties.t2(qubit))
props.set_frequency(qubit, backend_properties.frequency(qubit))
props.set_readout_error(qubit, backend_properties.readout_error(qubit))

for gate in backend_properties.gates:
if gate.gate == "reset":
continue

if len(gate.qubits) == 1:
props.set_single_qubit_error(
gate.qubits[0], gate.gate, backend_properties.gate_error(gate.gate, gate.qubits)
)
elif len(gate.qubits) == 2:
props.set_two_qubit_error(
gate.qubits[0], gate.qubits[1], backend_properties.gate_error(gate.gate, gate.qubits), gate.gate
)
return props


def import_backend_v1(backend: BackendV1) -> Architecture:
"""Import a backend from qiskit.providers.BackendV1."""
arch = Architecture()
arch.name = backend.name()
arch.num_qubits = backend.configuration().n_qubits
arch.coupling_map = {(c[0], c[1]) for c in backend.configuration().coupling_map}
arch.properties = import_backend_properties(backend.properties())

return arch


def import_target(target: Target) -> Architecture.Properties:
"""Import a target from qiskit.transpiler.Target.
Expand Down
15 changes: 14 additions & 1 deletion src/mqt/qmap/subarchitectures.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from collections.abc import Iterable

from matplotlib import figure
from qiskit.providers import BackendV1
from qiskit.providers import BackendV1, BackendV2
from typing_extensions import TypeAlias

from . import Architecture
Expand Down Expand Up @@ -104,6 +104,19 @@ def from_backend(cls, backend: BackendV1) -> SubarchitectureOrder:
coupling_map = [(c[0], c[1]) for c in backend.configuration().coupling_map]
return cls.from_coupling_map(coupling_map)

@classmethod
def from_backend_v2(cls, backend: BackendV2) -> SubarchitectureOrder:
"""Construct the partial order from a coupling map defined as a Qiskit backend.
Args:
backend: Qiskit backend.
Returns:
The resulting partial order.
"""
coupling_map = [(c[0], c[1]) for c in backend.coupling_map]
return cls.from_coupling_map(coupling_map)

@classmethod
def from_qmap_architecture(cls, arch: Architecture) -> SubarchitectureOrder:
"""Construct the partial order from a QMAP :class:`~mqt.qmap.Architecture` object.
Expand Down
2 changes: 1 addition & 1 deletion test/python/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pybind11==2.11.0
pytest==7.0.0
importlib_resources==5.0.0
typing_extensions==4.0.0
qiskit==0.45.0
qiskit==0.46.0
rustworkx==0.13.0
mqt.qcec==2.0.0
distinctipy==1.2.2
Expand Down
5 changes: 2 additions & 3 deletions test/python/test_cliffordsynthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pathlib import Path

import pytest
from qiskit import QuantumCircuit
from qiskit import QuantumCircuit, qasm2
from qiskit.quantum_info import Clifford, PauliList

from mqt import qcec, qmap
Expand Down Expand Up @@ -236,8 +236,7 @@ def test_optimize_quantum_computation(bell_circuit: QuantumCircuit) -> None:

def test_optimize_from_qasm_file(bell_circuit: QuantumCircuit) -> None:
"""Test that we can optimize from a QASM file."""
with Path("bell.qasm").open("w") as f:
f.write(bell_circuit.qasm())
qasm2.dump(bell_circuit, Path("bell.qasm"))
circ, _ = qmap.optimize_clifford(circuit="bell.qasm")
assert qcec.verify(circ, bell_circuit).considered_equivalent()

Expand Down
21 changes: 14 additions & 7 deletions test/python/test_exact_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,29 @@

from __future__ import annotations

import pytest
from qiskit import QuantumCircuit
from qiskit.providers.fake_provider import FakeLondon
from qiskit.providers.fake_provider import GenericBackendV2

from mqt import qmap
from mqt.qcec import verify


def test_exact_no_swaps_trivial_layout() -> None:
@pytest.fixture()
def backend() -> GenericBackendV2:
"""Return a test backend."""
return GenericBackendV2(num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [2, 1], [1, 3], [3, 1], [3, 4], [4, 3]])


def test_exact_no_swaps_trivial_layout(backend: GenericBackendV2) -> None:
"""Verify that the exact mapper works on a simple circuit that requires no swaps on a trivial initial layout."""
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure_all()

qc_mapped, results = qmap.compile(qc, arch=FakeLondon(), method="exact")
qc_mapped, results = qmap.compile(qc, arch=backend, method="exact")
assert results.timeout is False
assert results.mapped_circuit
assert results.output.swaps == 0
Expand All @@ -26,7 +33,7 @@ def test_exact_no_swaps_trivial_layout() -> None:
assert result.considered_equivalent() is True


def test_exact_no_swaps_non_trivial_layout() -> None:
def test_exact_no_swaps_non_trivial_layout(backend: GenericBackendV2) -> None:
"""Verify that the exact mapper works on a simple circuit that requires a non-trivial layout to achieve no swaps."""
qc = QuantumCircuit(4)
qc.h(0)
Expand All @@ -35,7 +42,7 @@ def test_exact_no_swaps_non_trivial_layout() -> None:
qc.cx(0, 3)
qc.measure_all()

qc_mapped, results = qmap.compile(qc, arch=FakeLondon(), method="exact")
qc_mapped, results = qmap.compile(qc, arch=backend, method="exact")

assert results.timeout is False
assert results.mapped_circuit
Expand All @@ -45,7 +52,7 @@ def test_exact_no_swaps_non_trivial_layout() -> None:
assert result.considered_equivalent() is True


def test_exact_non_trivial_swaps() -> None:
def test_exact_non_trivial_swaps(backend: GenericBackendV2) -> None:
"""Verify that the exact mapper works on a simple circuit that requires at least a single SWAP."""
qc = QuantumCircuit(3)
qc.h(0)
Expand All @@ -54,7 +61,7 @@ def test_exact_non_trivial_swaps() -> None:
qc.cx(2, 0)
qc.measure_all()

qc_mapped, results = qmap.compile(qc, arch=FakeLondon(), method="exact")
qc_mapped, results = qmap.compile(qc, arch=backend, method="exact")

assert results.timeout is False
assert results.mapped_circuit
Expand Down
21 changes: 14 additions & 7 deletions test/python/test_heuristic_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,29 @@

from __future__ import annotations

import pytest
from qiskit import QuantumCircuit
from qiskit.providers.fake_provider import FakeLondon
from qiskit.providers.fake_provider import GenericBackendV2

from mqt import qmap
from mqt.qcec import verify


def test_heuristic_no_swaps_trivial_layout() -> None:
@pytest.fixture()
def backend() -> GenericBackendV2:
"""Return a test backend."""
return GenericBackendV2(num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [2, 1], [1, 3], [3, 1], [3, 4], [4, 3]])


def test_heuristic_no_swaps_trivial_layout(backend: GenericBackendV2) -> None:
"""Verify that the heuristic mapper works on a simple circuit that requires no swaps on a trivial initial layout."""
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure_all()

qc_mapped, results = qmap.compile(qc, arch=FakeLondon())
qc_mapped, results = qmap.compile(qc, arch=backend)
assert results.timeout is False
assert results.mapped_circuit
# assert results.output.swaps == 0
Expand All @@ -26,7 +33,7 @@ def test_heuristic_no_swaps_trivial_layout() -> None:
assert result.considered_equivalent() is True


def test_heuristic_no_swaps_non_trivial_layout() -> None:
def test_heuristic_no_swaps_non_trivial_layout(backend: GenericBackendV2) -> None:
"""Verify that the heuristic mapper works on a simple circuit that requires a non-trivial layout to achieve no swaps."""
qc = QuantumCircuit(4)
qc.h(0)
Expand All @@ -35,7 +42,7 @@ def test_heuristic_no_swaps_non_trivial_layout() -> None:
qc.cx(0, 3)
qc.measure_all()

qc_mapped, results = qmap.compile(qc, arch=FakeLondon())
qc_mapped, results = qmap.compile(qc, arch=backend)

assert results.timeout is False
assert results.mapped_circuit
Expand All @@ -45,7 +52,7 @@ def test_heuristic_no_swaps_non_trivial_layout() -> None:
assert result.considered_equivalent() is True


def test_heuristic_non_trivial_swaps() -> None:
def test_heuristic_non_trivial_swaps(backend: GenericBackendV2) -> None:
"""Verify that the heuristic mapper works on a simple circuit that requires at least a single SWAP."""
qc = QuantumCircuit(3)
qc.h(0)
Expand All @@ -54,7 +61,7 @@ def test_heuristic_non_trivial_swaps() -> None:
qc.cx(2, 0)
qc.measure_all()

qc_mapped, results = qmap.compile(qc, arch=FakeLondon())
qc_mapped, results = qmap.compile(qc, arch=backend)

assert results.timeout is False
assert results.mapped_circuit
Expand Down
47 changes: 10 additions & 37 deletions test/python/test_qiskit_backend_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@

import pytest
from qiskit import QuantumCircuit
from qiskit.providers.fake_provider import (
FakeAthens,
FakeAthensV2,
FakeLondon,
FakeLondonV2,
)
from qiskit.providers.fake_provider import GenericBackendV2

from mqt import qmap

Expand All @@ -25,43 +20,21 @@ def example_circuit() -> QuantumCircuit:
return qc


def test_old_backend_v1(example_circuit: QuantumCircuit) -> None:
"""Test that circuits can be mapped to Qiskit BackendV1 instances providing the old basis_gates."""
_, results = qmap.compile(example_circuit, arch=FakeLondon())
assert results.timeout is False
assert results.mapped_circuit


def test_old_backend_v2(example_circuit: QuantumCircuit) -> None:
"""Test that circuits can be mapped to Qiskit BackendV2 instances providing the old basis_gates."""
_, results = qmap.compile(example_circuit, arch=FakeLondonV2())
assert results.timeout is False
assert results.mapped_circuit


def test_new_backend_v1(example_circuit: QuantumCircuit) -> None:
"""Test that circuits can be mapped to Qiskit BackendV1 instances providing the new basis_gates."""
_, results = qmap.compile(example_circuit, arch=FakeAthens())
assert results.timeout is False
assert results.mapped_circuit


def test_new_backend_v2(example_circuit: QuantumCircuit) -> None:
"""Test that circuits can be mapped to Qiskit BackendV2 instances providing the new basis_gates."""
_, results = qmap.compile(example_circuit, arch=FakeAthensV2())
assert results.timeout is False
assert results.mapped_circuit
@pytest.fixture()
def backend() -> GenericBackendV2:
"""Return a test backend."""
return GenericBackendV2(num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [2, 1], [1, 3], [3, 1], [3, 4], [4, 3]])


def test_architecture_from_v1_backend_properties(example_circuit: QuantumCircuit) -> None:
"""Test that circuits can be mapped by simply providing the backend properties (the BackendV1 way)."""
_, results = qmap.compile(example_circuit, arch=None, calibration=FakeLondon().properties())
def test_backend_v2(example_circuit: QuantumCircuit, backend: GenericBackendV2) -> None:
"""Test that circuits can be mapped to Qiskit BackendV1 instances providing the old basis_gates."""
_, results = qmap.compile(example_circuit, arch=backend)
assert results.timeout is False
assert results.mapped_circuit


def test_architecture_from_v2_target(example_circuit: QuantumCircuit) -> None:
def test_architecture_from_v2_target(example_circuit: QuantumCircuit, backend: GenericBackendV2) -> None:
"""Test that circuits can be mapped by simply providing the target (the BackendV2 way)."""
_, results = qmap.compile(example_circuit, arch=None, calibration=FakeLondonV2().target)
_, results = qmap.compile(example_circuit, arch=None, calibration=backend.target)
assert results.timeout is False
assert results.mapped_circuit
Loading

0 comments on commit 752a66a

Please sign in to comment.