diff --git a/_metadata.py b/_metadata.py index 1b9eecc2..1c493e6d 100644 --- a/_metadata.py +++ b/_metadata.py @@ -1,2 +1,2 @@ -__extension_version__ = "0.50.0" +__extension_version__ = "0.51.0" __extension_name__ = "pytket-qiskit" diff --git a/docs/api.rst b/docs/api.rst index 033acfb9..191152d5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -11,11 +11,6 @@ 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 acad5414..a01f69d5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,15 @@ Changelog ~~~~~~~~~ +0.51.0 (March 2024) +------------------- + +* Update qiskit-ibm-runtime version requirement to 0.22. +* remove all remote simulators +* rename ``IBMQLocalEmulatorBackend`` to ``IBMQEmulatorBackend`` +* ``IBMQEmulatorBackend`` will now run locally +* add support for contextual optimisation on local emulator + 0.50.0 (March 2024) ------------------- diff --git a/docs/intro.txt b/docs/intro.txt index b2c3a4f6..8fbe5f0f 100644 --- a/docs/intro.txt +++ b/docs/intro.txt @@ -29,7 +29,6 @@ Available IBM Backends IBMQBackend IBMQEmulatorBackend - IBMQLocalEmulatorBackend AerBackend AerStateBackend AerUnitaryBackend @@ -95,7 +94,7 @@ to disk: IBMProvider.save_account(token=ibm_token) QiskitRuntimeService.save_account(channel="ibm_quantum", token=ibm_token) -To see which devices you can access you can use the ``available_devices`` method on the :py:class:`IBMQBackend` or :py:class:`IBMQEmulatorBackend`. Note that it is possible to pass optional ``instance`` and ``provider`` arguments to this method. This allows you to see which devices are accessible through your IBM hub. +To see which devices you can access you can use the ``available_devices`` method on the :py:class:`IBMQBackend`. Note that it is possible to pass optional ``instance`` and ``provider`` arguments to this method. This allows you to see which devices are accessible through your IBM hub. :: @@ -160,7 +159,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, IBMQEmulatorBackend and IBMQLocalEmulatorBackend** +.. list-table:: **Default compilation pass for the IBMQBackend and IBMQEmulatorBackend** :widths: 25 25 25 :header-rows: 1 diff --git a/pytket/extensions/qiskit/__init__.py b/pytket/extensions/qiskit/__init__.py index 98aba5f4..0887ac7f 100644 --- a/pytket/extensions/qiskit/__init__.py +++ b/pytket/extensions/qiskit/__init__.py @@ -22,7 +22,6 @@ 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 0b6b8f88..a7a0c547 100644 --- a/pytket/extensions/qiskit/backends/__init__.py +++ b/pytket/extensions/qiskit/backends/__init__.py @@ -16,4 +16,3 @@ 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/aer.py b/pytket/extensions/qiskit/backends/aer.py index 3bcb57d1..bd2c05af 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -15,6 +15,7 @@ import itertools from collections import defaultdict from dataclasses import dataclass +import json from logging import warning from typing import ( Dict, @@ -66,6 +67,7 @@ ) from pytket.utils.operators import QubitPauliOperator from pytket.utils.results import KwargTypes +from pytket.utils import prepare_circuit from .ibm_utils import _STATUS_MAP, _batch_circuits from .._metadata import __extension_version__ @@ -133,7 +135,7 @@ def required_predicates(self) -> List[Predicate]: @property def _result_id_type(self) -> _ResultIdTuple: - return (str, int) + return (str, int, int, str) @property def backend_info(self) -> BackendInfo: @@ -243,6 +245,8 @@ def process_circuits( See :py:meth:`pytket.backends.Backend.process_circuits`. Supported kwargs: `seed`, `postprocess`. """ + postprocess = kwargs.get("postprocess", False) + circuits = list(circuits) n_shots_list = Backend._get_n_shots_as_list( n_shots, @@ -268,14 +272,22 @@ def process_circuits( replace_implicit_swaps = self.supports_state or self.supports_unitary for (n_shots, batch), indices in zip(circuit_batches, batch_order): - qcs = [] + qcs, ppcirc_strs, tkc_qubits_count = [], [], [] for tkc in batch: - qc = tk_to_qiskit(tkc, replace_implicit_swaps) + if postprocess: + c0, ppcirc = prepare_circuit(tkc, allow_classical=False) + ppcirc_rep = ppcirc.to_dict() + else: + c0, ppcirc_rep = tkc, None + + qc = tk_to_qiskit(c0, replace_implicit_swaps) if self.supports_state: qc.save_state() elif self.supports_unitary: qc.save_unitary() qcs.append(qc) + tkc_qubits_count.append(c0.n_qubits) + ppcirc_strs.append(json.dumps(ppcirc_rep)) if self._needs_transpile: qcs = transpile(qcs, self._qiskit_backend) @@ -291,7 +303,7 @@ def process_circuits( seed += 1 jobid = job.job_id() for i, ind in enumerate(indices): - handle = ResultHandle(jobid, i) + handle = ResultHandle(jobid, i, tkc_qubits_count[i], ppcirc_strs[i]) handle_list[ind] = handle self._cache[handle] = {"job": job} return cast(List[ResultHandle], handle_list) @@ -312,7 +324,7 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul try: return super().get_result(handle) except CircuitNotRunError: - jobid, _ = handle + jobid, _, qubit_n, ppc = handle try: job: "AerJob" = self._cache[handle]["job"] except KeyError: @@ -321,7 +333,9 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul res = job.result() backresults = qiskit_result_to_backendresult(res) for circ_index, backres in enumerate(backresults): - self._cache[ResultHandle(jobid, circ_index)]["result"] = backres + self._cache[ResultHandle(jobid, circ_index, qubit_n, ppc)][ + "result" + ] = backres return cast(BackendResult, self._cache[handle]["result"]) diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index 6a4523a0..1970559f 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -355,8 +355,7 @@ def default_compilation_pass( This is a an abstract method which is implemented in the backend itself, and so is tailored to the backend's requirements. - The default compilation passes for the :py:class:`IBMQBackend`, - :py:class:`IBMQEmulatorBackend` and the + The default compilation passes for the :py:class:`IBMQBackend` and the Aer simulators support an optional ``placement_options`` dictionary containing arguments to override the default settings in :py:class:`NoiseAwarePlacement`. diff --git a/pytket/extensions/qiskit/backends/ibmq_emulator.py b/pytket/extensions/qiskit/backends/ibmq_emulator.py index e97b264c..33c15975 100644 --- a/pytket/extensions/qiskit/backends/ibmq_emulator.py +++ b/pytket/extensions/qiskit/backends/ibmq_emulator.py @@ -12,12 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ast import literal_eval from collections import Counter -import itertools -import json from typing import ( - cast, Dict, Optional, List, @@ -25,39 +21,33 @@ Tuple, Union, ) -from warnings import warn from qiskit_aer.noise.noise_model import NoiseModel # type: ignore -from qiskit_ibm_runtime import ( # type: ignore - QiskitRuntimeService, - Session, - Options, - Sampler, - RuntimeJob, -) + from qiskit_ibm_provider import IBMProvider # type: ignore -from pytket.backends import Backend, CircuitNotRunError, ResultHandle, CircuitStatus +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 Bit, Circuit, OpType -from pytket.extensions.qiskit.qiskit_convert import tk_to_qiskit +from pytket.circuit import Circuit from pytket.passes import BasePass from pytket.predicates import Predicate -from pytket.utils import prepare_circuit -from pytket.utils.outcomearray import OutcomeArray from pytket.utils.results import KwargTypes +from .aer import AerBackend from .ibm import IBMQBackend -from .ibm_utils import _STATUS_MAP, _batch_circuits class IBMQEmulatorBackend(Backend): - """A Backend which uses the ibmq_qasm_simulator to emulate the behaviour of + """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 IBMQ account. + Requires a valid IBM account. """ _supports_shots = False @@ -81,14 +71,12 @@ def __init__( token=token, ) - self._service = QiskitRuntimeService( - channel="ibm_quantum", instance=instance, token=token - ) - self._session = Session(service=self._service, backend="ibmq_qasm_simulator") - # 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() @@ -112,8 +100,7 @@ def default_compilation_pass( @property def _result_id_type(self) -> _ResultIdTuple: - # job ID, index, stringified sequence of measured bits, post-processing circuit - return (str, int, str, str) + return self._aer._result_id_type def rebase_pass(self) -> BasePass: return self._ibmq.rebase_pass() @@ -129,123 +116,22 @@ def process_circuits( See :py:meth:`pytket.backends.Backend.process_circuits`. Supported kwargs: `seed`, `postprocess`. """ - circuits = list(circuits) - n_shots_list = Backend._get_n_shots_as_list( - n_shots, - len(circuits), - optional=False, - ) - handle_list: List[Optional[ResultHandle]] = [None] * len(circuits) - seed = kwargs.get("seed") - circuit_batches, batch_order = _batch_circuits(circuits, n_shots_list) - - batch_id = 0 # identify batches for debug purposes only - for (n_shots, batch), indices in zip(circuit_batches, batch_order): - for chunk in itertools.zip_longest( - *([iter(zip(batch, indices))] * self._ibmq._max_per_job) - ): - filtchunk = list(filter(lambda x: x is not None, chunk)) - batch_chunk, indices_chunk = zip(*filtchunk) - - if valid_check: - self._check_all_circuits(batch_chunk) - - postprocess = kwargs.get("postprocess", False) - - qcs, c_bit_strs, ppcirc_strs = [], [], [] - for tkc in batch_chunk: - if postprocess: - c0, ppcirc = prepare_circuit(tkc, allow_classical=False) - ppcirc_rep = ppcirc.to_dict() - else: - c0, ppcirc_rep = tkc, None - qcs.append(tk_to_qiskit(c0)) - measured_bits = sorted( - [cmd.args[1] for cmd in tkc if cmd.op.type == OpType.Measure] - ) - c_bit_strs.append( - repr([(b.reg_name, b.index) for b in measured_bits]) - ) - ppcirc_strs.append(json.dumps(ppcirc_rep)) - options = Options() - options.resilience_level = 0 - options.execution.shots = n_shots - options.simulator.noise_model = self._noise_model - options.seed_simulator = seed - if type(seed) is int: - seed += 1 - sampler = Sampler(session=self._session, options=options) - job = sampler.run(circuits=qcs) - job_id = job.job_id() - for i, ind in enumerate(indices_chunk): - handle_list[ind] = ResultHandle( - job_id, i, c_bit_strs[i], ppcirc_strs[i] - ) - batch_id += 1 - for handle in handle_list: - assert handle is not None - self._cache[handle] = dict() - return cast(List[ResultHandle], handle_list) - - def _retrieve_job(self, jobid: str) -> RuntimeJob: - return self._service.job(jobid) + 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: - jobid = cast(str, handle[0]) - job = self._retrieve_job(jobid) - try: - job.cancel() - except Exception as e: - warn(f"Unable to cancel job {jobid}: {e}") + self._aer.cancel(handle) def circuit_status(self, handle: ResultHandle) -> CircuitStatus: - self._check_handle_type(handle) - jobid = cast(str, handle[0]) - job = self._service.job(jobid) - ibmstatus = job.status() - return CircuitStatus(_STATUS_MAP[ibmstatus], ibmstatus.value) + 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: `timeout`, `wait`. + Supported kwargs: none. """ - self._check_handle_type(handle) - if handle in self._cache: - cached_result = self._cache[handle] - if "result" in cached_result: - return cast(BackendResult, cached_result["result"]) - jobid, index, c_bit_str, ppcirc_str = handle - c_bits = [Bit(reg_name, index) for reg_name, index in literal_eval(c_bit_str)] - ppcirc_rep = json.loads(ppcirc_str) - ppcirc = Circuit.from_dict(ppcirc_rep) if ppcirc_rep is not None else None - cache_key = (jobid, index) - if cache_key not in self._ibm_res_cache: - try: - job = self._retrieve_job(jobid) - except Exception as e: - warn(f"Unable to retrieve job {jobid}: {e}") - raise CircuitNotRunError(handle) - - res = job.result(timeout=kwargs.get("timeout", None)) - for circ_index, (r, d) in enumerate(zip(res.quasi_dists, res.metadata)): - self._ibm_res_cache[(jobid, circ_index)] = Counter( - {n: int(0.5 + d["shots"] * p) for n, p in r.items()} - ) - - counts = self._ibm_res_cache[cache_key] # Counter[int] - # Convert to `OutcomeArray`: - tket_counts: Counter = Counter() - for outcome_key, sample_count in counts.items(): - array = OutcomeArray.from_ints( - ints=[outcome_key], - width=len(c_bits), - big_endian=False, - ) - tket_counts[array] = sample_count - # Convert to `BackendResult`: - result = BackendResult(c_bits=c_bits, counts=tket_counts, ppcirc=ppcirc) - - self._cache[handle] = {"result": result} - return result + return self._aer.get_result(handle) diff --git a/pytket/extensions/qiskit/backends/ibmq_local_emulator.py b/pytket/extensions/qiskit/backends/ibmq_local_emulator.py deleted file mode 100644 index ff326374..00000000 --- a/pytket/extensions/qiskit/backends/ibmq_local_emulator.py +++ /dev/null @@ -1,136 +0,0 @@ -# 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_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/setup.py b/setup.py index abbbb889..e4fe4b6d 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ "pytket ~= 1.25", "qiskit ~= 1.0", "qiskit-algorithms ~= 0.3.0", - "qiskit-ibm-runtime ~= 0.21.0", + "qiskit-ibm-runtime ~= 0.22.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 55db9584..88514d5f 100644 --- a/tests/backend_test.py +++ b/tests/backend_test.py @@ -64,7 +64,6 @@ AerStateBackend, AerUnitaryBackend, IBMQEmulatorBackend, - IBMQLocalEmulatorBackend, ) from pytket.extensions.qiskit import ( qiskit_to_tk, @@ -472,9 +471,8 @@ def test_nshots_batching(brisbane_backend: IBMQBackend) -> None: @pytest.mark.skipif(skip_remote_tests, reason=REASON) def test_nshots( brisbane_emulator_backend: IBMQEmulatorBackend, - brisbane_local_emulator_backend: IBMQLocalEmulatorBackend, ) -> None: - for b in [AerBackend(), brisbane_emulator_backend, brisbane_local_emulator_backend]: + for b in [AerBackend(), brisbane_emulator_backend]: circuit = Circuit(1).X(0) circuit.measure_all() n_shots = [1, 2, 3] @@ -714,7 +712,7 @@ def test_aer_result_handle() -> None: handles = b.process_circuits([c, c.copy()], n_shots=2) - ids, indices = zip(*(han for han in handles)) + ids, indices, _, _ = zip(*(han for han in handles)) assert all(isinstance(idval, str) for idval in ids) assert indices == (0, 1) @@ -727,7 +725,7 @@ def test_aer_result_handle() -> None: errorinfo.value ) - wronghandle = ResultHandle("asdf", 3) + wronghandle = ResultHandle("asdf", 3, 0, "jsonstr") with pytest.raises(CircuitNotRunError) as errorinfoCirc: _ = b.get_result(wronghandle) @@ -837,40 +835,43 @@ def test_operator_expectation_value() -> None: @pytest.mark.skipif(skip_remote_tests, reason=REASON) 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()) + 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()) @given( @@ -1115,60 +1116,21 @@ def test_postprocess() -> None: @pytest.mark.flaky(reruns=3, reruns_delay=10) @pytest.mark.skipif(skip_remote_tests, reason=REASON) -def test_postprocess_emu(ibmq_qasm_emulator_backend: IBMQEmulatorBackend) -> None: - assert ibmq_qasm_emulator_backend.supports_contextual_optimisation +def test_postprocess_emu(brisbane_emulator_backend: IBMQEmulatorBackend) -> None: + assert brisbane_emulator_backend.supports_contextual_optimisation c = Circuit(2, 2) c.X(0).X(1).measure_all() - c = ibmq_qasm_emulator_backend.get_compiled_circuit(c) - h = ibmq_qasm_emulator_backend.process_circuit(c, n_shots=10, postprocess=True) + c = brisbane_emulator_backend.get_compiled_circuit(c) + h = brisbane_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 = ibmq_qasm_emulator_backend.get_result(h) + r = brisbane_emulator_backend.get_result(h) counts = r.get_counts() assert sum(counts.values()) == 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) - c.H(0).SX(1).CX(0, 1).measure_all() - c = simulator_stabilizer_backend.get_compiled_circuit(c, 0) - h = simulator_stabilizer_backend.process_circuit(c, n_shots=10) - assert sum(simulator_stabilizer_backend.get_result(h).get_counts().values()) == 10 - - c = Circuit(2, 2) - c.H(0).SX(1).Rz(0.1, 0).CX(0, 1).measure_all() - 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") @@ -1194,14 +1156,12 @@ 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 - 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 + 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 def test_backendinfo_serialization2() -> None: @@ -1251,22 +1211,20 @@ def test_sim_qubit_order() -> None: @pytest.mark.skipif(skip_remote_tests, reason=REASON) def test_required_predicates( brisbane_emulator_backend: IBMQEmulatorBackend, - brisbane_local_emulator_backend: IBMQLocalEmulatorBackend, ) -> None: # https://github.com/CQCL/pytket-qiskit/issues/93 - 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) - ) + 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) + ) @pytest.mark.flaky(reruns=3, reruns_delay=10) @@ -1472,10 +1430,9 @@ def test_barriers_in_aer_simulators() -> None: @pytest.mark.skipif(skip_remote_tests, reason=REASON) def test_ibmq_local_emulator( - brisbane_local_emulator_backend: IBMQLocalEmulatorBackend, + brisbane_emulator_backend: IBMQEmulatorBackend, ) -> None: - b = brisbane_local_emulator_backend - assert not b.supports_contextual_optimisation + b = brisbane_emulator_backend circ = Circuit(2).H(0).CX(0, 1).measure_all() circ1 = b.get_compiled_circuit(circ) h = b.process_circuit(circ1, n_shots=100) diff --git a/tests/conftest.py b/tests/conftest.py index 25255efe..ffa18aa5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,7 +19,6 @@ from pytket.extensions.qiskit import ( IBMQBackend, IBMQEmulatorBackend, - IBMQLocalEmulatorBackend, ) @@ -58,16 +57,6 @@ def qasm_simulator_backend() -> IBMQBackend: ) -@pytest.fixture(scope="module") -def simulator_stabilizer_backend() -> IBMQBackend: - return IBMQBackend( - "simulator_stabilizer", - instance="ibm-q/open/main", - monitor=False, - token=os.getenv("PYTKET_REMOTE_QISKIT_TOKEN"), - ) - - @pytest.fixture(scope="module") def brisbane_emulator_backend() -> IBMQEmulatorBackend: return IBMQEmulatorBackend( @@ -77,33 +66,6 @@ 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( - "ibm_brisbane", - instance="ibm-q/open/main", - token=os.getenv("PYTKET_REMOTE_QISKIT_TOKEN"), - ) - - -@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") diff --git a/tests/qiskit_backend_test.py b/tests/qiskit_backend_test.py index 31b51b34..7c5e64f4 100644 --- a/tests/qiskit_backend_test.py +++ b/tests/qiskit_backend_test.py @@ -115,14 +115,14 @@ def test_cancel() -> None: # 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(ibmq_qasm_emulator_backend: IBMQEmulatorBackend) -> None: +def test_qiskit_counts(brisbane_emulator_backend: IBMQEmulatorBackend) -> None: num_qubits = 2 qc = QuantumCircuit(num_qubits) qc.h(0) qc.cx(0, 1) qc.measure_all() - s = BackendSampler(TketBackend(ibmq_qasm_emulator_backend)) + s = BackendSampler(TketBackend(brisbane_emulator_backend)) job = s.run([qc], shots=10) res = job.result()