Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Noise Aware ZNE #96

Merged
merged 12 commits into from
Mar 16, 2024
4 changes: 4 additions & 0 deletions docs_src/zero_noise_extrapolation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ qermit.zero_noise_extrapolation

.. automethod:: qermit.zero_noise_extrapolation.zne.gen_ZNE_MitEx

.. automethod:: qermit.zero_noise_extrapolation.zne.gen_noise_scaled_mitex

.. autoclass:: qermit.zero_noise_extrapolation.zne.Folding
:members:

Expand All @@ -13,6 +15,8 @@ qermit.zero_noise_extrapolation

.. automethod:: qermit.zero_noise_extrapolation.zne.digital_folding_task_gen

.. automethod:: qermit.zero_noise_extrapolation.zne.merge_experiments_task_gen

.. automethod:: qermit.zero_noise_extrapolation.zne.extrapolation_task_gen

.. automethod:: qermit.zero_noise_extrapolation.zne.gen_initial_compilation_task
Expand Down
53 changes: 53 additions & 0 deletions qermit/noise_model/noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,43 @@ def plot(self):

return fig

@staticmethod
def _scale_error_rate(scaling_factor: float, error_rate: float) -> float:
"""Assuming error is distributed like Poisson point process, scale
error rate.

:param scaling_factor: The factor by which error rate should be
scaled. This is the factor by which the time the Poisson process
is acted for should be scaled.
:type scaling_factor: float
:param error_rate: Original error rate.
:type error_rate: float
:return: Scaled error rate.
:rtype: float
"""
if error_rate == 1:
return 1

return 1 - math.exp(scaling_factor * math.log(1 - error_rate))

def scale(self, scaling_factor: float) -> ErrorDistribution:
"""Generates new ErrorDistribution but with all error rates scaled
by the given factor. This assumes that errors are distributed like
Poisson point processes.

:param scaling_factor: Factor to scaled error rates by.
:type scaling_factor: float
:return: New ErrorDistribution with scaled error rates.
:rtype: ErrorDistribution
"""
return ErrorDistribution(
distribution={
error: self._scale_error_rate(scaling_factor, error_rate)
for error, error_rate in self.distribution.items()
},
rng=self.rng,
)


class LogicalErrorDistribution:
"""
Expand Down Expand Up @@ -304,6 +341,22 @@ def __init__(self, noise_model: Dict[OpType, ErrorDistribution]):

self.noise_model = noise_model

def scale(self, scaling_factor: float) -> NoiseModel:
"""Generate new error model where all error rates have been scaled by
the given scaling factor.

:param scaling_factor: Factor by which to scale the error rates.
:type scaling_factor: float
:return: New noise model with scaled error rates.
:rtype: NoiseModel
"""
return NoiseModel(
noise_model={
op_type: error_distribution.scale(scaling_factor=scaling_factor)
for op_type, error_distribution in self.noise_model.items()
}
)

def reset_rng(self, rng: Generator):
"""Reset randomness generator.

Expand Down
34 changes: 32 additions & 2 deletions qermit/noise_model/transpiler_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from pytket.backends.backendresult import BackendResult
from pytket.utils.outcomearray import OutcomeArray
import uuid
from pytket.passes import BasePass
from typing import Dict, List, Optional, Iterator
from pytket.passes import BasePass, CustomPass
from typing import Dict, List, Optional, Iterator, Sequence, Iterable
from pytket import Circuit, Bit


Expand Down Expand Up @@ -51,6 +51,20 @@ def __init__(
self.max_batch_size = max_batch_size
self.result_dict = result_dict

def default_compilation_pass(self, **kwargs) -> BasePass:

def transform(circuit: Circuit) -> Circuit:
return circuit

return CustomPass(transform=transform)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just do this:
return CustomPass(transform=lambda circuit: circuit)


def rebase_pass(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return type for function (BasePass?)


def transform(circuit: Circuit) -> Circuit:
return circuit

return CustomPass(transform=transform)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same again: return CustomPass(transform=lambda circuit: circuit)


def run_circuit(
self,
circuit: Circuit,
Expand All @@ -70,6 +84,17 @@ def run_circuit(
handle = self.process_circuit(circuit, n_shots, **kwargs)
return self.get_result(handle=handle)

def process_circuits(
self,
circuits: Sequence[Circuit],
n_shots: Sequence[int],
) -> List[uuid.UUID]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, do you use uuid.UUID instead of ResultHandle in this mock backend?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do, in part as I don't know how to create new unique ResultHandle objects. Do you happen to know how to do that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, figured it out :P


return [
self.process_circuit(circuit=circuit, n_shots=n)
for circuit, n in zip(circuits, n_shots)
]

def process_circuit(
self,
circuit: Circuit,
Expand Down Expand Up @@ -104,6 +129,11 @@ def process_circuit(

return handle

def get_results(self, handles: Iterable[uuid.UUID]) -> List[BackendResult]:
return [
self.get_result(handle) for handle in handles
]

def get_result(self, handle: uuid.UUID) -> BackendResult:
"""Retrieve result from backend.

Expand Down
4 changes: 4 additions & 0 deletions qermit/spam/spam_mitres.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ def gen_UnCorrelated_SPAM_MitRes(
"""
if backend.backend_info is None:
raise ValueError("Backend has no backend_info attribute.")
if backend.backend_info.architecture is None:
raise ValueError(
"Backend Architecture has no specified Nodes, please use a Backend with a specified Architecture."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd change the error message to:
BackendInfo stored by Backend has no defined Architecture, please use a Backend with a specified Architecture.

)
if len(backend.backend_info.architecture.nodes) == 0:
raise ValueError(
"Backend Architecture has no specified Nodes, please use a Backend with a specified Architecture."
Expand Down
Loading