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

Add leakage noise in NoiseModel #714

Merged
merged 13 commits into from
Jul 29, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@
},
"temperature": {
"type": "number"
},
"with_leakage": {
"type": "boolean"
HGSilveri marked this conversation as resolved.
Show resolved Hide resolved
}
},
"required": [
Expand All @@ -125,13 +128,13 @@
},
"NoiseType": {
"enum": [
"leakage",
HGSilveri marked this conversation as resolved.
Show resolved Hide resolved
"doppler",
"amplitude",
"SPAM",
"relaxation",
"dephasing",
"depolarizing",
"leakage",
"eff_noise"
],
"type": "string"
Expand Down
67 changes: 60 additions & 7 deletions pulser-core/pulser/noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
__all__ = ["NoiseModel"]

NoiseTypes = Literal[
"leakage",
"doppler",
"amplitude",
"SPAM",
Expand All @@ -40,6 +41,7 @@
]

_NOISE_TYPE_PARAMS: dict[NoiseTypes, tuple[str, ...]] = {
"leakage": ("with_leakage",),
"doppler": ("temperature",),
"amplitude": ("laser_waist", "amp_sigma"),
"SPAM": ("p_false_pos", "p_false_neg", "state_prep_error"),
Expand Down Expand Up @@ -76,6 +78,8 @@
"amp_sigma",
}

_BOOLEAN = {"with_leakage"}

_LEGACY_DEFAULTS = {
"runs": 15,
"samples_per_run": 5,
Expand All @@ -98,6 +102,11 @@ class NoiseModel:

Supported noise types:

- "leakage": Adds an error state 'x' to the computational
basis, that can interact with the other states via an
effective noise channel. Must be defined with an effective
noise channel, but is incompatible with dephasing and
depolarizing noise channels.
- **relaxation**: Noise due to a decay from the Rydberg to
the ground state (parametrized by ``relaxation_rate``),
commonly characterized experimentally by the T1 time.
Expand Down Expand Up @@ -156,6 +165,8 @@ class NoiseModel:
eff_noise_rates: The rate associated to each effective noise operator
(in 1/µs).
eff_noise_opers: The operators for the effective noise model.
with_leakage: Whether or not to include an error state in the
computations (default to False).
"""

noise_types: tuple[NoiseTypes, ...]
Expand All @@ -173,6 +184,7 @@ class NoiseModel:
depolarizing_rate: float
eff_noise_rates: tuple[float, ...]
eff_noise_opers: tuple[ArrayLike, ...]
with_leakage: bool

def __init__(
self,
Expand All @@ -191,6 +203,7 @@ def __init__(
depolarizing_rate: float | None = None,
eff_noise_rates: tuple[float, ...] = (),
eff_noise_opers: tuple[ArrayLike, ...] = (),
with_leakage: bool = False,
) -> None:
"""Initializes a noise model."""

Expand All @@ -214,8 +227,8 @@ def to_tuple(obj: tuple) -> tuple:
depolarizing_rate=depolarizing_rate,
eff_noise_rates=to_tuple(eff_noise_rates),
eff_noise_opers=to_tuple(eff_noise_opers),
with_leakage=with_leakage,
)

if noise_types is not None:
with warnings.catch_warnings():
warnings.simplefilter("always")
Expand All @@ -231,21 +244,26 @@ def to_tuple(obj: tuple) -> tuple:
)
self._check_noise_types(noise_types)
for nt_ in noise_types:
if nt_ == "leakage":
raise ValueError(
"'leakage' cannot be explicitely defined in the noise"
" types. Set 'with_leakage' to True instead."
)
for p_ in _NOISE_TYPE_PARAMS[nt_]:
# Replace undefined relevant params by the legacy default
if param_vals[p_] is None:
param_vals[p_] = _LEGACY_DEFAULTS[p_]

true_noise_types: set[NoiseTypes] = {
_PARAM_TO_NOISE_TYPE[p_]
for p_ in param_vals
if param_vals[p_] and p_ in _PARAM_TO_NOISE_TYPE
}

self._check_leakage_noise(true_noise_types)
self._check_eff_noise(
cast(tuple, param_vals["eff_noise_rates"]),
cast(tuple, param_vals["eff_noise_opers"]),
"eff_noise" in (noise_types or true_noise_types),
with_leakage=cast(bool, param_vals["with_leakage"]),
)

# Get rid of unnecessary None's
Expand Down Expand Up @@ -277,7 +295,7 @@ def to_tuple(obj: tuple) -> tuple:
relevant_param_vals = {
p: param_vals[p]
for p in param_vals
if param_vals[p] is not None or (p in relevant_params)
if param_vals[p] is not None or p in relevant_params
}
self._validate_parameters(relevant_param_vals)

Expand Down Expand Up @@ -314,6 +332,24 @@ def _find_relevant_params(
relevant_params.discard("laser_waist")
return relevant_params

@staticmethod
def _check_leakage_noise(noise_types: Collection[NoiseTypes]) -> None:
# Can't define "dephasing", "depolarizing" with "leakage"
if "leakage" not in noise_types:
return
# TODO: Implement the depolarizing and dephasing operations with
# projectors will stop raising an error.
if "dephasing" in noise_types or "depolarizing" in noise_types:
raise NotImplementedError(
"Dephasing and depolarizing channels can't be defined "
"with a leakage noise."
)
if "eff_noise" not in noise_types:
raise ValueError(
"At least one effective noise operator must be defined to"
" simulate leakage."
)

@staticmethod
def _check_noise_types(noise_types: Sequence[NoiseTypes]) -> None:
for noise_type in noise_types:
Expand All @@ -329,6 +365,7 @@ def _check_eff_noise(
eff_noise_rates: Sequence[float],
eff_noise_opers: Sequence[ArrayLike],
check_contents: bool,
with_leakage: bool,
) -> None:
if len(eff_noise_opers) != len(eff_noise_rates):
raise ValueError(
Expand All @@ -355,6 +392,11 @@ def _check_eff_noise(
raise ValueError("The provided rates must be greater than 0.")

# Check the validity of operators
min_shape = 2 if not with_leakage else 3
possible_shapes = [
(min_shape, min_shape),
(min_shape + 1, min_shape + 1),
]
for op in eff_noise_opers:
# type checking
try:
Expand All @@ -366,9 +408,17 @@ def _check_eff_noise(
if operator.ndim != 2:
raise ValueError(f"Operator '{op!r}' is not a 2D array.")

if operator.shape != (2, 2):
raise NotImplementedError(
f"Operator's shape must be (2,2) not {operator.shape}."
# TODO: Modify when effective noise can be provided for qutrit
if operator.shape != possible_shapes[0]:
err_type = (
NotImplementedError
if operator.shape in possible_shapes
else ValueError
)
raise err_type(
f"With{'' if with_leakage else 'out'} leakage, operator's "
f"shape must be {possible_shapes[0]}, "
f"not {operator.shape}."
)

@staticmethod
Expand All @@ -388,6 +438,9 @@ def _validate_parameters(param_vals: dict[str, Any]) -> None:
"greater than or equal to zero and smaller than "
"or equal to one"
)
elif param in _BOOLEAN:
is_valid = isinstance(value, bool)
comp = "a boolean"
HGSilveri marked this conversation as resolved.
Show resolved Hide resolved
if not is_valid:
raise ValueError(f"'{param}' must be {comp}, not {value}.")

Expand Down
10 changes: 10 additions & 0 deletions pulser-simulation/pulser_simulation/simconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class SimConfig:
simulation. You may specify just one, or a tuple of the allowed
noise types:

- "leakage": Adds an error state 'x' to the computational
basis, that can interact with the other states via an
effective noise channel (which must be defined).
- "relaxation": Relaxation from the Rydberg to the ground state.
- "dephasing": Random phase (Z) flip.
- "depolarizing": Quantum noise where the state (rho) is
Expand Down Expand Up @@ -140,6 +143,7 @@ def from_noise_model(cls: Type[T], noise_model: NoiseModel) -> T:
kwargs[_DIFF_NOISE_PARAMS.get(param, param)] = getattr(
noise_model, param
)
kwargs.pop("with_leakage", None)
return cls(**kwargs)

def to_noise_model(self) -> NoiseModel:
Expand Down Expand Up @@ -176,6 +180,11 @@ def __post_init__(self) -> None:
{f.name: getattr(self, f.name) for f in fields(self)}
)

@property
def with_leakage(self) -> bool:
"""Whether or not 'leakage' is included in the noise types."""
return "leakage" in self.noise

@property
def spam_dict(self) -> dict[str, float]:
"""A dictionary combining the SPAM error parameters."""
Expand Down Expand Up @@ -253,6 +262,7 @@ def _check_eff_noise(self) -> None:
self.eff_noise_rates,
self.eff_noise_opers,
"eff_noise" in self.noise,
self.with_leakage,
)

@property
Expand Down
5 changes: 5 additions & 0 deletions tests/test_abstract_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ def test_register(reg: Register | Register3D):
eff_noise_rates=(0.1,),
eff_noise_opers=(((0, -1j), (1j, 0)),),
),
NoiseModel(
eff_noise_rates=(0.1,),
eff_noise_opers=(((0, -1j, 0), (1j, 0, 0), (0, 0, 1)),),
with_leakage=True,
),
],
)
def test_noise_model(noise_model: NoiseModel):
Expand Down
Loading