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
55 changes: 51 additions & 4 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"
a-corni marked this conversation as resolved.
Show resolved Hide resolved
"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 All @@ -257,6 +275,7 @@ def to_tuple(obj: tuple) -> tuple:
cast(float, param_vals["state_prep_error"]),
cast(float, param_vals["amp_sigma"]),
cast(Union[float, None], param_vals["laser_waist"]),
cast(bool, param_vals["with_leakage"]),
)

if noise_types is not None:
Expand All @@ -277,7 +296,11 @@ 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 not (
param_vals[p] is None
or (isinstance(param_vals[p], bool) and not param_vals[p])
)
or p in relevant_params
a-corni marked this conversation as resolved.
Show resolved Hide resolved
}
self._validate_parameters(relevant_param_vals)

Expand All @@ -299,6 +322,7 @@ def _find_relevant_params(
state_prep_error: float,
amp_sigma: float,
laser_waist: float | None,
with_leakage: bool,
a-corni marked this conversation as resolved.
Show resolved Hide resolved
) -> set[str]:
relevant_params: set[str] = set()
for nt_ in noise_types:
Expand All @@ -314,6 +338,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 +371,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 Down Expand Up @@ -388,6 +431,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) and value
comp = "True"
if not is_valid:
raise ValueError(f"'{param}' must be {comp}, not {value}.")

Expand All @@ -404,6 +450,7 @@ def __repr__(self) -> str:
self.state_prep_error,
self.amp_sigma,
self.laser_waist,
self.with_leakage,
)
relevant_params.add("noise_types")
params_list = []
Expand Down
13 changes: 12 additions & 1 deletion pulser-simulation/pulser_simulation/simconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ class SimConfig:
noise: Types of noises to be used in the
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 @@ -135,11 +137,13 @@ def from_noise_model(cls: Type[T], noise_model: NoiseModel) -> T:
noise_model.state_prep_error,
noise_model.amp_sigma,
noise_model.laser_waist,
noise_model.with_leakage,
)
for param in relevant_params:
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 All @@ -149,6 +153,7 @@ def to_noise_model(self) -> NoiseModel:
self.eta,
self.amp_sigma,
self.laser_waist,
self.with_leakage,
)
kwargs = {}
for param in relevant_params:
Expand Down Expand Up @@ -176,6 +181,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 +263,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
1 change: 1 addition & 0 deletions pulser-simulation/pulser_simulation/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ def add_config(self, config: SimConfig) -> None:
noise_model.state_prep_error,
noise_model.amp_sigma,
noise_model.laser_waist,
noise_model.with_leakage,
)
for param in relevant_params:
param_dict[param] = getattr(noise_model, param)
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), (1j, 0)),),
with_leakage=True,
),
],
)
def test_noise_model(noise_model: NoiseModel):
Expand Down
Loading