From 46c232d1ab97e5abdeee245a7b01390e457cf0b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:21:18 +0100 Subject: [PATCH 01/13] Bump actions/deploy-pages from 4.0.3 to 4.0.4 (#255) Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4.0.3 to 4.0.4. - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](https://github.com/actions/deploy-pages/compare/v4.0.3...v4.0.4) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 387bb8a2..31fa08a2 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -159,4 +159,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4.0.3 + uses: actions/deploy-pages@v4.0.4 From d859d0587fb0bca1316847447fbca091e09fb084 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 15 Feb 2024 10:28:30 +0000 Subject: [PATCH 02/13] fix backend links (#262) --- docs/intro.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/intro.txt b/docs/intro.txt index 6e68a814..780a762d 100644 --- a/docs/intro.txt +++ b/docs/intro.txt @@ -122,15 +122,15 @@ and several types of simulator. * - Backend - Type - * - `IBMQBackend `_ + * - `IBMQBackend `_ - Interface to an IBM quantum computer. - * - `IBMQEmulatorBackend `_ + * - `IBMQEmulatorBackend `_ - Emulator for a chosen ``IBMBackend`` (Device specific). - * - `AerBackend `_ + * - `AerBackend `_ - A noiseless, shots-based simulator for quantum circuits [1] - * - `AerStateBackend `_ + * - `AerStateBackend `_ - Statevector simulator. - * - `AerUnitaryBackend `_ + * - `AerUnitaryBackend `_ - Unitary simulator * [1] :py:class:`AerBackend` is noiseless by default and has no architecture. However it can accept a user defined :py:class:`NoiseModel` and :py:class:`Architecture`. From c6034bddd3ae0b868d2ee12a2c73dba1a98c8955 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:25:55 +0000 Subject: [PATCH 03/13] Fix link. (#270) --- docs/intro.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.txt b/docs/intro.txt index 780a762d..0bdda121 100644 --- a/docs/intro.txt +++ b/docs/intro.txt @@ -65,7 +65,7 @@ In this section we are assuming that you have set the following variables with t group = '' project = '' -.. note:: The documentation below is correct as of pytket-qiskit version 0.40.0 and newer. In the 0.40.0 release pytket-qiskit moved to using the `qiskit-ibm-provider `_. In pytket-qiskit versions 0.39.0 and older the parameters ``hub``, ``group`` and ``project`` were handled separately instead of a single ``instance`` string as in 0.40.0 and newer. +.. note:: The documentation below is correct as of pytket-qiskit version 0.40.0 and newer. In the 0.40.0 release pytket-qiskit moved to using the `qiskit-ibm-provider `_. In pytket-qiskit versions 0.39.0 and older the parameters ``hub``, ``group`` and ``project`` were handled separately instead of a single ``instance`` string as in 0.40.0 and newer. :: From 7b76c0ce0201bb5a8c360d7e9a729a08ca15e0c7 Mon Sep 17 00:00:00 2001 From: cqc-melf <70640934+cqc-melf@users.noreply.github.com> Date: Mon, 4 Mar 2024 12:29:59 +0000 Subject: [PATCH 04/13] mark not working testcase (#277) * remove testcases, solve some issues * update imports --- tests/conftest.py | 9 +++++++++ tests/qiskit_backend_test.py | 30 +++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index af296a10..0e8bd6d0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -73,6 +73,15 @@ def brisbane_emulator_backend() -> IBMQEmulatorBackend: ) +@pytest.fixture(scope="module") +def ibmq_qasm_emulator_backend() -> IBMQEmulatorBackend: + return IBMQEmulatorBackend( + "ibmq_qasm_simulator", + instance="ibm-q/open/main", + token=os.getenv("PYTKET_REMOTE_QISKIT_TOKEN"), + ) + + @pytest.fixture(scope="module") def nairobi_emulator_backend() -> IBMQEmulatorBackend: return IBMQEmulatorBackend( diff --git a/tests/qiskit_backend_test.py b/tests/qiskit_backend_test.py index 26574934..68e563f9 100644 --- a/tests/qiskit_backend_test.py +++ b/tests/qiskit_backend_test.py @@ -23,7 +23,7 @@ from qiskit.providers import JobStatus # type: ignore from qiskit_algorithms import Grover, AmplificationProblem, AlgorithmError # type: ignore from qiskit_aer import Aer # type: ignore - +from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler # type: ignore from qiskit_ibm_provider import IBMProvider # type: ignore from pytket.extensions.qiskit import ( @@ -112,15 +112,17 @@ def test_cancel() -> None: assert job.status() in [JobStatus.CANCELLED, JobStatus.DONE] +# https://github.com/CQCL/pytket-qiskit/issues/272 +@pytest.mark.xfail(reason="Qiskit sampler not working") @pytest.mark.skipif(skip_remote_tests, reason=REASON) -def test_qiskit_counts(nairobi_emulator_backend: IBMQEmulatorBackend) -> None: +def test_qiskit_counts(ibmq_qasm_emulator_backend: IBMQEmulatorBackend) -> None: num_qubits = 2 qc = QuantumCircuit(num_qubits) qc.h(0) qc.cx(0, 1) qc.measure_all() - s = BackendSampler(TketBackend(nairobi_emulator_backend)) + s = BackendSampler(TketBackend(ibmq_qasm_emulator_backend)) job = s.run([qc], shots=10) res = job.result() @@ -129,6 +131,28 @@ def test_qiskit_counts(nairobi_emulator_backend: IBMQEmulatorBackend) -> None: assert all(n in range(4) for n in res.quasi_dists[0].keys()) +# https://github.com/CQCL/pytket-qiskit/issues/272 +@pytest.mark.xfail(reason="Qiskit sampler not working") +@pytest.mark.skipif(skip_remote_tests, reason=REASON) +def test_qiskit_counts_0() -> None: + num_qubits = 32 + qc = QuantumCircuit(num_qubits) + qc.h(0) + qc.cx(0, 1) + qc.measure_all() + + _service = QiskitRuntimeService( + channel="ibm_quantum", + instance="ibm-q/open/main", + token=os.getenv("PYTKET_REMOTE_QISKIT_TOKEN"), + ) + _session = Session(service=_service, backend="ibmq_qasm_simulator") + + sampler = Sampler(session=_session) + job = sampler.run(circuits=qc) + job.result() + + def test_architectures() -> None: # https://github.com/CQCL/pytket-qiskit/issues/14 arch_list = [None, Architecture([(0, 1), (1, 2)]), FullyConnected(3)] From 0d96baecf2014ff20919eb4362092d2d96a234a5 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:04:53 +0000 Subject: [PATCH 05/13] Update pytket, qiskit, qiskit-ibm-provider and qiskit-ibm-runtime (#268) --- docs/changelog.rst | 8 ++++++++ setup.py | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 1fd0cafc..dff3466d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,14 @@ Changelog ~~~~~~~~~ +Unreleased +---------- + +* Update pytket version requirement to 1.25. +* Update qiskit version requirement to 0.46. +* Update qiskit-ibm-provider version requirement to 0.10. +* Update qiskit-ibm-runtime version requirement to 0.19. + 0.48.1rc1 --------- diff --git a/setup.py b/setup.py index f9e0c700..623d324f 100644 --- a/setup.py +++ b/setup.py @@ -44,12 +44,12 @@ packages=find_namespace_packages(include=["pytket.*"]), include_package_data=True, install_requires=[ - "pytket ~= 1.24", - "qiskit ~= 0.45.0", + "pytket ~= 1.25", + "qiskit ~= 0.46.0", "qiskit-algorithms ~= 0.2.1", - "qiskit-ibm-runtime ~= 0.17.0", + "qiskit-ibm-runtime ~= 0.19.0", "qiskit-aer ~= 0.13.0", - "qiskit-ibm-provider ~= 0.8.0", + "qiskit-ibm-provider ~= 0.10.0", "numpy", ], classifiers=[ From f9436bd8c052f8b3a6848d98c8175f378abbb68e Mon Sep 17 00:00:00 2001 From: irfankhan10 Date: Mon, 4 Mar 2024 14:07:22 +0000 Subject: [PATCH 06/13] qiskit session is supplied the instance from IBMQBackend constructor (#276) --- pytket/extensions/qiskit/backends/ibm.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index 324cbae5..0fbbb6c9 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -194,7 +194,9 @@ def __init__( gate_set = _tk_gate_set(self._backend) self._backend_info = self._get_backend_info(self._backend) - self._service = QiskitRuntimeService(channel="ibm_quantum", token=token) + self._service = QiskitRuntimeService( + channel="ibm_quantum", token=token, instance=instance + ) self._session = Session(service=self._service, backend=backend_name) self._primitive_gates = _get_primitive_gates(gate_set) From 57af3a2535fe627d2b45c6f30306513ad0f9d560 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:01:15 +0000 Subject: [PATCH 07/13] Use sphinx autosummary for pytket-qiskit API docs (#265) * use autosummary for pytket-qiskit docs * ignore jupyter-execute * expand snippet * add config methods and CrossTalkParams * separate config and crosstalk * remove duplicate docs on Backends * minor content additons * fix link * remove snippets from api.html for now * dont use jupyter sphinx for now * remove requirement * update .gitignore * dont show inheritance for Aer backends * fix link to credentials docs * reorder autosummary content * remove additional line * move CrossTalkParams table * remove section on Backend predicates * move autosummary to index page * only document IBMQEmulatorbackend class, not the __init__ method * remove docstring for __init__ method * remove extra space --- docs/api.rst | 3 - docs/intro.txt | 103 ++++++++++++------ pytket/extensions/qiskit/backends/aer.py | 55 ++++++---- pytket/extensions/qiskit/backends/ibm.py | 56 +++++----- .../qiskit/backends/ibmq_emulator.py | 12 +- 5 files changed, 135 insertions(+), 94 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index f7ebe38d..191152d5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -13,19 +13,16 @@ API documentation .. autoclass:: pytket.extensions.qiskit.AerBackend :special-members: __init__ - :show-inheritance: :inherited-members: :members: .. autoclass:: pytket.extensions.qiskit.AerStateBackend :special-members: __init__ :inherited-members: - :show-inheritance: :members: .. autoclass:: pytket.extensions.qiskit.AerUnitaryBackend :special-members: __init__ - :show-inheritance: :inherited-members: :members: diff --git a/docs/intro.txt b/docs/intro.txt index 0bdda121..1c9f1e47 100644 --- a/docs/intro.txt +++ b/docs/intro.txt @@ -1,5 +1,5 @@ pytket-qiskit -================================== +############# IBM's `Qiskit `_ is an open-source framework for quantum computation, ranging from high-level algorithms to low-level circuit @@ -19,6 +19,21 @@ Windows. To install, run: This will install ``pytket`` if it isn't already installed, and add new classes and methods into the ``pytket.extensions`` namespace. +Available IBM Backends +====================== + +.. currentmodule:: pytket.extensions.qiskit + +.. autosummary:: + :nosignatures: + + IBMQBackend + IBMQEmulatorBackend + AerBackend + AerStateBackend + AerUnitaryBackend + + An example using the shots-based :py:class:`AerBackend` simulator is shown below. :: @@ -110,31 +125,30 @@ To see which devices you can access you can use the ``available_devices`` method print([backend.device_name for backend in backendinfo_list]) -Backends Available Through pytket-qiskit -======================================== +.. currentmodule:: pytket.extensions.qiskit.backends.config -The ``pytket-qiskit`` extension has several types of available :py:class:`Backend`. These are the :py:class:`IBMQBackend` -and several types of simulator. +.. autosummary:: + :nosignatures: -.. list-table:: - :widths: 25 25 - :header-rows: 1 + QiskitConfig + set_ibmq_config + + +Converting circuits between pytket and qiskit +============================================= + +Users may wish to port quantum circuits between pytket and qiskit. This allows the features of both libraries to be used. +For instance those familiar with qiskit may wish to convert their circuits to pytket and use the available compilation passes to optimise circuits. + +.. currentmodule:: pytket.extensions.qiskit + + +.. autosummary:: + :nosignatures: + + qiskit_to_tk + tk_to_qiskit - * - Backend - - Type - * - `IBMQBackend `_ - - Interface to an IBM quantum computer. - * - `IBMQEmulatorBackend `_ - - Emulator for a chosen ``IBMBackend`` (Device specific). - * - `AerBackend `_ - - A noiseless, shots-based simulator for quantum circuits [1] - * - `AerStateBackend `_ - - Statevector simulator. - * - `AerUnitaryBackend `_ - - Unitary simulator - -* [1] :py:class:`AerBackend` is noiseless by default and has no architecture. However it can accept a user defined :py:class:`NoiseModel` and :py:class:`Architecture`. -* In addition to the backends above the ``pytket-qiskit`` extension also has the :py:class:`TketBackend`. This allows a tket :py:class:`Backend` to be used directly through qiskit. see the `notebook example `_ on qiskit integration. Default Compilation =================== @@ -184,21 +198,44 @@ Every :py:class:`Backend` in pytket has its own ``default_compilation_pass`` met **Note:** The ``default_compilation_pass`` for :py:class:`AerBackend` is the same as above. -Backend Predicates -================== +Noise Modelling +=============== + +.. currentmodule:: pytket.extensions.qiskit.backends.crosstalk_model + +.. autosummary:: + :nosignatures: + + CrosstalkParams + + +Using TKET directly on qiskit circuits +====================================== + +For usage of :py:class:`TketBackend` see the `qiskit integration notebook example `_. + +.. currentmodule:: pytket.extensions.qiskit.tket_backend + +.. autosummary:: + :nosignatures: + + TketBackend + +.. currentmodule:: pytket.extensions.qiskit.tket_pass + +.. autosummary:: + :nosignatures: -Circuits must satisfy certain conditions before they can be processed on a device or simulator. In pytket these conditions are called predicates. + TketPass + TketAutoPass -All ``pytket-qiskit`` backends have the following two predicates. +.. currentmodule:: pytket.extensions.qiskit.tket_job -* `GateSetPredicate `_ - The circuit must contain only operations supported by the :py:class`Backend`. To view supported Ops run ``BACKENDNAME.backend_info.gate_set``. -* `NoSymbolsPredicate `_ - Parameterised gates must have numerical values when the circuit is executed. +.. autosummary:: + :nosignatures: -The :py:class:`IBMQBackend` and :py:class:`IBMQEmulatorBackend` may also have the following predicates depending on the capabilities of the specified device. + TketJob -* `NoClassicalControlPredicate `_ -* `NoMidMeasurePredicate `_ -* `NoFastFeedforwardPredicate `_ .. toctree:: api.rst diff --git a/pytket/extensions/qiskit/backends/aer.py b/pytket/extensions/qiskit/backends/aer.py index 480d4b45..87ccf250 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -449,6 +449,21 @@ def _get_characterisation_of_noise_model( class AerBackend(_AerBaseBackend): + """ + Backend for running simulations on the Qiskit Aer QASM simulator. + + :param noise_model: Noise model to apply during simulation. Defaults to None. + :type noise_model: Optional[NoiseModel], optional + :param simulation_method: Simulation method, see + https://qiskit.org/documentation/stubs/qiskit.providers.aer.AerSimulator.html + for available values. Defaults to "automatic". + :type simulation_method: str + :param crosstalk_params: Apply crosstalk noise simulation to the circuits before + execution. `noise_model` will be overwritten if this is given. Default to None. + :type: Optional[`CrosstalkParams`] + :param n_qubits: The maximum number of qubits supported by the backend. + """ + _persistent_handles = False _supports_shots = True _supports_counts = True @@ -472,19 +487,6 @@ def __init__( crosstalk_params: Optional[CrosstalkParams] = None, n_qubits: int = 40, ): - """Backend for running simulations on the Qiskit Aer QASM simulator. - - :param noise_model: Noise model to apply during simulation. Defaults to None. - :type noise_model: Optional[NoiseModel], optional - :param simulation_method: Simulation method, see - https://qiskit.org/documentation/stubs/qiskit.providers.aer.AerSimulator.html - for available values. Defaults to "automatic". - :type simulation_method: str - :param crosstalk_params: Apply crosstalk noise simulation to the circuits before - execution. `noise_model` will be overwritten if this is given. Default to None. - :type: Optional[`CrosstalkParams`] - :param n_qubits: The maximum number of qubits supported by the backend. - """ super().__init__() self._qiskit_backend: "QiskitAerBackend" = Aer.get_backend( self._qiskit_backend_name @@ -517,9 +519,11 @@ def __init__( name=type(self).__name__, device_name=self._qiskit_backend_name, version=__extension_version__, - architecture=characterisation.architecture - if self._has_arch - else FullyConnected(n_qubits), + architecture=( + characterisation.architecture + if self._has_arch + else FullyConnected(n_qubits) + ), gate_set=gate_set, supports_midcircuit_measurement=True, # is this correct? supports_fast_feedforward=True, @@ -553,6 +557,12 @@ def __init__( class AerStateBackend(_AerBaseBackend): + """ + Backend for running simulations on the Qiskit Aer Statevector simulator. + + :param n_qubits: The maximum number of qubits supported by the backend. + """ + _persistent_handles = False _supports_state = True _supports_expectation = True @@ -567,10 +577,6 @@ def __init__( self, n_qubits: int = 40, ) -> None: - """Backend for running simulations on the Qiskit Aer Statevector simulator. - - :param n_qubits: The maximum number of qubits supported by the backend. - """ super().__init__() self._qiskit_backend: "QiskitAerBackend" = Aer.get_backend( self._qiskit_backend_name @@ -592,6 +598,11 @@ def __init__( class AerUnitaryBackend(_AerBaseBackend): + """Backend for running simulations on the Qiskit Aer Unitary simulator. + + :param n_qubits: The maximum number of qubits supported by the backend. + """ + _persistent_handles = False _supports_unitary = True @@ -602,10 +613,6 @@ class AerUnitaryBackend(_AerBaseBackend): _qiskit_backend_name = "aer_simulator_unitary" def __init__(self, n_qubits: int = 40) -> None: - """Backend for running simulations on the Qiskit Aer Unitary simulator. - - :param n_qubits: The maximum number of qubits supported by the backend. - """ super().__init__() self._qiskit_backend: "QiskitAerBackend" = Aer.get_backend( self._qiskit_backend_name diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index 0fbbb6c9..6a4523a0 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -149,6 +149,26 @@ def _get_primitive_gates(gateset: Set[OpType]) -> Set[OpType]: class IBMQBackend(Backend): + """A backend for running circuits on remote IBMQ devices. + + The provider arguments of `hub`, `group` and `project` can + be specified here as parameters or set in the config file + using :py:meth:`pytket.extensions.qiskit.set_ibmq_config`. + This function can also be used to set the IBMQ API token. + + :param backend_name: Name of the IBMQ device, e.g. `ibmq_16_melbourne`. + :type backend_name: str + :param instance: String containing information about the hub/group/project. + :type instance: str, optional + :param monitor: Use the IBM job monitor. Defaults to True. + :type monitor: bool, optional + :raises ValueError: If no IBMQ account is loaded and none exists on the disk. + :param provider: An IBMProvider + :type provider: Optional[IBMProvider] + :param token: Authentication token to use the `QiskitRuntimeService`. + :type token: Optional[str] + """ + _supports_shots = False _supports_counts = True _supports_contextual_optimisation = True @@ -162,24 +182,6 @@ def __init__( provider: Optional["IBMProvider"] = None, token: Optional[str] = None, ): - """A backend for running circuits on remote IBMQ devices. - The provider arguments of `hub`, `group` and `project` can - be specified here as parameters or set in the config file - using :py:meth:`pytket.extensions.qiskit.set_ibmq_config`. - This function can also be used to set the IBMQ API token. - - :param backend_name: Name of the IBMQ device, e.g. `ibmq_16_melbourne`. - :type backend_name: str - :param instance: String containing information about the hub/group/project. - :type instance: str, optional - :param monitor: Use the IBM job monitor. Defaults to True. - :type monitor: bool, optional - :raises ValueError: If no IBMQ account is loaded and none exists on the disk. - :param provider: An IBMProvider - :type provider: Optional[IBMProvider] - :param token: Authentication token to use the `QiskitRuntimeService`. - :type token: Optional[str] - """ super().__init__() self._pytket_config = QiskitConfig.from_default_config_file() self._provider = ( @@ -274,14 +276,16 @@ def _get_backend_info(cls, backend: "_QiskIBMBackend") -> BackendInfo: backend.name, __extension_version__, arch, - gate_set.union( - { - OpType.RangePredicate, - OpType.Conditional, - } - ) - if supports_fast_feedforward - else gate_set, + ( + gate_set.union( + { + OpType.RangePredicate, + OpType.Conditional, + } + ) + if supports_fast_feedforward + else gate_set + ), supports_midcircuit_measurement=supports_mid_measure, supports_fast_feedforward=supports_fast_feedforward, supports_reset=supports_reset, diff --git a/pytket/extensions/qiskit/backends/ibmq_emulator.py b/pytket/extensions/qiskit/backends/ibmq_emulator.py index 93297427..f0167d86 100644 --- a/pytket/extensions/qiskit/backends/ibmq_emulator.py +++ b/pytket/extensions/qiskit/backends/ibmq_emulator.py @@ -54,10 +54,10 @@ class IBMQEmulatorBackend(Backend): - """A backend which uses the AerBackend simulator to emulate the behaviour of - IBMQBackend. Performs the same compilation and predicate checks as IBMQBackend. - Requires a valid IBMQ account. - + """A Backend which uses the AerBackend simulator to emulate the behaviour of + IBMQBackend. Identical to :py:class:`IBMQBackend` except there is no `monitor` + parameter. Performs the same compilation and predicate checks as IBMQBackend. + Requires a valid IBM account. """ _supports_shots = False @@ -73,10 +73,6 @@ def __init__( provider: Optional["IBMProvider"] = None, token: Optional[str] = None, ): - """Construct an IBMQEmulatorBackend. Identical to :py:class:`IBMQBackend` - constructor, except there is no `monitor` parameter. See :py:class:`IBMQBackend` - docs for more details. - """ super().__init__() self._ibmq = IBMQBackend( backend_name=backend_name, From 3aeb53cd084a79549de95b156ecfd49659b90414 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:39:37 +0000 Subject: [PATCH 08/13] Add local emulator backend (#273) --- docs/api.rst | 5 + docs/changelog.rst | 2 + docs/intro.txt | 3 +- pytket/extensions/qiskit/__init__.py | 1 + pytket/extensions/qiskit/backends/__init__.py | 1 + .../qiskit/backends/ibmq_emulator.py | 4 +- .../qiskit/backends/ibmq_local_emulator.py | 136 ++++++++++++++++++ tests/backend_test.py | 136 ++++++++++-------- tests/conftest.py | 15 +- 9 files changed, 243 insertions(+), 60 deletions(-) create mode 100644 pytket/extensions/qiskit/backends/ibmq_local_emulator.py diff --git a/docs/api.rst b/docs/api.rst index 191152d5..033acfb9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -11,6 +11,11 @@ API documentation :show-inheritance: :members: +.. autoclass:: pytket.extensions.qiskit.IBMQLocalEmulatorBackend + :special-members: __init__ + :show-inheritance: + :members: + .. autoclass:: pytket.extensions.qiskit.AerBackend :special-members: __init__ :inherited-members: diff --git a/docs/changelog.rst b/docs/changelog.rst index dff3466d..cb6b19cc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,6 +8,8 @@ Unreleased * Update qiskit version requirement to 0.46. * Update qiskit-ibm-provider version requirement to 0.10. * Update qiskit-ibm-runtime version requirement to 0.19. +* Add ``IBMQLocalEmulatorBackend`` for running local emulation of + ``IBMQBackend`` using ``AerBackend`` with a noise model. 0.48.1rc1 --------- diff --git a/docs/intro.txt b/docs/intro.txt index 1c9f1e47..ab87481a 100644 --- a/docs/intro.txt +++ b/docs/intro.txt @@ -29,6 +29,7 @@ Available IBM Backends IBMQBackend IBMQEmulatorBackend + IBMQLocalEmulatorBackend AerBackend AerStateBackend AerUnitaryBackend @@ -155,7 +156,7 @@ Default Compilation Every :py:class:`Backend` in pytket has its own ``default_compilation_pass`` method. This method applies a sequence of optimisations to a circuit depending on the value of an ``optimisation_level`` parameter. This default compilation will ensure that the circuit meets all the constraints required to run on the :py:class:`Backend`. The passes applied by different levels of optimisation are specified in the table below. -.. list-table:: **Default compilation pass for the IBMQBackend and IBMQEmulatorBackend** +.. list-table:: **Default compilation pass for the IBMQBackend, IBMQEmulatorBackend and IBMQLocalEmulatorBackend** :widths: 25 25 25 :header-rows: 1 diff --git a/pytket/extensions/qiskit/__init__.py b/pytket/extensions/qiskit/__init__.py index 0887ac7f..98aba5f4 100644 --- a/pytket/extensions/qiskit/__init__.py +++ b/pytket/extensions/qiskit/__init__.py @@ -22,6 +22,7 @@ AerStateBackend, AerUnitaryBackend, IBMQEmulatorBackend, + IBMQLocalEmulatorBackend, ) from .backends.config import set_ibmq_config from .qiskit_convert import qiskit_to_tk, tk_to_qiskit, process_characterisation diff --git a/pytket/extensions/qiskit/backends/__init__.py b/pytket/extensions/qiskit/backends/__init__.py index a7a0c547..0b6b8f88 100644 --- a/pytket/extensions/qiskit/backends/__init__.py +++ b/pytket/extensions/qiskit/backends/__init__.py @@ -16,3 +16,4 @@ from .ibm import IBMQBackend, NoIBMQCredentialsError from .aer import AerBackend, AerStateBackend, AerUnitaryBackend from .ibmq_emulator import IBMQEmulatorBackend +from .ibmq_local_emulator import IBMQLocalEmulatorBackend diff --git a/pytket/extensions/qiskit/backends/ibmq_emulator.py b/pytket/extensions/qiskit/backends/ibmq_emulator.py index f0167d86..84d1da3d 100644 --- a/pytket/extensions/qiskit/backends/ibmq_emulator.py +++ b/pytket/extensions/qiskit/backends/ibmq_emulator.py @@ -54,10 +54,10 @@ class IBMQEmulatorBackend(Backend): - """A Backend which uses the AerBackend simulator to emulate the behaviour of + """A Backend which uses the ibmq_qasm_simulator to emulate the behaviour of IBMQBackend. Identical to :py:class:`IBMQBackend` except there is no `monitor` parameter. Performs the same compilation and predicate checks as IBMQBackend. - Requires a valid IBM account. + Requires a valid IBMQ account. """ _supports_shots = False diff --git a/pytket/extensions/qiskit/backends/ibmq_local_emulator.py b/pytket/extensions/qiskit/backends/ibmq_local_emulator.py new file mode 100644 index 00000000..88e0764f --- /dev/null +++ b/pytket/extensions/qiskit/backends/ibmq_local_emulator.py @@ -0,0 +1,136 @@ +# Copyright 2019-2024 Cambridge Quantum Computing +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import Counter +from typing import ( + Dict, + Optional, + List, + Sequence, + Tuple, + Union, +) + +from qiskit.providers.aer.noise.noise_model import NoiseModel # type: ignore + +from qiskit_ibm_provider import IBMProvider # type: ignore + +from pytket.backends import ( + Backend, + ResultHandle, + CircuitStatus, +) +from pytket.backends.backendinfo import BackendInfo +from pytket.backends.backendresult import BackendResult +from pytket.backends.resulthandle import _ResultIdTuple +from pytket.circuit import Circuit +from pytket.passes import BasePass +from pytket.predicates import Predicate +from pytket.utils.results import KwargTypes + +from .aer import AerBackend +from .ibm import IBMQBackend + + +class IBMQLocalEmulatorBackend(Backend): + """A backend which uses the AerBackend to loaclly emulate the behaviour of + IBMQBackend. Identical to :py:class:`IBMQBackend` except there is no `monitor` + parameter. Performs the same compilation and predicate checks as IBMQBackend. + Requires a valid IBM account. + """ + + _supports_shots = False + _supports_counts = True + _supports_contextual_optimisation = False + _persistent_handles = False + _supports_expectation = False + + def __init__( + self, + backend_name: str, + instance: Optional[str] = None, + provider: Optional["IBMProvider"] = None, + token: Optional[str] = None, + ): + super().__init__() + self._ibmq = IBMQBackend( + backend_name=backend_name, + instance=instance, + provider=provider, + token=token, + ) + + # Get noise model: + self._noise_model = NoiseModel.from_backend(self._ibmq._backend) + + # Construct AerBackend based on noise model: + self._aer = AerBackend(noise_model=self._noise_model) + + # cache of results keyed by job id and circuit index + self._ibm_res_cache: Dict[Tuple[str, int], Counter] = dict() + + @property + def backend_info(self) -> BackendInfo: + return self._ibmq._backend_info + + @property + def required_predicates(self) -> List[Predicate]: + return self._ibmq.required_predicates + + def default_compilation_pass( + self, optimisation_level: int = 2, placement_options: Optional[Dict] = None + ) -> BasePass: + """ + See documentation for :py:meth:`IBMQBackend.default_compilation_pass`. + """ + return self._ibmq.default_compilation_pass( + optimisation_level=optimisation_level, placement_options=placement_options + ) + + @property + def _result_id_type(self) -> _ResultIdTuple: + return self._aer._result_id_type + + def rebase_pass(self) -> BasePass: + return self._ibmq.rebase_pass() + + def process_circuits( + self, + circuits: Sequence[Circuit], + n_shots: Union[None, int, Sequence[Optional[int]]] = None, + valid_check: bool = True, + **kwargs: KwargTypes, + ) -> List[ResultHandle]: + """ + See :py:meth:`pytket.backends.Backend.process_circuits`. + Supported kwargs: `seed`, `postprocess`. + """ + if valid_check: + self._ibmq._check_all_circuits(circuits) + return self._aer.process_circuits( + circuits, n_shots=n_shots, valid_check=False, **kwargs + ) + + def cancel(self, handle: ResultHandle) -> None: + self._aer.cancel(handle) + + def circuit_status(self, handle: ResultHandle) -> CircuitStatus: + return self._aer.circuit_status(handle) + + def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResult: + """ + See :py:meth:`pytket.backends.Backend.get_result`. + Supported kwargs: none. + """ + return self._aer.get_result(handle) diff --git a/tests/backend_test.py b/tests/backend_test.py index 5dfc5130..4ac610cd 100644 --- a/tests/backend_test.py +++ b/tests/backend_test.py @@ -63,6 +63,7 @@ AerStateBackend, AerUnitaryBackend, IBMQEmulatorBackend, + IBMQLocalEmulatorBackend, ) from pytket.extensions.qiskit import ( qiskit_to_tk, @@ -468,8 +469,11 @@ def test_nshots_batching(brisbane_backend: IBMQBackend) -> None: @pytest.mark.flaky(reruns=3, reruns_delay=10) @pytest.mark.skipif(skip_remote_tests, reason=REASON) -def test_nshots(brisbane_emulator_backend: IBMQEmulatorBackend) -> None: - for b in [AerBackend(), brisbane_emulator_backend]: +def test_nshots( + brisbane_emulator_backend: IBMQEmulatorBackend, + brisbane_local_emulator_backend: IBMQLocalEmulatorBackend, +) -> None: + for b in [AerBackend(), brisbane_emulator_backend, brisbane_local_emulator_backend]: circuit = Circuit(1).X(0) circuit.measure_all() n_shots = [1, 2, 3] @@ -830,43 +834,42 @@ def test_operator_expectation_value() -> None: @pytest.mark.flaky(reruns=3, reruns_delay=10) @pytest.mark.skipif(skip_remote_tests, reason=REASON) -def test_ibmq_emulator(brisbane_emulator_backend: IBMQEmulatorBackend) -> None: - assert brisbane_emulator_backend._noise_model is not None - b_ibm = brisbane_emulator_backend._ibmq - b_aer = AerBackend() - for ol in range(3): - comp_pass = brisbane_emulator_backend.default_compilation_pass(ol) - c = Circuit(3, 3) - c.H(0) - c.CX(0, 1) - c.CSWAP(1, 0, 2) - c.ZZPhase(0.84, 2, 0) - c_cop = c.copy() - comp_pass.apply(c_cop) - c.measure_all() - for bac in (brisbane_emulator_backend, b_ibm): - assert all(pred.verify(c_cop) for pred in bac.required_predicates) - - c_cop_2 = c.copy() - c_cop_2 = b_aer.get_compiled_circuit(c_cop_2, ol) - if ol == 0: - assert not all( - pred.verify(c_cop_2) - for pred in brisbane_emulator_backend.required_predicates - ) - - circ = Circuit(2, 2).H(0).CX(0, 1).measure_all() - copy_circ = circ.copy() - brisbane_emulator_backend.rebase_pass().apply(copy_circ) - assert brisbane_emulator_backend.required_predicates[1].verify(copy_circ) - circ = brisbane_emulator_backend.get_compiled_circuit(circ) - b_noi = AerBackend(noise_model=brisbane_emulator_backend._noise_model) - emu_counts = brisbane_emulator_backend.run_circuit( - circ, n_shots=10, seed=10 - ).get_counts() - aer_counts = b_noi.run_circuit(circ, n_shots=10, seed=10).get_counts() - # Even with the same seed, the results may differ. - assert sum(emu_counts.values()) == sum(aer_counts.values()) +def test_ibmq_emulator( + brisbane_emulator_backend: IBMQEmulatorBackend, + brisbane_local_emulator_backend: IBMQLocalEmulatorBackend, +) -> None: + for b in [brisbane_emulator_backend, brisbane_local_emulator_backend]: + assert b._noise_model is not None # type: ignore + b_ibm = b._ibmq # type: ignore + b_aer = AerBackend() + for ol in range(3): + comp_pass = b.default_compilation_pass(ol) + c = Circuit(3, 3) + c.H(0) + c.CX(0, 1) + c.CSWAP(1, 0, 2) + c.ZZPhase(0.84, 2, 0) + c_cop = c.copy() + comp_pass.apply(c_cop) + c.measure_all() + for bac in (b, b_ibm): + assert all(pred.verify(c_cop) for pred in bac.required_predicates) + + c_cop_2 = c.copy() + c_cop_2 = b_aer.get_compiled_circuit(c_cop_2, ol) + if ol == 0: + assert not all(pred.verify(c_cop_2) for pred in b.required_predicates) + + circ = Circuit(2, 2).H(0).CX(0, 1).measure_all() + copy_circ = circ.copy() + b.rebase_pass().apply(copy_circ) + assert b.required_predicates[1].verify(copy_circ) + circ = b.get_compiled_circuit(circ) + b_noi = AerBackend(noise_model=b._noise_model) # type: ignore + emu_counts = b.run_circuit(circ, n_shots=10, seed=10).get_counts() + aer_counts = b_noi.run_circuit(circ, n_shots=10, seed=10).get_counts() + # Even with the same seed, the results may differ. + assert sum(emu_counts.values()) == sum(aer_counts.values()) @given( @@ -1165,12 +1168,14 @@ def test_available_devices(ibm_provider: IBMProvider) -> None: @pytest.mark.skipif(skip_remote_tests, reason=REASON) def test_backendinfo_serialization1( brisbane_emulator_backend: IBMQEmulatorBackend, + brisbane_local_emulator_backend: IBMQLocalEmulatorBackend, ) -> None: # https://github.com/CQCL/tket/issues/192 - backend_info_json = brisbane_emulator_backend.backend_info.to_dict() - s = json.dumps(backend_info_json) - backend_info_json1 = json.loads(s) - assert backend_info_json == backend_info_json1 + for b in [brisbane_emulator_backend, brisbane_local_emulator_backend]: + backend_info_json = b.backend_info.to_dict() # type: ignore + s = json.dumps(backend_info_json) + backend_info_json1 = json.loads(s) + assert backend_info_json == backend_info_json1 def test_backendinfo_serialization2() -> None: @@ -1218,20 +1223,24 @@ def test_sim_qubit_order() -> None: @pytest.mark.flaky(reruns=3, reruns_delay=10) @pytest.mark.skipif(skip_remote_tests, reason=REASON) -def test_requrired_predicates(brisbane_emulator_backend: IBMQEmulatorBackend) -> None: +def test_required_predicates( + brisbane_emulator_backend: IBMQEmulatorBackend, + brisbane_local_emulator_backend: IBMQLocalEmulatorBackend, +) -> None: # https://github.com/CQCL/pytket-qiskit/issues/93 - circ = Circuit(8) # 8 qubit circuit in IBMQ gateset - circ.X(0).CX(0, 1).CX(0, 2).CX(0, 3).CX(0, 4).CX(0, 5).CX(0, 6).CX( - 0, 7 - ).measure_all() - with pytest.raises(CircuitNotValidError) as errorinfo: - brisbane_emulator_backend.run_circuit(circ, n_shots=100) - assert ( - "pytket.backends.backend_exceptions.CircuitNotValidError:" - + "Circuit with index 0 in submitted does" - + "not satisfy MaxNQubitsPredicate(5)" - in str(errorinfo) - ) + for b in [brisbane_emulator_backend, brisbane_local_emulator_backend]: + circ = Circuit(8) # 8 qubit circuit in IBMQ gateset + circ.X(0).CX(0, 1).CX(0, 2).CX(0, 3).CX(0, 4).CX(0, 5).CX(0, 6).CX( + 0, 7 + ).measure_all() + with pytest.raises(CircuitNotValidError) as errorinfo: + b.run_circuit(circ, n_shots=100) + assert ( + "pytket.backends.backend_exceptions.CircuitNotValidError:" + + "Circuit with index 0 in submitted does" + + "not satisfy MaxNQubitsPredicate(5)" + in str(errorinfo) + ) @pytest.mark.flaky(reruns=3, reruns_delay=10) @@ -1433,3 +1442,18 @@ def test_barriers_in_aer_simulators() -> None: assert compare_statevectors(test_state, state_result) assert compare_unitaries(test_unitary, unitary_result) + + +@pytest.mark.skipif(skip_remote_tests, reason=REASON) +def test_ibmq_local_emulator( + brisbane_local_emulator_backend: IBMQLocalEmulatorBackend, +) -> None: + b = brisbane_local_emulator_backend + assert not b.supports_contextual_optimisation + circ = Circuit(2).H(0).CX(0, 1).measure_all() + circ1 = b.get_compiled_circuit(circ) + h = b.process_circuit(circ1, n_shots=100) + r = b.get_result(h) + counts = r.get_counts() + # Most results should be (0,0) or (1,1): + assert sum(c0 != c1 for c0, c1 in counts) < 25 diff --git a/tests/conftest.py b/tests/conftest.py index 0e8bd6d0..25255efe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,7 +16,11 @@ import pytest from qiskit_ibm_provider import IBMProvider # type: ignore -from pytket.extensions.qiskit import IBMQBackend, IBMQEmulatorBackend +from pytket.extensions.qiskit import ( + IBMQBackend, + IBMQEmulatorBackend, + IBMQLocalEmulatorBackend, +) @pytest.fixture(autouse=True, scope="session") @@ -91,6 +95,15 @@ def nairobi_emulator_backend() -> IBMQEmulatorBackend: ) +@pytest.fixture(scope="module") +def brisbane_local_emulator_backend() -> IBMQLocalEmulatorBackend: + return IBMQLocalEmulatorBackend( + "ibm_brisbane", + instance="ibm-q/open/main", + token=os.getenv("PYTKET_REMOTE_QISKIT_TOKEN"), + ) + + @pytest.fixture(scope="module") def ibm_provider() -> IBMProvider: token = os.getenv("PYTKET_REMOTE_QISKIT_TOKEN") From ff1b092552771f06ee8081a18f6f8994ac70ffe4 Mon Sep 17 00:00:00 2001 From: cqc-melf <70640934+cqc-melf@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:10:56 +0000 Subject: [PATCH 09/13] update qiskit 1 0 0 (#280) * Simple fixes * Fix for removed Clbit attributes * Update changelog * Update docs * lint :sparkles: * Update ibmq_local_emulator.py * Update setup.py * Update pytket/extensions/qiskit/backends/aer.py * Update docs/changelog.rst * Update setup.py Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> * fix testcase with backend --------- Co-authored-by: Frank Harkins Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- docs/changelog.rst | 2 +- docs/intro.txt | 2 +- pytket/extensions/qiskit/backends/aer.py | 8 ++-- .../qiskit/backends/crosstalk_model.py | 2 +- .../qiskit/backends/ibmq_emulator.py | 2 +- .../qiskit/backends/ibmq_local_emulator.py | 2 +- pytket/extensions/qiskit/qiskit_convert.py | 19 +++++++--- pytket/extensions/qiskit/tket_pass.py | 2 +- setup.py | 4 +- tests/backend_test.py | 16 ++++---- tests/qiskit_backend_test.py | 16 ++++---- tests/qiskit_convert_test.py | 38 +++++++++++-------- 12 files changed, 63 insertions(+), 50 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index cb6b19cc..ba1bd6ff 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,7 +5,7 @@ Unreleased ---------- * Update pytket version requirement to 1.25. -* Update qiskit version requirement to 0.46. +* Update qiskit version requirement to 1.0. * Update qiskit-ibm-provider version requirement to 0.10. * Update qiskit-ibm-runtime version requirement to 0.19. * Add ``IBMQLocalEmulatorBackend`` for running local emulation of diff --git a/docs/intro.txt b/docs/intro.txt index ab87481a..51cb5add 100644 --- a/docs/intro.txt +++ b/docs/intro.txt @@ -1,7 +1,7 @@ pytket-qiskit ############# -IBM's `Qiskit `_ is an open-source framework for quantum +IBM's `Qiskit `_ is an open-source framework for quantum computation, ranging from high-level algorithms to low-level circuit representations, simulation and access to the `IBMQ `_ Experience devices. diff --git a/pytket/extensions/qiskit/backends/aer.py b/pytket/extensions/qiskit/backends/aer.py index 87ccf250..3bcb57d1 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -30,7 +30,7 @@ import numpy as np from qiskit import transpile # type: ignore -from qiskit.providers.aer.noise import NoiseModel # type: ignore +from qiskit_aer.noise import NoiseModel # type: ignore from qiskit.quantum_info.operators import Pauli as qk_Pauli # type: ignore from qiskit.quantum_info.operators.symplectic.sparse_pauli_op import SparsePauliOp # type: ignore from qiskit_aer import Aer # type: ignore @@ -80,8 +80,8 @@ ) if TYPE_CHECKING: - from qiskit.providers.aer import AerJob # type: ignore - from qiskit.providers.aer.backends.aerbackend import AerBackend as QiskitAerBackend # type: ignore + from qiskit_aer import AerJob + from qiskit_aer.backends.aerbackend import AerBackend as QiskitAerBackend # type: ignore def _default_q_index(q: Qubit) -> int: @@ -455,7 +455,7 @@ class AerBackend(_AerBaseBackend): :param noise_model: Noise model to apply during simulation. Defaults to None. :type noise_model: Optional[NoiseModel], optional :param simulation_method: Simulation method, see - https://qiskit.org/documentation/stubs/qiskit.providers.aer.AerSimulator.html + https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.AerSimulator.html for available values. Defaults to "automatic". :type simulation_method: str :param crosstalk_params: Apply crosstalk noise simulation to the circuits before diff --git a/pytket/extensions/qiskit/backends/crosstalk_model.py b/pytket/extensions/qiskit/backends/crosstalk_model.py index 25af7a0e..0964cf74 100644 --- a/pytket/extensions/qiskit/backends/crosstalk_model.py +++ b/pytket/extensions/qiskit/backends/crosstalk_model.py @@ -16,7 +16,7 @@ from typing import List, Tuple, Union, Dict, Optional from dataclasses import dataclass -from qiskit.providers.aer.noise import NoiseModel # type: ignore +from qiskit_aer.noise import NoiseModel # type: ignore from qiskit_aer.noise.errors.standard_errors import amplitude_damping_error, phase_damping_error # type: ignore import numpy as np diff --git a/pytket/extensions/qiskit/backends/ibmq_emulator.py b/pytket/extensions/qiskit/backends/ibmq_emulator.py index 84d1da3d..e97b264c 100644 --- a/pytket/extensions/qiskit/backends/ibmq_emulator.py +++ b/pytket/extensions/qiskit/backends/ibmq_emulator.py @@ -27,7 +27,7 @@ ) from warnings import warn -from qiskit.providers.aer.noise.noise_model import NoiseModel # type: ignore +from qiskit_aer.noise.noise_model import NoiseModel # type: ignore from qiskit_ibm_runtime import ( # type: ignore QiskitRuntimeService, Session, diff --git a/pytket/extensions/qiskit/backends/ibmq_local_emulator.py b/pytket/extensions/qiskit/backends/ibmq_local_emulator.py index 88e0764f..ff326374 100644 --- a/pytket/extensions/qiskit/backends/ibmq_local_emulator.py +++ b/pytket/extensions/qiskit/backends/ibmq_local_emulator.py @@ -22,7 +22,7 @@ Union, ) -from qiskit.providers.aer.noise.noise_model import NoiseModel # type: ignore +from qiskit_aer.noise.noise_model import NoiseModel # type: ignore from qiskit_ibm_provider import IBMProvider # type: ignore diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index 7d04ba63..6aab6fd5 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -72,7 +72,6 @@ Unitary2qBox, Unitary3qBox, UnitType, - CustomGateDef, Bit, Qubit, QControlBox, @@ -339,7 +338,10 @@ def add_xs( if ((c >> i) & 1) == 0: self.tkc.X(self.qbmap[qargs[i]]) - def add_qiskit_data(self, data: "QuantumCircuitData") -> None: + def add_qiskit_data( + self, circuit: QuantumCircuit, data: Optional["QuantumCircuitData"] = None + ) -> None: + data = data or circuit.data for instr, qargs, cargs in data: condition_kwargs = {} if instr.condition is not None: @@ -350,9 +352,14 @@ def add_qiskit_data(self, data: "QuantumCircuitData") -> None: "condition_value": instr.condition[1], } elif type(instr.condition[0]) == Clbit: - cond_reg = self.cregmap[instr.condition[0].register] + # .find_bit() returns type: + # tuple[index, list[tuple[ClassicalRegister, index]]] + # We assume each bit belongs to exactly one register. + index = circuit.find_bit(instr.condition[0])[0] + register = circuit.find_bit(instr.condition[0])[1][0][0] + cond_reg = self.cregmap[register] condition_kwargs = { - "condition_bits": [cond_reg[instr.condition[0].index]], + "condition_bits": [cond_reg[index]], "condition_value": instr.condition[1], } else: @@ -501,7 +508,7 @@ def add_qiskit_data(self, data: "QuantumCircuitData") -> None: else [] ) builder = CircuitBuilder(qregs, cregs) - builder.add_qiskit_data(instr.definition) + builder.add_qiskit_data(circuit, instr.definition) subc = builder.circuit() subc.name = instr.name self.tkc.add_circbox(CircBox(subc), qubits + bits, **condition_kwargs) # type: ignore @@ -551,7 +558,7 @@ def qiskit_to_tk(qcirc: QuantumCircuit, preserve_param_uuid: bool = False) -> Ci name=circ_name, phase=param_to_tk(qcirc.global_phase), ) - builder.add_qiskit_data(qcirc.data) + builder.add_qiskit_data(qcirc) return builder.circuit() diff --git a/pytket/extensions/qiskit/tket_pass.py b/pytket/extensions/qiskit/tket_pass.py index 37fb6fdf..bdceb7df 100644 --- a/pytket/extensions/qiskit/tket_pass.py +++ b/pytket/extensions/qiskit/tket_pass.py @@ -17,7 +17,7 @@ from qiskit.providers import BackendV1 # type: ignore from qiskit.transpiler.basepasses import TransformationPass, BasePass as qBasePass # type: ignore from qiskit.converters import circuit_to_dag, dag_to_circuit # type: ignore -from qiskit.providers.aer.aerprovider import AerProvider # type: ignore # type: ignore +from qiskit_aer.aerprovider import AerProvider # type: ignore # type: ignore from qiskit_ibm_provider import IBMProvider # type: ignore diff --git a/setup.py b/setup.py index 623d324f..3014ccca 100644 --- a/setup.py +++ b/setup.py @@ -45,10 +45,10 @@ include_package_data=True, install_requires=[ "pytket ~= 1.25", - "qiskit ~= 0.46.0", + "qiskit ~= 1.0", "qiskit-algorithms ~= 0.2.1", "qiskit-ibm-runtime ~= 0.19.0", - "qiskit-aer ~= 0.13.0", + "qiskit-aer ~= 0.13.3", "qiskit-ibm-provider ~= 0.10.0", "numpy", ], diff --git a/tests/backend_test.py b/tests/backend_test.py index 4ac610cd..61a031f8 100644 --- a/tests/backend_test.py +++ b/tests/backend_test.py @@ -25,9 +25,9 @@ from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister # type: ignore from qiskit.circuit import Parameter # type: ignore -from qiskit.providers.aer.noise.noise_model import NoiseModel # type: ignore -from qiskit.providers.aer.noise import ReadoutError # type: ignore -from qiskit.providers.aer.noise.errors import depolarizing_error, pauli_error # type: ignore +from qiskit_aer.noise.noise_model import NoiseModel # type: ignore +from qiskit_aer.noise import ReadoutError # type: ignore +from qiskit_aer.noise.errors import depolarizing_error, pauli_error # type: ignore from qiskit_ibm_provider import IBMProvider # type: ignore from qiskit_aer import Aer # type: ignore @@ -1114,17 +1114,17 @@ def test_postprocess() -> None: @pytest.mark.flaky(reruns=3, reruns_delay=10) @pytest.mark.skipif(skip_remote_tests, reason=REASON) -def test_postprocess_emu(brisbane_emulator_backend: IBMQEmulatorBackend) -> None: - assert brisbane_emulator_backend.supports_contextual_optimisation +def test_postprocess_emu(ibmq_qasm_emulator_backend: IBMQEmulatorBackend) -> None: + assert ibmq_qasm_emulator_backend.supports_contextual_optimisation c = Circuit(2, 2) c.X(0).X(1).measure_all() - c = brisbane_emulator_backend.get_compiled_circuit(c) - h = brisbane_emulator_backend.process_circuit(c, n_shots=10, postprocess=True) + c = ibmq_qasm_emulator_backend.get_compiled_circuit(c) + h = ibmq_qasm_emulator_backend.process_circuit(c, n_shots=10, postprocess=True) ppcirc = Circuit.from_dict(json.loads(cast(str, h[3]))) ppcmds = ppcirc.get_commands() assert len(ppcmds) > 0 assert all(ppcmd.op.type == OpType.ClassicalTransform for ppcmd in ppcmds) - r = brisbane_emulator_backend.get_result(h) + r = ibmq_qasm_emulator_backend.get_result(h) counts = r.get_counts() assert sum(counts.values()) == 10 diff --git a/tests/qiskit_backend_test.py b/tests/qiskit_backend_test.py index 68e563f9..31b51b34 100644 --- a/tests/qiskit_backend_test.py +++ b/tests/qiskit_backend_test.py @@ -18,7 +18,7 @@ import numpy as np import pytest -from qiskit import QuantumCircuit, execute # type: ignore +from qiskit import QuantumCircuit # type: ignore from qiskit.primitives import BackendSampler # type: ignore from qiskit.providers import JobStatus # type: ignore from qiskit_algorithms import Grover, AmplificationProblem, AlgorithmError # type: ignore @@ -66,7 +66,7 @@ def test_samples() -> None: b = AerBackend() for comp in (None, b.default_compilation_pass()): tb = TketBackend(b, comp) - job = execute(qc, tb, shots=100, memory=True) + job = tb.run(qc, shots=100, memory=True) shots = job.result().get_memory() assert all(((r[0] == "1" and r[1] == r[2]) for r in shots)) counts = job.result().get_counts() @@ -78,12 +78,12 @@ def test_state() -> None: b = AerStateBackend() for comp in (None, b.default_compilation_pass()): tb = TketBackend(b, comp) - job = execute(qc, tb) + job = tb.run(qc) state = job.result().get_statevector() qb = Aer.get_backend("aer_simulator_statevector") qc1 = qc.copy() qc1.save_state() - job2 = execute(qc1, qb) + job2 = qb.run(qc1) state2 = job2.result().get_statevector() assert np.allclose(state, state2) @@ -93,12 +93,12 @@ def test_unitary() -> None: b = AerUnitaryBackend() for comp in (None, b.default_compilation_pass()): tb = TketBackend(b, comp) - job = execute(qc, tb) + job = tb.run(qc) u = job.result().get_unitary() qb = Aer.get_backend("aer_simulator_unitary") qc1 = qc.copy() qc1.save_unitary() - job2 = execute(qc1, qb) + job2 = qb.run(qc1) u2 = job2.result().get_unitary() assert np.allclose(u, u2) @@ -107,7 +107,7 @@ def test_cancel() -> None: b = AerBackend() tb = TketBackend(b) qc = circuit_gen() - job = execute(qc, tb, shots=1024) + job = tb.run(qc, shots=1024) job.cancel() assert job.status() in [JobStatus.CANCELLED, JobStatus.DONE] @@ -161,7 +161,7 @@ def test_architectures() -> None: # without architecture b = MockShotBackend(arch=arch) tb = TketBackend(b, b.default_compilation_pass()) - job = execute(qc, tb, shots=100, memory=True) + job = tb.run(qc, shots=100, memory=True) shots = job.result().get_memory() assert all(((r[0] == "1" and r[1] == r[2]) for r in shots)) counts = job.result().get_counts() diff --git a/tests/qiskit_convert_test.py b/tests/qiskit_convert_test.py index 16c941fa..81bf258a 100644 --- a/tests/qiskit_convert_test.py +++ b/tests/qiskit_convert_test.py @@ -22,7 +22,7 @@ QuantumCircuit, QuantumRegister, ClassicalRegister, - execute, + transpile, ) from qiskit.quantum_info import SparsePauliOp, Statevector, Operator # type: ignore from qiskit.transpiler import PassManager # type: ignore @@ -33,7 +33,7 @@ from qiskit_aer import Aer # type: ignore from qiskit.transpiler.passes import BasisTranslator # type: ignore from qiskit.circuit.equivalence_library import StandardEquivalenceLibrary # type: ignore -from qiskit.providers.fake_provider import FakeGuadalupe # type: ignore +from qiskit_ibm_runtime.fake_provider import FakeGuadalupe # type: ignore from qiskit.circuit.parameterexpression import ParameterExpression # type: ignore from pytket.circuit import ( @@ -188,10 +188,12 @@ def test_convert() -> None: backend = Aer.get_backend("aer_simulator_statevector") qc.save_state() - job = execute([qc], backend) + qc = transpile(qc, backend) + job = backend.run([qc]) state0 = job.result().get_statevector(qc) qc1.save_state() - job1 = execute([qc1], backend) + qc1 = transpile(qc1, backend) + job1 = backend.run([qc1]) state1 = job1.result().get_statevector(qc1) assert np.allclose(state0, state1, atol=1e-10) @@ -214,7 +216,7 @@ def test_symbolic() -> None: qc = tk_to_qiskit(tkc2) assert qc.name == tkc.name qc.save_state() - job = execute([qc], backend) + job = backend.run([qc]) state1 = job.result().get_statevector(qc) state0 = np.array( [ @@ -234,11 +236,13 @@ def test_symbolic() -> None: def test_measures() -> None: qc = get_test_circuit(True) backend = Aer.get_backend("aer_simulator") - job = execute([qc], backend, seed_simulator=7) + qc = transpile(qc, backend) + job = backend.run([qc], seed_simulator=7) counts0 = job.result().get_counts(qc) tkc = qiskit_to_tk(qc) qc = tk_to_qiskit(tkc) - job = execute([qc], backend, seed_simulator=7) + qc = transpile(qc, backend) + job = backend.run([qc], seed_simulator=7) counts1 = job.result().get_counts(qc) for result, count in counts1.items(): result_str = result.replace(" ", "") @@ -276,7 +280,7 @@ def test_Unitary1qBox() -> None: # Verify that unitary from simulator is correct back = Aer.get_backend("aer_simulator_unitary") qc.save_unitary() - job = execute(qc, back).result() + job = back.run(qc).result() a = job.get_unitary(qc) u1 = np.asarray(a) assert np.allclose(u1, u) @@ -292,7 +296,7 @@ def test_Unitary2qBox() -> None: # Verify that unitary from simulator is correct back = Aer.get_backend("aer_simulator_unitary") qc.save_unitary() - job = execute(qc, back).result() + job = back.run(qc).result() a = job.get_unitary(qc) u1 = permute_rows_cols_in_unitary(np.asarray(a), (1, 0)) # correct for endianness assert np.allclose(u1, u) @@ -319,7 +323,7 @@ def test_Unitary3qBox() -> None: # Verify that unitary from simulator is correct back = Aer.get_backend("aer_simulator_unitary") qc.save_unitary() - job = execute(qc, back).result() + job = back.run(qc).result() a = job.get_unitary(qc) u1 = permute_rows_cols_in_unitary( np.asarray(a), (2, 1, 0) @@ -351,13 +355,13 @@ def test_tketpass() -> None: tkpass.apply(tkc) qc1 = tk_to_qiskit(tkc) qc1.save_unitary() - res = execute(qc1, back).result() + res = back.run(qc1).result() u1 = res.get_unitary(qc1) qispass = TketPass(tkpass) pm = PassManager(qispass) qc2 = pm.run(qc) qc2.save_unitary() - res = execute(qc2, back).result() + res = back.run(qc2).result() u2 = res.get_unitary(qc2) assert np.allclose(u1, u2) @@ -517,7 +521,8 @@ def test_customgate() -> None: states = [] for qc in (qc1, qc2, correct_qc): qc.save_state() - job = execute([qc], backend) + qc = transpile(qc, backend) + job = backend.run([qc]) states.append(job.result().get_statevector(qc)) assert compare_statevectors(states[0], states[1]) @@ -538,7 +543,7 @@ def test_convert_result() -> None: simulator = Aer.get_backend("aer_simulator_statevector") qc1 = qc.copy() qc1.save_state() - qisk_result = execute(qc1, simulator, shots=10).result() + qisk_result = simulator.run(qc1, shots=10).result() tk_res = next(qiskit_result_to_backendresult(qisk_result)) @@ -552,7 +557,7 @@ def test_convert_result() -> None: qc.measure(qr2[1], cr2[0]) simulator = Aer.get_backend("aer_simulator") - qisk_result = execute(qc, simulator, shots=10).result() + qisk_result = simulator.run(qc, shots=10).result() tk_res = next(qiskit_result_to_backendresult(qisk_result)) one_bits = [Bit("z", 0), Bit("b", 0)] @@ -652,7 +657,8 @@ def assert_equivalence( assert isinstance(circ, QuantumCircuit) circ1 = circ.copy() circ1.save_unitary() - job = execute(circ1, backend) + circ1 = transpile(circ1, backend) + job = backend.run(circ1) unitaries.append(job.result().get_unitary(circ1)) for nn in range(1, len(circuits)): # Default np.allclose is very lax here, so use strict tolerances From e402c0f6d982f9586aedc927e28a31c0f43d0505 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:24:09 +0000 Subject: [PATCH 10/13] Improve docs on saving IBM credentials (#281) * remove note about deprecated syntax * remove another deprecation notice * add space to code snippet to improve readablity * update docs on rebases * move autosummary table for qiskit configuration * Clearly separate methods of saving credentials * reorder credential config methods * minor edits * add missing . * fix capital --------- Co-authored-by: cqc-melf <70640934+cqc-melf@users.noreply.github.com> --- docs/intro.txt | 50 +++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/docs/intro.txt b/docs/intro.txt index 51cb5add..b2c3a4f6 100644 --- a/docs/intro.txt +++ b/docs/intro.txt @@ -81,28 +81,11 @@ In this section we are assuming that you have set the following variables with t group = '' project = '' -.. note:: The documentation below is correct as of pytket-qiskit version 0.40.0 and newer. In the 0.40.0 release pytket-qiskit moved to using the `qiskit-ibm-provider `_. In pytket-qiskit versions 0.39.0 and older the parameters ``hub``, ``group`` and ``project`` were handled separately instead of a single ``instance`` string as in 0.40.0 and newer. +Method 1: Using :py:class:`IBMProvider` +--------------------------------------- -:: - - from pytket.extensions.qiskit import set_ibmq_config - - set_ibmq_config(ibmq_api_token=ibm_token) - -After saving your credentials you can access ``pytket-qiskit`` backend repeatedly without having to re-initialise your credentials. - -If you are a member of an IBM hub then you can add this information to ``set_ibmq_config`` as well. - -:: - - from pytket.extensions.qiskit import set_ibmq_config - - set_ibmq_config(ibmq_api_token=ibm_token, instance=f"{hub}/{group}/{project}") - -Alternatively you can use the following qiskit commands to save your credentials -locally without saving the token in pytket config: - -.. note:: If using pytket-qiskit 0.39.0 or older you will have to use the deprecated :py:meth:`IBMQ.save_account` instead of :py:meth:`IBMProvider.save_account` in the code below. +You can use the following qiskit commands to save your IBM credentials +to disk: :: @@ -122,10 +105,32 @@ To see which devices you can access you can use the ``available_devices`` method my_instance=f"{hub}/{group}/{project}" ibm_provider = IBMProvider(instance=my_instance) backend = IBMQBackend("ibmq_nairobi") # Initialise backend for an IBM device + backendinfo_list = backend.available_devices(instance=my_instance, provider=ibm_provider) print([backend.device_name for backend in backendinfo_list]) + +Method 2: Saving credentials in a local pytket config file +---------------------------------------------------------- +Alternatively, you can store your credentials in local pytket config using the :py:meth:`set_ibmq_config` method. + +:: + + from pytket.extensions.qiskit import set_ibmq_config + + set_ibmq_config(ibmq_api_token=ibm_token) + +After saving your credentials you can access ``pytket-qiskit`` backend repeatedly without having to re-initialise your credentials. + +If you are a member of an IBM hub then you can add this information to ``set_ibmq_config`` as well. + +:: + + from pytket.extensions.qiskit import set_ibmq_config + + set_ibmq_config(ibmq_api_token=ibm_token, instance=f"{hub}/{group}/{project}") + .. currentmodule:: pytket.extensions.qiskit.backends.config .. autosummary:: @@ -134,7 +139,6 @@ To see which devices you can access you can use the ``available_devices`` method QiskitConfig set_ibmq_config - Converting circuits between pytket and qiskit ============================================= @@ -192,7 +196,7 @@ Every :py:class:`Backend` in pytket has its own ``default_compilation_pass`` met - `RemoveRedundancies `_ * [1] If no value is specified then ``optimisation_level`` defaults to a value of 2. -* [2] self.rebase_pass is a rebase to the gateset supported by the backend, For IBM quantum devices that is {X, SX, Rz, CX}. +* [2] self.rebase_pass is a rebase to the gateset supported by the backend. For IBM quantum devices and emulators that is either {X, SX, Rz, CX} or {X, SX, Rz, ECR}. The more idealised Aer simulators have a much broader range of supported gates. * [3] Here :py:class:`CXMappingPass` maps program qubits to the architecture using a `NoiseAwarePlacement `_ From ba8e638ff6ff161d08c782f9bef1301fe26f5eeb Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Mon, 11 Mar 2024 17:06:22 +0000 Subject: [PATCH 11/13] Update qiskit-ibm-runtime to 0.21.0. (#284) --- docs/changelog.rst | 2 +- setup.py | 2 +- tests/backend_test.py | 28 +++++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index ba1bd6ff..904ffe5f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,7 +7,7 @@ Unreleased * Update pytket version requirement to 1.25. * Update qiskit version requirement to 1.0. * Update qiskit-ibm-provider version requirement to 0.10. -* Update qiskit-ibm-runtime version requirement to 0.19. +* Update qiskit-ibm-runtime version requirement to 0.21. * Add ``IBMQLocalEmulatorBackend`` for running local emulation of ``IBMQBackend`` using ``AerBackend`` with a noise model. diff --git a/setup.py b/setup.py index 3014ccca..52a24c32 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ "pytket ~= 1.25", "qiskit ~= 1.0", "qiskit-algorithms ~= 0.2.1", - "qiskit-ibm-runtime ~= 0.19.0", + "qiskit-ibm-runtime ~= 0.21.0", "qiskit-aer ~= 0.13.3", "qiskit-ibm-provider ~= 0.10.0", "numpy", diff --git a/tests/backend_test.py b/tests/backend_test.py index 61a031f8..55db9584 100644 --- a/tests/backend_test.py +++ b/tests/backend_test.py @@ -28,6 +28,7 @@ from qiskit_aer.noise.noise_model import NoiseModel # type: ignore from qiskit_aer.noise import ReadoutError # type: ignore from qiskit_aer.noise.errors import depolarizing_error, pauli_error # type: ignore +from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler # type: ignore from qiskit_ibm_provider import IBMProvider # type: ignore from qiskit_aer import Aer # type: ignore @@ -1129,7 +1130,9 @@ def test_postprocess_emu(ibmq_qasm_emulator_backend: IBMQEmulatorBackend) -> Non assert sum(counts.values()) == 10 -@pytest.mark.flaky(reruns=3, reruns_delay=10) +# https://github.com/CQCL/pytket-qiskit/issues/278 +# @pytest.mark.flaky(reruns=3, reruns_delay=10) +@pytest.mark.xfail(reason="Qiskit rejecting cx") @pytest.mark.skipif(skip_remote_tests, reason=REASON) def test_cloud_stabiliser(simulator_stabilizer_backend: IBMQBackend) -> None: c = Circuit(2, 2) @@ -1143,6 +1146,29 @@ def test_cloud_stabiliser(simulator_stabilizer_backend: IBMQBackend) -> None: assert not simulator_stabilizer_backend.valid_circuit(c) +# https://github.com/CQCL/pytket-qiskit/issues/278 +@pytest.mark.xfail(reason="Qiskit rejecting cx") +@pytest.mark.skipif(skip_remote_tests, reason=REASON) +def test_cloud_stabiliser_0() -> None: + num_qubits = 2 + qc = QuantumCircuit(num_qubits) + qc.h(0) + qc.sx(1) + qc.cx(0, 1) + qc.measure_all() + + _service = QiskitRuntimeService( + channel="ibm_quantum", + instance="ibm-q/open/main", + token=os.getenv("PYTKET_REMOTE_QISKIT_TOKEN"), + ) + _session = Session(service=_service, backend="simulator_stabilizer") + + sampler = Sampler(session=_session) + job = sampler.run(circuits=qc) + job.result() + + @pytest.mark.skipif(skip_remote_tests, reason=REASON) def test_available_devices(ibm_provider: IBMProvider) -> None: backend_info_list = IBMQBackend.available_devices(instance="ibm-q/open/main") From 2664bea128733606bebcc2314dc65e4f0b338740 Mon Sep 17 00:00:00 2001 From: Melf Date: Tue, 12 Mar 2024 10:40:18 +0000 Subject: [PATCH 12/13] update version and changelog --- _metadata.py | 2 +- docs/changelog.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/_metadata.py b/_metadata.py index a8d825eb..e50f0192 100644 --- a/_metadata.py +++ b/_metadata.py @@ -1,2 +1,2 @@ -__extension_version__ = "0.48.1rc1" +__extension_version__ = "0.49.0rc0" __extension_name__ = "pytket-qiskit" diff --git a/docs/changelog.rst b/docs/changelog.rst index 904ffe5f..b589d016 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,8 +1,8 @@ Changelog ~~~~~~~~~ -Unreleased ----------- +0.49.0rc0 (March 2024) +---------------------- * Update pytket version requirement to 1.25. * Update qiskit version requirement to 1.0. From 366ce0ec9cd6d24858c27af41d73807561afa126 Mon Sep 17 00:00:00 2001 From: Melf Date: Tue, 12 Mar 2024 11:04:47 +0000 Subject: [PATCH 13/13] update version and changelog, remove prerelease --- _metadata.py | 2 +- docs/changelog.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/_metadata.py b/_metadata.py index e50f0192..ef7f230e 100644 --- a/_metadata.py +++ b/_metadata.py @@ -1,2 +1,2 @@ -__extension_version__ = "0.49.0rc0" +__extension_version__ = "0.49.0" __extension_name__ = "pytket-qiskit" diff --git a/docs/changelog.rst b/docs/changelog.rst index b589d016..e1467a9f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,8 +1,8 @@ Changelog ~~~~~~~~~ -0.49.0rc0 (March 2024) ----------------------- +0.49.0 (March 2024) +------------------- * Update pytket version requirement to 1.25. * Update qiskit version requirement to 1.0.