Skip to content

Commit 576e13f

Browse files
yaelbhSamFerracin
andauthored
[executor-preview] Testing and fixing the NLV3 decoder (#2505)
* nlv3 decoding - fixes and one test * done * model_dump * fixes * fixed converter test * do the initial decodin for the schema version without relying on a model * additional tests for the decoder * black * lint * mypy * 2026 * reverting a change * mypy * trying to fight a circular import * lint --------- Co-authored-by: SamFerracin <sam.ferracin@ibm.com>
1 parent f77504a commit 576e13f

File tree

5 files changed

+114
-13
lines changed

5 files changed

+114
-13
lines changed

qiskit_ibm_runtime/noise_learner_v3/converters/version_0_1.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ def noise_learner_v3_result_from_0_1(
8989
NoiseLearnerV3Result.from_generators(
9090
generators=[
9191
QubitSparsePauliList.from_sparse_list(
92-
[tuple(term) for term in sparse_list], datum["num_qubits"]
92+
[tuple(term) for term in sparse_list], datum.num_qubits
9393
)
94-
for sparse_list in datum["generators_sparse"]
94+
for sparse_list in datum.generators_sparse
9595
],
96-
rates=F64TensorModel(**datum["rates"]).to_numpy(),
97-
rates_std=F64TensorModel(**datum["rates_std"]).to_numpy(),
98-
metadata=datum["metadata"],
96+
rates=datum.rates.to_numpy(),
97+
rates_std=datum.rates_std.to_numpy(),
98+
metadata=datum.metadata.model_dump(),
9999
)
100-
for datum in model["data"]
100+
for datum in model.data
101101
]
102102
)

qiskit_ibm_runtime/noise_learner_v3/noise_learner_v3_decoders.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,42 @@
1414

1515
from __future__ import annotations
1616

17+
from typing import Any
1718
import logging
1819

20+
from ibm_quantum_schemas.models.noise_learner_v3.version_0_1.models import (
21+
NoiseLearnerV3ResultsModel as NoiseLearnerV3ResultsModel_0_1,
22+
)
23+
24+
from qiskit_ibm_runtime.noise_learner_v3.noise_learner_v3_result import ( # type: ignore[attr-defined]
25+
NoiseLearnerV3Results,
26+
)
27+
1928
# pylint: disable=unused-import,cyclic-import
2029
from ..utils.result_decoder import ResultDecoder
2130
from .converters.version_0_1 import noise_learner_v3_result_from_0_1
2231

2332
logger = logging.getLogger(__name__)
2433

25-
AVAILABLE_DECODERS = {"v0.1": noise_learner_v3_result_from_0_1}
34+
AVAILABLE_DECODERS = {"v0.1": (noise_learner_v3_result_from_0_1, NoiseLearnerV3ResultsModel_0_1)}
2635

2736

2837
class NoiseLearnerV3ResultDecoder(ResultDecoder):
2938
"""Decoder for noise learner V3."""
3039

3140
@classmethod
32-
def decode(cls, raw_result: str): # type: ignore[no-untyped-def]
41+
def decode(cls, raw_result: str) -> NoiseLearnerV3Results: # type: ignore[no-untyped-def]
3342
"""Decode raw json to result type."""
34-
decoded: dict[str, str] = super().decode(raw_result)
43+
decoded: dict[str, Any] = super().decode(raw_result)
3544

3645
try:
3746
schema_version = decoded["schema_version"]
3847
except KeyError:
3948
raise ValueError("Missing schema version.")
4049

4150
try:
42-
decoder = AVAILABLE_DECODERS[schema_version]
51+
decoder, model = AVAILABLE_DECODERS[schema_version]
4352
except KeyError:
4453
raise ValueError(f"No decoder found for schema version {schema_version}.")
4554

46-
return decoder(decoded)
55+
return decoder(model.model_validate_json(raw_result))

qiskit_ibm_runtime/utils/noise_learner_result_decoder.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,22 @@
1212

1313
"""NoiseLearner result decoder."""
1414

15+
from __future__ import annotations
16+
17+
from typing import TYPE_CHECKING
1518

1619
from .noise_learner_result import LayerError, NoiseLearnerResult, PauliLindbladError
1720
from .result_decoder import ResultDecoder
1821

22+
if TYPE_CHECKING:
23+
from qiskit_ibm_runtime.noise_learner_v3.noise_learner_v3_result import NoiseLearnerV3Results
24+
1925

2026
class NoiseLearnerResultDecoder(ResultDecoder):
2127
"""Class used to decode noise learner results"""
2228

