diff --git a/qiskit_ibm_runtime/fake_provider/fake_backend.py b/qiskit_ibm_runtime/fake_provider/fake_backend.py index 39d53e42f..9851b082f 100644 --- a/qiskit_ibm_runtime/fake_provider/fake_backend.py +++ b/qiskit_ibm_runtime/fake_provider/fake_backend.py @@ -21,10 +21,17 @@ import os import re -from typing import List, Iterable - -from qiskit import circuit -from qiskit.providers.models import BackendProperties, BackendConfiguration, PulseDefaults +from typing import List, Iterable, Union + +from qiskit import circuit, QuantumCircuit +from qiskit.providers.models import ( + BackendProperties, + BackendConfiguration, + PulseDefaults, + BackendStatus, + QasmBackendConfiguration, + PulseBackendConfiguration, +) from qiskit.providers import BackendV2, BackendV1 from qiskit import pulse from qiskit.exceptions import QiskitError @@ -169,6 +176,92 @@ def _load_json(self, filename: str) -> dict: the_json = json.load(f_json) return the_json + def status(self) -> BackendStatus: + """Return the backend status. + + Returns: + The status of the backend. + + """ + + api_status = { + "backend_name": self.name, + "backend_version": "", + "status_msg": "active", + "operational": True, + "pending_jobs": 0, + } + + return BackendStatus.from_dict(api_status) + + def properties(self, refresh: bool = False) -> BackendProperties: + """Return the backend properties + + Args: + refresh: If ``True``, re-retrieve the backend properties + from the local file. + + Returns: + The backend properties. + """ + if refresh or (self._props_dict is None): + self._set_props_dict_from_json() + return BackendProperties.from_dict(self._props_dict) + + def defaults(self, refresh: bool = False) -> PulseDefaults: + """Return the pulse defaults for the backend + + Args: + refresh: If ``True``, re-retrieve the backend defaults from the + local file. + + Returns: + The backend pulse defaults or ``None`` if the backend does not support pulse. + """ + if refresh or self._defs_dict is None: + self._set_defs_dict_from_json() + if self._defs_dict: + return PulseDefaults.from_dict(self._defs_dict) # type: ignore[unreachable] + return None + + def configuration(self) -> Union[QasmBackendConfiguration, PulseBackendConfiguration]: + """Return the backend configuration.""" + return BackendConfiguration.from_dict(self._conf_dict) + + def check_faulty(self, circuit: QuantumCircuit) -> None: # pylint: disable=redefined-outer-name + """Check if the input circuit uses faulty qubits or edges. + + Args: + circuit: Circuit to check. + + Raises: + ValueError: If an instruction operating on a faulty qubit or edge is found. + """ + if not self.properties(): + return + + faulty_qubits = self.properties().faulty_qubits() + faulty_gates = self.properties().faulty_gates() + faulty_edges = [tuple(gate.qubits) for gate in faulty_gates if len(gate.qubits) > 1] + + for instr in circuit.data: + if instr.operation.name == "barrier": + continue + qubit_indices = tuple(circuit.find_bit(x).index for x in instr.qubits) + + for circ_qubit in qubit_indices: + if circ_qubit in faulty_qubits: + raise ValueError( + f"Circuit {circuit.name} contains instruction " + f"{instr} operating on a faulty qubit {circ_qubit}." + ) + + if len(qubit_indices) == 2 and qubit_indices in faulty_edges: + raise ValueError( + f"Circuit {circuit.name} contains instruction " + f"{instr} operating on a faulty edge {qubit_indices}" + ) + @property def target(self) -> Target: """A :class:`qiskit.transpiler.Target` object for the backend. diff --git a/release-notes/unreleased/1765.feat.rst b/release-notes/unreleased/1765.feat.rst new file mode 100644 index 000000000..23f517dd6 --- /dev/null +++ b/release-notes/unreleased/1765.feat.rst @@ -0,0 +1,2 @@ +The methods ``properties``, ``defaults``, ``configuration``, +and ``check_faulty`` have been added to :class:`FakeBackendV2`. \ No newline at end of file diff --git a/test/integration/test_fake_backends.py b/test/integration/test_fake_backends.py index e55f6183f..a4da9c1fb 100644 --- a/test/integration/test_fake_backends.py +++ b/test/integration/test_fake_backends.py @@ -99,7 +99,7 @@ def test_circuit_on_fake_backend(self, backend, optimization_level): max_count = max(counts.items(), key=operator.itemgetter(1))[0] self.assertEqual(max_count, "11") - @data(*FAKE_PROVIDER.backends()) + @data(*FAKE_PROVIDER.backends(), *FAKE_PROVIDER_FOR_BACKEND_V2.backends()) def test_to_dict_properties(self, backend): properties = backend.properties() if properties: @@ -118,7 +118,7 @@ def test_backend_v2_dtm(self, backend): if backend.dtm: self.assertLess(backend.dtm, 1e-6) - @data(*FAKE_PROVIDER.backends()) + @data(*FAKE_PROVIDER.backends(), *FAKE_PROVIDER_FOR_BACKEND_V2.backends()) def test_to_dict_configuration(self, backend): configuration = backend.configuration() if configuration.open_pulse: @@ -140,19 +140,20 @@ def test_to_dict_configuration(self, backend): self.assertIsInstance(configuration.to_dict(), dict) - @data(*FAKE_PROVIDER.backends()) + @data(*FAKE_PROVIDER.backends(), *FAKE_PROVIDER_FOR_BACKEND_V2.backends()) def test_defaults_to_dict(self, backend): if hasattr(backend, "defaults"): defaults = backend.defaults() - self.assertIsInstance(defaults.to_dict(), dict) + if defaults: + self.assertIsInstance(defaults.to_dict(), dict) - for i in defaults.qubit_freq_est: - self.assertGreater(i, 1e6) - self.assertGreater(i, 1e6) + for i in defaults.qubit_freq_est: + self.assertGreater(i, 1e6) + self.assertGreater(i, 1e6) - for i in defaults.meas_freq_est: - self.assertGreater(i, 1e6) - self.assertGreater(i, 1e6) + for i in defaults.meas_freq_est: + self.assertGreater(i, 1e6) + self.assertGreater(i, 1e6) else: self.skipTest("Backend %s does not have defaults" % backend)