From 0e5a2e8bb9a768a0b30b53cc6f36206b5a0607a0 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Mon, 12 Aug 2024 11:01:55 -0400 Subject: [PATCH] integration tests --- qiskit_ibm_runtime/utils/json.py | 23 +++--- .../utils/noise_learner_result.py | 10 +++ test/integration/test_noise_learner.py | 77 +++++++++++++------ 3 files changed, 72 insertions(+), 38 deletions(-) diff --git a/qiskit_ibm_runtime/utils/json.py b/qiskit_ibm_runtime/utils/json.py index 23b2cc7b9..49653f2e0 100644 --- a/qiskit_ibm_runtime/utils/json.py +++ b/qiskit_ibm_runtime/utils/json.py @@ -276,12 +276,6 @@ def default(self, obj: Any) -> Any: # pylint: disable=arguments-differ "fields": dict(obj.items()), } return {"__type__": "DataBin", "__value__": out_val} - if isinstance(obj, LayerError): - out_val = {"circuit": obj.circuit, "qubits": obj.qubits, "error": obj.error} - return {"__type__": "LayerError", "__value__": out_val} - if isinstance(obj, PauliLindbladError): - out_val = {"generators": obj.generators, "rates": obj.rates} - return {"__type__": "PauliLindbladError", "__value__": out_val} if isinstance(obj, EstimatorPub): return ( obj.circuit, @@ -357,14 +351,15 @@ def object_hook(self, obj: Any) -> Any: # to deserialize load qpy circuit and return first instruction object in that circuit. circuit = _decode_and_deserialize(obj_val, load)[0] return circuit.data[0][0] - if obj_type == "settings" and obj["__module__"].startswith( - "qiskit.quantum_info.operators" - ): - return _deserialize_from_settings( - mod_name=obj["__module__"], - class_name=obj["__class__"], - settings=_cast_strings_keys_to_int(obj_val), - ) + if obj_type == "settings": + deserialize = obj["__module__"].startswith("qiskit.quantum_info.operators") + deserialize = deserialize or obj["__class__"] in [PauliLindbladError, LayerError] + if deserialize is True: + return _deserialize_from_settings( + mod_name=obj["__module__"], + class_name=obj["__class__"], + settings=_cast_strings_keys_to_int(obj_val), + ) if obj_type == "Result": return Result.from_dict(obj_val) if obj_type == "spmatrix": diff --git a/qiskit_ibm_runtime/utils/noise_learner_result.py b/qiskit_ibm_runtime/utils/noise_learner_result.py index 18191e93f..3b2a7150f 100644 --- a/qiskit_ibm_runtime/utils/noise_learner_result.py +++ b/qiskit_ibm_runtime/utils/noise_learner_result.py @@ -90,6 +90,11 @@ def num_qubits(self) -> int: The number of qubits in this :class:`~.PauliLindbladError`. """ return self.generators.num_qubits + + @property + def settings(self): + """Return settings.""" + return {"generators": self.generators, "rates": self.rates} def __repr__(self) -> str: return f"PauliLindbladError(generators={self.generators}, rates={self.rates.tolist()})" @@ -170,6 +175,11 @@ def num_qubits(self) -> int: The number of qubits in this :class:`~.LayerError`. """ return len(self.qubits) + + @property + def settings(self): + """Return settings.""" + return {"circuit": self.circuit, "qubits": self.qubits, "error": self.error} def __repr__(self) -> str: ret = f"circuit={repr(self.circuit)}, qubits={self.qubits}, error={self.error})" diff --git a/test/integration/test_noise_learner.py b/test/integration/test_noise_learner.py index 6a92f2390..1995ab661 100644 --- a/test/integration/test_noise_learner.py +++ b/test/integration/test_noise_learner.py @@ -17,16 +17,18 @@ from qiskit.circuit import QuantumCircuit from qiskit.providers.jobstatus import JobStatus -from qiskit.compiler import transpile -from qiskit_ibm_runtime import RuntimeJob, Session +from qiskit_ibm_runtime import RuntimeJob, Session, EstimatorV2 from qiskit_ibm_runtime.noise_learner import NoiseLearner from qiskit_ibm_runtime.utils.noise_learner_result import PauliLindbladError, LayerError -from qiskit_ibm_runtime.options import NoiseLearnerOptions +from qiskit_ibm_runtime.options import NoiseLearnerOptions, EstimatorOptions from ..decorators import run_integration_test from ..ibm_test_case import IBMIntegrationTestCase +# TODO: remove +image = "prim-custom-img-noise-learner-phase2:56a77a61da2801fdf8b1e957157f9373ccf243f7" + class TestIntegrationNoiseLearner(IBMIntegrationTestCase): """Integration tests for NoiseLearner.""" @@ -39,12 +41,12 @@ def setUp(self) -> None: raise SkipTest("test_eagle not available in this environment") c1 = QuantumCircuit(2) - c1.cx(0, 1) + c1.ecr(0, 1) c2 = QuantumCircuit(3) - c2.cx(0, 1) - c2.cx(1, 2) - c2.cx(0, 1) + c2.ecr(0, 1) + c2.ecr(1, 2) + c2.ecr(0, 1) self.circuits = [c1, c2] @@ -64,11 +66,11 @@ def test_with_default_options(self, service): # pylint: disable=unused-argument options = NoiseLearnerOptions() learner = NoiseLearner(mode=backend, options=options) + learner.options.experimental = {"image": image} - circuits = transpile(self.circuits, backend=backend) - job = learner.run(circuits) + job = learner.run(self.circuits) - self._verify(job, self.default_input_options) + self._verify(job, self.default_input_options, 3) @run_integration_test def test_with_non_default_options(self, service): # pylint: disable=unused-argument @@ -80,13 +82,13 @@ def test_with_non_default_options(self, service): # pylint: disable=unused-argu options.layer_pair_depths = [0, 1] learner = NoiseLearner(mode=backend, options=options) - circuits = transpile(self.circuits, backend=backend) - job = learner.run(circuits) + job = learner.run(self.circuits) + learner.options.experimental = {"image": image} input_options = deepcopy(self.default_input_options) input_options["max_layers_to_learn"] = 1 input_options["layer_pair_depths"] = [0, 1] - self._verify(job, input_options) + self._verify(job, input_options, 1) @run_integration_test def test_in_session(self, service): @@ -94,29 +96,28 @@ def test_in_session(self, service): backend = self.backend options = NoiseLearnerOptions() - options.max_layers_to_learn = 1 options.layer_pair_depths = [0, 1] input_options = deepcopy(self.default_input_options) input_options["max_layers_to_learn"] = 1 input_options["layer_pair_depths"] = [0, 1] - circuits = transpile(self.circuits, backend=backend) - with Session(service, backend) as session: options.twirling_strategy = "all" learner1 = NoiseLearner(mode=session, options=options) - job1 = learner1.run(circuits) + learner1.options.experimental = {"image": image} + job1 = learner1.run(self.circuits) input_options["twirling_strategy"] = "all" - self._verify(job1, input_options) + self._verify(job1, input_options, 2) options.twirling_strategy = "active-circuit" learner2 = NoiseLearner(mode=session, options=options) - job2 = learner2.run(circuits) + learner2.options.experimental = {"image": image} + job2 = learner2.run(self.circuits) input_options["twirling_strategy"] = "active-circuit" - self._verify(job2, input_options) + self._verify(job2, input_options, 3) @run_integration_test def test_with_no_layers(self, service): # pylint: disable=unused-argument @@ -126,21 +127,49 @@ def test_with_no_layers(self, service): # pylint: disable=unused-argument options = NoiseLearnerOptions() options.max_layers_to_learn = 0 learner = NoiseLearner(mode=backend, options=options) + learner.options.experimental = {"image": image} - circuits = transpile(self.circuits, backend=backend) - job = learner.run(circuits) + job = learner.run(self.circuits) self.assertEqual(job.result().data, []) input_options = deepcopy(self.default_input_options) input_options["max_layers_to_learn"] = 0 - self._verify(job, input_options) + self._verify(job, input_options, 0) + + @run_integration_test + def test_learner_plus_estimator(self, service): # pylint: disable=unused-argument + """Test feeding noise learner data to estimator.""" + backend = self.backend - def _verify(self, job: RuntimeJob, expected_input_options: dict) -> None: + options = EstimatorOptions() + options.resilience.zne_mitigation = True + options.resilience.layer_noise_learning.layer_pair_depths = [0, 1] + options.twirling.strategy = "all" + # TODO: remove experimental options + options.experimental = {"image": image, "resilience": {"zne": {"amplifier": "pea"}}} + + with Session(service, backend) as session: + learner = NoiseLearner(mode=session, options=options) + nl_job = learner.run(self.circuits) + layer_noise_model = nl_job.result() + self.assertEqual(len(layer_noise_model), 2) + + estimator = EstimatorV2(mode=session, options=options) + estimator.options.resilience.pec_mitigation = True + estimator.options.resilience.layer_noise_model = layer_noise_model + + pubs = [(c, "Z" * c.num_qubits) for c in self.circuits] + e_job = estimator.run(pubs) + self.assertEqual(e_job.metadata["resilience"]["layer_noise_model"], layer_noise_model) + + def _verify(self, job: RuntimeJob, expected_input_options: dict, n_results: int) -> None: job.wait_for_final_state() self.assertEqual(job.status(), JobStatus.DONE, job.error_message()) result = job.result() + self.assertEqual(len(result), n_results) + for datum in result.data: circuit = datum.circuit qubits = datum.qubits