2329
@classmethod
24-
def decode(cls, raw_result: str) -> NoiseLearnerResult:
30+
def decode(cls, raw_result: str) -> NoiseLearnerResult | NoiseLearnerV3Results:
2531
"""Convert the result to NoiseLearnerResult."""
2632
if "schema_version" in raw_result:
2733
# pylint: disable=import-outside-toplevel

test/unit/noise_learner_v3/test_converters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def test_converting_results(self):
8080
result1 = NoiseLearnerV3Result.from_generators(generators, rates, metadata=metadatum1)
8181
results = NoiseLearnerV3Results([result0, result1])
8282

83-
encoded = noise_learner_v3_result_to_0_1(results).model_dump()
83+
encoded = noise_learner_v3_result_to_0_1(results)
8484
decoded = noise_learner_v3_result_from_0_1(encoded)
8585
for datum_in, datum_out in zip(results.data, decoded.data):
8686
assert datum_in._generators == datum_out._generators
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# This code is part of Qiskit.
2+
#
3+
# (C) Copyright IBM 2026.
4+
#
5+
# This code is licensed under the Apache License, Version 2.0. You may
6+
# obtain a copy of this license in the LICENSE.txt file in the root directory
7+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
#
9+
# Any modifications or derivative works of this code must retain this
10+
# copyright notice, and modified files need to carry a notice indicating
11+
# that they have been altered from the originals.
12+
13+
"""Tests the decoder for the noise learner v3 model."""
14+
15+
import json
16+
import numpy as np
17+
from qiskit.quantum_info import QubitSparsePauliList
18+
19+
from qiskit_ibm_runtime.noise_learner_v3.converters.version_0_1 import (
20+
noise_learner_v3_result_to_0_1,
21+
)
22+
from qiskit_ibm_runtime.noise_learner_v3.noise_learner_v3_decoders import (
23+
NoiseLearnerV3ResultDecoder,
24+
)
25+
from qiskit_ibm_runtime.noise_learner_v3.noise_learner_v3_result import ( # type: ignore[attr-defined]
26+
NoiseLearnerV3Result,
27+
NoiseLearnerV3Results,
28+
)
29+
30+
from ...ibm_test_case import IBMTestCase
31+
32+
33+
class TestDecoder(IBMTestCase):
34+
"""Tests the decoder for the noise learner v3 model."""
35+
36+
def setUp(self):
37+
super().setUp()
38+
39+
generators = [
40+
QubitSparsePauliList.from_list(["IX", "XX"]),
41+
QubitSparsePauliList.from_list(["XI"]),
42+
]
43+
rates = [0.1, 0.2]
44+
rates_std = [0.01, 0.02]
45+
46+
metadatum0 = {
47+
"learning_protocol": "trex",
48+
"post_selection": {"fraction_kept": 1},
49+
}
50+
result0 = NoiseLearnerV3Result.from_generators(generators, rates, rates_std, metadatum0)
51+
52+
metadatum1 = {
53+
"learning_protocol": "lindblad",
54+
"post_selection": {"fraction_kept": {0: 1, 4: 1}},
55+
}
56+
result1 = NoiseLearnerV3Result.from_generators(generators, rates, metadata=metadatum1)
57+
self.results = NoiseLearnerV3Results([result0, result1])
58+
59+
self.encoded = noise_learner_v3_result_to_0_1(self.results).model_dump_json()
60+
61+
def test_decoder(self):
62+
"""Tests the decoder."""
63+
decoded = NoiseLearnerV3ResultDecoder.decode(self.encoded)
64+
for datum_in, datum_out in zip(self.results.data, decoded.data):
65+
assert datum_in._generators == datum_out._generators
66+
assert np.allclose(datum_in._rates, datum_out._rates)
67+
assert np.allclose(datum_in._rates_std, datum_out._rates_std)
68+
assert datum_in.metadata == datum_out.metadata
69+
70+
def test_no_schema_version(self):
71+
"""Verify that an error is raised if the encoded string
72+
does not specify any schema version."""
73+
encoded_as_json = json.loads(self.encoded)
74+
del encoded_as_json["schema_version"]
75+
encoded_as_str = json.dumps(encoded_as_json)
76+
with self.assertRaisesRegex(ValueError, "Missing schema version."):
77+
NoiseLearnerV3ResultDecoder.decode(encoded_as_str)
78+
79+
def test_unknown_schema_version(self):
80+
"""Verify that an error is raised if the schema version specified in the encoded string
81+
does not exist."""
82+
encoded_as_json = json.loads(self.encoded)
83+
encoded_as_json["schema_version"] = "unknown"
84+
encoded_as_str = json.dumps(encoded_as_json)
85+
with self.assertRaisesRegex(ValueError, "No decoder found for schema version unknown."):
86+
NoiseLearnerV3ResultDecoder.decode(encoded_as_str)

0 commit comments

Comments
 (0)