Skip to content

Commit

Permalink
Make NoiseLearnerResult JSON serializable (#1909)
Browse files Browse the repository at this point in the history
* initial commit

* linting

* release note

* Update noise learner tests (#1906)

* Update noise learner tests

* remove status check

---------

Co-authored-by: Kevin Tian <[email protected]>
  • Loading branch information
joshuasn and kt474 authored Sep 9, 2024
1 parent f12aa74 commit 7e79b7c
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 2 deletions.
7 changes: 7 additions & 0 deletions qiskit_ibm_runtime/utils/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
)
from qiskit_ibm_runtime.execution_span import SliceSpan, ExecutionSpans

from .noise_learner_result import NoiseLearnerResult

_TERRA_VERSION = tuple(
int(x) for x in re.match(r"\d+\.\d+\.\d", _terra_version_string).group(0).split(".")[:3]
)
Expand Down Expand Up @@ -322,6 +324,9 @@ def default(self, obj: Any) -> Any: # pylint: disable=arguments-differ
if isinstance(obj, PrimitiveResult):
out_val = {"pub_results": obj._pub_results, "metadata": obj.metadata}
return {"__type__": "PrimitiveResult", "__value__": out_val}
if isinstance(obj, NoiseLearnerResult):
out_val = {"data": obj.data, "metadata": obj.metadata}
return {"__type__": "NoiseLearnerResult", "__value__": out_val}
if isinstance(obj, SliceSpan):
out_val = {
"start": obj.start,
Expand Down Expand Up @@ -443,6 +448,8 @@ def object_hook(self, obj: Any) -> Any:
return PubResult(**obj_val)
if obj_type == "PrimitiveResult":
return PrimitiveResult(**obj_val)
if obj_type == "NoiseLearnerResult":
return NoiseLearnerResult(**obj_val)
if obj_type == "ExecutionSpan":
new_slices = {
int(idx): (tuple(shape), slice(*sl_args))
Expand Down
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/utils/noise_learner_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def __init__(self, data: Sequence[LayerError], metadata: dict[str, Any] | None =
pubs should be placed in their metadata fields. Keys are expected to be strings.
"""
self._data = list(data)
self._metadata = metadata.copy() or {}
self._metadata = {} if metadata is None else metadata.copy()

@property
def data(self) -> List[LayerError]:
Expand Down
1 change: 1 addition & 0 deletions release-notes/unreleased/1908.feat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added logic to encode and decode ``NoiseLearnerResult``.
48 changes: 47 additions & 1 deletion test/unit/test_data_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from qiskit.circuit.library import EfficientSU2, CXGate, PhaseGate, U2Gate

import qiskit.quantum_info as qi
from qiskit.quantum_info import SparsePauliOp, Pauli
from qiskit.quantum_info import SparsePauliOp, Pauli, PauliList
from qiskit.result import Result, Counts
from qiskit.primitives.containers.bindings_array import BindingsArray
from qiskit.primitives.containers.observables_array import ObservablesArray
Expand All @@ -41,6 +41,11 @@
)
from qiskit_aer.noise import NoiseModel
from qiskit_ibm_runtime.utils import RuntimeEncoder, RuntimeDecoder
from qiskit_ibm_runtime.utils.noise_learner_result import (
PauliLindbladError,
LayerError,
NoiseLearnerResult,
)
from qiskit_ibm_runtime.fake_provider import FakeNairobi
from qiskit_ibm_runtime.execution_span import SliceSpan, ExecutionSpans

Expand Down Expand Up @@ -333,6 +338,25 @@ def assert_primitive_results_equal(self, primitive_result1, primitive_result2):

self.assertEqual(primitive_result1.metadata, primitive_result2.metadata)

def assert_pauli_lindblad_error_equal(self, error1, error2):
"""Tests that two PauliLindbladError objects are equal"""
self.assertEqual(error1.generators, error2.generators)
self.assertEqual(error1.rates.tolist(), error2.rates.tolist())

def assert_layer_errors_equal(self, layer_error1, layer_error2):
"""Tests that two LayerError objects are equal"""
self.assertEqual(layer_error1.circuit, layer_error2.circuit)
self.assertEqual(layer_error1.qubits, layer_error2.qubits)
self.assert_pauli_lindblad_error_equal(layer_error1.error, layer_error2.error)

def assert_noise_learner_results_equal(self, result1, result2):
"""Tests that two NoiseLearnerResult objects are equal"""
self.assertEqual(len(result1), len(result2))
for layer_error1, layer_error2 in zip(result1, result2):
self.assert_layer_errors_equal(layer_error1, layer_error2)

self.assertEqual(result1.metadata, result2.metadata)

# Data generation methods

def make_test_data_bins(self):
Expand Down Expand Up @@ -435,6 +459,19 @@ def make_test_primitive_results(self):
primitive_results.append(result)
return primitive_results

def make_test_noise_learner_results(self):
"""Generates test data for NoiseLearnerResult test"""
noise_learner_results = []
circuit = QuantumCircuit(2)
circuit.cx(0, 1)
circuit.measure_all()
error = PauliLindbladError(PauliList(["XX", "ZZ"]), [0.1, 0.2])
layer_error = LayerError(circuit, [3, 5], error)

noise_learner_result = NoiseLearnerResult([layer_error])
noise_learner_results.append(noise_learner_result)
return noise_learner_results

# Tests
@data(
ObservablesArray([["X", "Y", "Z"], ["0", "1", "+"]]),
Expand Down Expand Up @@ -559,6 +596,15 @@ def test_primitive_result(self):
self.assertIsInstance(decoded, PrimitiveResult)
self.assert_primitive_results_equal(primitive_result, decoded)

def test_noise_learner_result(self):
"""Test encoding and decoding NoiseLearnerResult"""
for noise_learner_result in self.make_test_noise_learner_results():
payload = {"noise_learner_result": noise_learner_result}
encoded = json.dumps(payload, cls=RuntimeEncoder)
decoded = json.loads(encoded, cls=RuntimeDecoder)["noise_learner_result"]
self.assertIsInstance(decoded, NoiseLearnerResult)
self.assert_noise_learner_results_equal(noise_learner_result, decoded)

def test_unknown_settings(self):
"""Test settings not on whitelisted path."""
random_settings = {
Expand Down

0 comments on commit 7e79b7c

Please sign in to comment.