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
43 changes: 17 additions & 26 deletions pulser-core/pulser/noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@
_BOOLEAN = {"with_leakage"}

_LEGACY_DEFAULTS = {
"with_leakage": True,
"runs": 15,
"samples_per_run": 5,
"state_prep_error": 0.005,
Expand Down Expand Up @@ -230,7 +229,6 @@ def to_tuple(obj: tuple) -> tuple:
eff_noise_opers=to_tuple(eff_noise_opers),
with_leakage=with_leakage,
)
print("Initial param_vals", param_vals)
if noise_types is not None:
with warnings.catch_warnings():
warnings.simplefilter("always")
Expand All @@ -246,26 +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_]
print("param_vals post noise_types", param_vals)
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
}
print("With leakage", param_vals["with_leakage"])
self._check_leakage_noise(
true_noise_types if noise_types is None else noise_types
)
print("With leakage", param_vals["with_leakage"])
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=False,
with_leakage=cast(bool, param_vals["with_leakage"]),
)

# Get rid of unnecessary None's
Expand Down Expand Up @@ -298,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 Down Expand Up @@ -334,9 +336,6 @@ def _find_relevant_params(
# Disregard laser_waist when not defined
if laser_waist is None:
relevant_params.discard("laser_waist")
# Diregard leakage if with_leakage is False
if not with_leakage:
relevant_params.discard("with_leakage")
return relevant_params

@staticmethod
Expand Down Expand Up @@ -397,12 +396,6 @@ def _check_eff_noise(

if np.any(np.array(eff_noise_rates) < 0):
raise ValueError("The provided rates must be greater than 0.")
print(with_leakage)
min_shape = 2 if not with_leakage else 3
possible_shapes = [
(min_shape, min_shape),
(min_shape + 1, min_shape + 1),
]

# Check the validity of operators
for op in eff_noise_opers:
Expand All @@ -416,11 +409,9 @@ def _check_eff_noise(
if operator.ndim != 2:
raise ValueError(f"Operator '{op!r}' is not a 2D array.")

if operator.shape not in possible_shapes:
raise ValueError(
f"With{'' if with_leakage else 'out'} leakage, operator's "
f"shape must be {possible_shapes[0]} or "
f"{possible_shapes[1]}, not {operator.shape}."
if operator.shape != (2, 2):
raise NotImplementedError(
f"Operator's shape must be (2,2) not {operator.shape}."
a-corni marked this conversation as resolved.
Show resolved Hide resolved
)

@staticmethod
Expand All @@ -441,8 +432,8 @@ def _validate_parameters(param_vals: dict[str, Any]) -> None:
"or equal to one"
)
elif param in _BOOLEAN:
is_valid = isinstance(value, bool)
comp = "a boolean"
is_valid = isinstance(value, bool) and value
comp = "True"
if not is_valid:
raise ValueError(f"'{param}' must be {comp}, not {value}.")

Expand Down
6 changes: 3 additions & 3 deletions pulser-simulation/pulser_simulation/simconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ 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.
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 @@ -182,7 +182,7 @@ def __post_init__(self) -> None:
)

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

Expand Down
2 changes: 1 addition & 1 deletion tests/test_abstract_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def test_register(reg: Register | Register3D):
),
NoiseModel(
eff_noise_rates=(0.1,),
eff_noise_opers=(((0, -1j, 0), (1j, 0, 0), (0, 0, 1)),),
eff_noise_opers=(((0, -1j), (1j, 0)),),
with_leakage=True,
),
],
Expand Down
38 changes: 20 additions & 18 deletions tests/test_noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,25 +177,24 @@ def matrices(self):
matrices["Zh"] = 0.5 * np.array([[1, 0], [0, -1]])
matrices["ket"] = np.array([[1.0], [2.0]])
matrices["I3"] = np.eye(3)
matrices["I4"] = np.eye(4)
return matrices

