From e6d48bc484b96252c883aa1f6b260987e934b4b4 Mon Sep 17 00:00:00 2001 From: merav-aharoni Date: Thu, 6 Jul 2023 17:46:04 +0300 Subject: [PATCH] Fixed bug in deepcopy of IBMBackend (#902) * Fixed infinite recursion when attempting to deepcopy an IBMBackend * Moved test to integration tests * Cleaned up deepcopy and relevant test * Improved release notes * Lint and Style * black * lint --------- Co-authored-by: Kevin Tian --- qiskit_ibm_runtime/ibm_backend.py | 24 +++++++++++++++++++ .../backend_deepcopy-965f6a19a6d17cb2.yaml | 6 +++++ test/integration/test_backend.py | 24 ++++++++++++++++++- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/backend_deepcopy-965f6a19a6d17cb2.yaml diff --git a/qiskit_ibm_runtime/ibm_backend.py b/qiskit_ibm_runtime/ibm_backend.py index edcb65d9e..eb0ff4840 100644 --- a/qiskit_ibm_runtime/ibm_backend.py +++ b/qiskit_ibm_runtime/ibm_backend.py @@ -16,6 +16,7 @@ from typing import Iterable, Union, Optional, Any, List from datetime import datetime as python_datetime +from copy import deepcopy from qiskit import QuantumCircuit from qiskit.qobj.utils import MeasLevel, MeasReturnType @@ -199,6 +200,11 @@ def __getattr__(self, name: str) -> Any: This magic method executes when user accesses an attribute that does not yet exist on IBMBackend class. """ + # Prevent recursion since these properties are accessed within __getattr__ + if name in ["_properties", "_defaults", "_target", "_configuration"]: + raise AttributeError( + "'{}' object has no attribute '{}'".format(self.__class__.__name__, name) + ) # Lazy load properties and pulse defaults and construct the target object. self._get_properties() self._get_defaults() @@ -529,6 +535,24 @@ def check_faulty(self, circuit: QuantumCircuit) -> None: f"{instr} operating on a faulty edge {qubit_indices}" ) + def __deepcopy__(self, _memo: dict = None) -> "IBMBackend": + cpy = IBMBackend( + configuration=deepcopy(self.configuration()), + service=self._service, + api_client=deepcopy(self._api_client), + instance=self._instance, + ) + cpy.name = self.name + cpy.description = self.description + cpy.online_date = self.online_date + cpy.backend_version = self.backend_version + cpy._coupling_map = self._coupling_map + cpy._defaults = deepcopy(self._defaults, _memo) + cpy._target = deepcopy(self._target, _memo) + cpy._max_circuits = self._max_circuits + cpy._options = deepcopy(self._options, _memo) + return cpy + class IBMRetiredBackend(IBMBackend): """Backend class interfacing with an IBM Quantum device no longer available.""" diff --git a/releasenotes/notes/backend_deepcopy-965f6a19a6d17cb2.yaml b/releasenotes/notes/backend_deepcopy-965f6a19a6d17cb2.yaml new file mode 100644 index 000000000..733d556f6 --- /dev/null +++ b/releasenotes/notes/backend_deepcopy-965f6a19a6d17cb2.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed infinite recursion when attempting to deepcopy an IBMBackend. Added + a method :meth:`qiskit_ibm_runtime.IBMBackend.deepcopy`. + diff --git a/test/integration/test_backend.py b/test/integration/test_backend.py index acc5ba121..129dbf332 100644 --- a/test/integration/test_backend.py +++ b/test/integration/test_backend.py @@ -13,7 +13,7 @@ """Tests for backend functions using real runtime service.""" from unittest import SkipTest - +import copy from qiskit.transpiler.target import Target from qiskit_ibm_runtime import QiskitRuntimeService @@ -163,3 +163,25 @@ def test_backend_run(self): with self.subTest(backend=backend.name): with self.assertRaises(RuntimeError): backend.run() + + def test_backend_deepcopy(self): + """Test that deepcopy on IBMBackend works correctly""" + backend = self.backend + with self.subTest(backend=backend.name): + backend_copy = copy.deepcopy(backend) + self.assertEqual(backend_copy.name, backend.name) + self.assertEqual( + backend_copy.configuration().basis_gates, + backend.configuration().basis_gates, + ) + self.assertEqual( + backend_copy.properties().last_update_date, + backend.properties().last_update_date, + ) + self.assertEqual(backend_copy._instance, backend._instance) + self.assertEqual(backend_copy._service._backends, backend._service._backends) + self.assertEqual(backend_copy._get_defaults(), backend._get_defaults()) + self.assertEqual( + backend_copy._api_client._session.base_url, + backend._api_client._session.base_url, + )