Skip to content

Commit

Permalink
Exposing layer_noise_model option (#1858)
Browse files Browse the repository at this point in the history
Co-authored-by: Christopher J. Wood <[email protected]>
  • Loading branch information
SamFerracin and chriseclectic authored Aug 15, 2024
1 parent 05bb9e1 commit 99d84c5
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 49 deletions.
1 change: 1 addition & 0 deletions docs/apidocs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ qiskit-ibm-runtime API reference

runtime_service
noise_learner
noise_learner_result
options
transpiler
qiskit_ibm_runtime.transpiler.passes.scheduling
Expand Down
4 changes: 4 additions & 0 deletions docs/apidocs/noise_learner_result.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.. automodule:: qiskit_ibm_runtime.utils.noise_learner_result
:no-members:
:no-inherited-members:
:no-special-members:
10 changes: 9 additions & 1 deletion qiskit_ibm_runtime/options/resilience_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@

"""Resilience options."""

from typing import Literal, Union
from typing import List, Literal, Union
from dataclasses import asdict

from pydantic import model_validator, Field

from ..utils.noise_learner_result import LayerError
from .utils import Unset, UnsetType, Dict, primitive_dataclass
from .measure_noise_learning_options import MeasureNoiseLearningOptions
from .zne_options import ZneOptions
Expand Down Expand Up @@ -65,6 +66,12 @@ class ResilienceOptionsV2:
layer_noise_learning: Layer noise learning options.
See :class:`LayerNoiseLearningOptions` for all options.
layer_noise_model: A list of :class:`LayerError` objects.
If set, all the mitigation strategies that require noise data (e.g., PEC and PEA)
skip the noise learning stage, and instead gather the required information from
``layer_noise_model``. Layers whose information is missing in ``layer_noise_model``
are treated as noiseless and their noise is not mitigated.
"""

measure_mitigation: Union[UnsetType, bool] = Unset
Expand All @@ -78,6 +85,7 @@ class ResilienceOptionsV2:
layer_noise_learning: Union[LayerNoiseLearningOptions, Dict] = Field(
default_factory=LayerNoiseLearningOptions
)
layer_noise_model: Union[UnsetType, List[LayerError]] = Unset

@model_validator(mode="after")
def _validate_options(self) -> "ResilienceOptionsV2":
Expand Down
12 changes: 11 additions & 1 deletion qiskit_ibm_runtime/utils/noise_learner_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""NoiseLearner result class"""
"""
==================================================================================
NoiseLearner result classes (:mod:`qiskit_ibm_runtime.utils.noise_learner_result`)
==================================================================================
.. autosummary::
:toctree: ../stubs/
PauliLindbladError
LayerError
"""

from __future__ import annotations

Expand Down
3 changes: 3 additions & 0 deletions release-notes/unreleased/1858.feat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
``ResilienceOptionsV2`` has a new field ``layer_noise_model``. When this field is set, all the
mitigation strategies that require noise data skip the noise learning stage, and instead gather
the required information from ``layer_noise_model``.
93 changes: 46 additions & 47 deletions test/integration/test_noise_learner.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@

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
Expand All @@ -39,12 +38,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]

Expand All @@ -65,10 +64,9 @@ def test_with_default_options(self, service): # pylint: disable=unused-argument
options = NoiseLearnerOptions()
learner = NoiseLearner(mode=backend, options=options)

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
Expand All @@ -80,43 +78,12 @@ 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)

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)

@run_integration_test
def test_in_session(self, service):
"""Test noise learner when used within a session."""
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)

input_options["twirling_strategy"] = "all"
self._verify(job1, input_options)

options.twirling_strategy = "active-circuit"
learner2 = NoiseLearner(mode=session, options=options)
job2 = learner2.run(circuits)

input_options["twirling_strategy"] = "active-circuit"
self._verify(job2, input_options)
self._verify(job, input_options, 1)

@run_integration_test
def test_with_no_layers(self, service): # pylint: disable=unused-argument
Expand All @@ -127,20 +94,52 @@ def test_with_no_layers(self, service): # pylint: disable=unused-argument
options.max_layers_to_learn = 0
learner = NoiseLearner(mode=backend, options=options)

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

options = EstimatorOptions()
options.resilience.zne_mitigation = True # pylint: disable=assigning-non-slot
options.resilience.zne.amplifier = "pea"
options.resilience.layer_noise_learning.layer_pair_depths = [0, 1]

def _verify(self, job: RuntimeJob, expected_input_options: dict) -> None:
pubs = [(c, "Z" * c.num_qubits) for c in self.circuits]

with Session(service, backend) as session:
learner = NoiseLearner(mode=session, options=options)
learner_job = learner.run(self.circuits)
noise_model = learner_job.result()
self.assertEqual(len(noise_model), 3)

estimator = EstimatorV2(mode=session, options=options)
estimator.options.resilience.layer_noise_model = noise_model

estimator_job = estimator.run(pubs)
result = estimator_job.result()

noise_model_metadata = result.metadata["resilience"]["layer_noise_model"]
for x, y in zip(noise_model, noise_model_metadata):
self.assertEqual(x.circuit, y.circuit)
self.assertEqual(x.qubits, y.qubits)
self.assertEqual(x.error.generators, y.error.generators)
self.assertEqual(x.error.rates.tolist(), y.error.rates.tolist())

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
Expand Down

0 comments on commit 99d84c5

Please sign in to comment.