@pytest.mark.parametrize("value", [False, True, 1, 0.1])
@pytest.mark.parametrize("value", [False, True, 0, 1, 0.1])
def test_init_bool_like(self, value, matrices):
if isinstance(value, bool):
noise_model = NoiseModel(
eff_noise_rates=[0.1],
eff_noise_opers=[matrices["I3"]],
eff_noise_opers=[matrices["I"]],
with_leakage=value,
)
assert noise_model.with_leakage == value
return
with pytest.raises(
ValueError, match=f"'with_leakage' must be a boolean, not {value}"
ValueError, match=f"'with_leakage' must be True, not {value}"
):
noise_model = NoiseModel(
eff_noise_rates=[0.1],
eff_noise_opers=[matrices["I3"]],
eff_noise_opers=[matrices["I"]],
with_leakage=value,
)

Expand Down Expand Up @@ -228,14 +227,9 @@ def test_eff_noise_opers(self, matrices):
eff_noise_opers=[2.0],
eff_noise_rates=[1.0],
)
with pytest.raises(ValueError, match="Without leakage"):
with pytest.raises(NotImplementedError, match="Operator's shape"):
NoiseModel(
eff_noise_opers=[matrices["I4"]],
eff_noise_rates=[1.0],
)
with pytest.raises(ValueError, match="With leakage"):
NoiseModel(
eff_noise_opers=[matrices["I"]],
eff_noise_opers=[matrices["I3"]],
eff_noise_rates=[1.0],
with_leakage=True,
)
Expand Down Expand Up @@ -304,18 +298,16 @@ def _add_leakage(noise_set: set, with_leakage: bool) -> set:
) == {"amp_sigma", "laser_waist", "runs", "samples_per_run"}
assert NoiseModel._find_relevant_params(
{"dephasing", "leakage"}, 0.0, 0.0, None, with_leakage
) == _add_leakage(
{"dephasing_rate", "hyperfine_dephasing_rate"}, with_leakage
)
) == {"dephasing_rate", "hyperfine_dephasing_rate", "with_leakage"}
assert NoiseModel._find_relevant_params(
{"relaxation", "leakage"}, 0.0, 0.0, None, with_leakage
) == _add_leakage({"relaxation_rate"}, with_leakage)
) == {"relaxation_rate", "with_leakage"}
assert NoiseModel._find_relevant_params(
{"depolarizing", "leakage"}, 0.0, 0.0, None, with_leakage
) == _add_leakage({"depolarizing_rate"}, with_leakage)
) == {"depolarizing_rate", "with_leakage"}
assert NoiseModel._find_relevant_params(
{"eff_noise", "leakage"}, 0.0, 0.0, None, with_leakage
) == _add_leakage({"eff_noise_rates", "eff_noise_opers"}, with_leakage)
) == {"eff_noise_rates", "eff_noise_opers", "with_leakage"}

def test_repr(self):
assert repr(NoiseModel()) == "NoiseModel(noise_types=())"
Expand Down Expand Up @@ -397,6 +389,16 @@ def test_legacy_init(self, noise_type):
# Check that the parameter is not overwritten by the default
assert getattr(noise_model, non_zero_param) == 1

with pytest.raises(
ValueError,
match="'Leakage' cannot be explicitely defined in the noise",
):
with pytest.warns(
DeprecationWarning,
match="The explicit definition of noise types is deprecated",
):
NoiseModel(noise_types=("leakage",))

relevant_params = NoiseModel._find_relevant_params(
{noise_type},
# These values don't matter, they just have to be > 0
Expand Down
12 changes: 3 additions & 9 deletions tests/test_simconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def matrices():
pauli["X"] = sigmax()
pauli["Zh"] = 0.5 * sigmaz()
pauli["ket"] = Qobj([[1.0], [2.0]])
pauli["I4"] = qeye(4)
pauli["I3"] = qeye(3)
return pauli


Expand Down Expand Up @@ -103,16 +103,10 @@ def test_eff_noise_opers(matrices):
eff_noise_opers=[matrices["ket"]],
eff_noise_rates=[1.0],
)
with pytest.raises(ValueError, match="With leakage, operator's shape"):
with pytest.raises(NotImplementedError, match="Operator's shape"):
SimConfig(
noise=("eff_noise", "leakage"),
eff_noise_opers=[matrices["I"]],
eff_noise_rates=[1.0],
)
with pytest.raises(ValueError, match="Without leakage, operator's shape"):
SimConfig(
noise=("eff_noise",),
eff_noise_opers=[matrices["I4"]],
eff_noise_opers=[matrices["I3"]],
eff_noise_rates=[1.0],
)
SimConfig(
Expand Down