Skip to content

Commit

Permalink
change name of noise to include digital
Browse files Browse the repository at this point in the history
  • Loading branch information
Charles MOUSSA committed Jan 8, 2025
1 parent 8053a7a commit 07db5e0
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 39 deletions.
14 changes: 7 additions & 7 deletions docs/noise.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,27 +69,27 @@ K_{2} \ =\sqrt{1\ -p} \ \begin{pmatrix}
\end{pmatrix}
\end{cases}$

Noise protocols can be added to gates by instantiating `NoiseInstance` providing the `NoiseType` and the `error_probability` (either float or tuple of float):
Noise protocols can be added to gates by instantiating `DigitalNoiseInstance` providing the `DigitalNoiseType` and the `error_probability` (either float or tuple of float):

```python exec="on" source="material-block" html="1"
from horqrux.noise import NoiseInstance, NoiseType
from horqrux.noise import DigitalNoiseInstance, DigitalNoiseType

noise_prob = 0.3
AmpD = NoiseInstance(NoiseType.AMPLITUDE_DAMPING, error_probability=noise_prob)
AmpD = DigitalNoiseInstance(DigitalNoiseType.AMPLITUDE_DAMPING, error_probability=noise_prob)

```

Then a gate can be instantiated by providing a tuple of `NoiseInstance` instances. Let’s show this through the simulation of a realistic $X$ gate.
Then a gate can be instantiated by providing a tuple of `DigitalNoiseInstance` instances. Let’s show this through the simulation of a realistic $X$ gate.

For instance, an $X$ gate flips the state of the qubit: $X|0\rangle = |1\rangle$. In practice, it's common for the target qubit to stay in its original state after applying $X$ due to the interactions between it and its environment. The possibility of failure can be represented by the `BitFlip` subclass of `NoiseInstance`, which flips the state again after the application of the $X$ gate, returning it to its original state with a probability `1 - gate_fidelity`.
For instance, an $X$ gate flips the state of the qubit: $X|0\rangle = |1\rangle$. In practice, it's common for the target qubit to stay in its original state after applying $X$ due to the interactions between it and its environment. The possibility of failure can be represented by the `BitFlip` subclass of `DigitalNoiseInstance`, which flips the state again after the application of the $X$ gate, returning it to its original state with a probability `1 - gate_fidelity`.

```python exec="on" source="material-block"
from horqrux.api import sample
from horqrux.noise import NoiseInstance, NoiseType
from horqrux.noise import DigitalNoiseInstance, DigitalNoiseType
from horqrux.utils import density_mat, product_state
from horqrux.primitive import X

noise = (NoiseInstance(NoiseType.BITFLIP, 0.1),)
noise = (DigitalNoiseInstance(DigitalNoiseType.BITFLIP, 0.1),)
ops = [X(0)]
noisy_ops = [X(0, noise=noise)]
state = product_state("0")
Expand Down
10 changes: 5 additions & 5 deletions horqrux/noise.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
)


class NoiseType(StrEnum):
class DigitalNoiseType(StrEnum):
BITFLIP = "BitFlip"
PHASEFLIP = "PhaseFlip"
DEPOLARIZING = "Depolarizing"
Expand All @@ -43,16 +43,16 @@ class NoiseType(StrEnum):

@register_pytree_node_class
@dataclass
class NoiseInstance:
type: NoiseType
class DigitalNoiseInstance:
type: DigitalNoiseType
error_probability: tuple[float, ...] | float

def __iter__(self) -> Iterable:
return iter((self.kraus, self.error_probability))

def tree_flatten(
self,
) -> tuple[tuple, tuple[NoiseType, tuple[float, ...] | float]]:
) -> tuple[tuple, tuple[DigitalNoiseType, tuple[float, ...] | float]]:
children = ()
aux_data = (self.type, self.error_probability)
return (children, aux_data)
Expand All @@ -70,4 +70,4 @@ def __repr__(self) -> str:
return self.type + f"(p={self.error_probability})"


NoiseProtocol = Union[tuple[NoiseInstance, ...], None]
NoiseProtocol = Union[tuple[DigitalNoiseInstance, ...], None]
8 changes: 4 additions & 4 deletions horqrux/shots.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def observable_to_matrix(


@singledispatch
def probs_from_eigenvectors_state(state: Any, eigvecs: Array) -> Array:
def eigen_probabilities(state: Any, eigvecs: Array) -> Array:
"""Obtain the probabilities using an input state and the eigenvectors decomposition
of an observable.
Expand All @@ -52,7 +52,7 @@ def probs_from_eigenvectors_state(state: Any, eigvecs: Array) -> Array:
raise NotImplementedError("prod_eigenvectors_state is not implemented")


@probs_from_eigenvectors_state.register
@eigen_probabilities.register
def _(state: Array, eigvecs: Array) -> Array:
"""Obtain the probabilities using an input quantum state vector
and the eigenvectors decomposition
Expand All @@ -69,7 +69,7 @@ def _(state: Array, eigvecs: Array) -> Array:
return jnp.abs(inner_prod) ** 2


@probs_from_eigenvectors_state.register
@eigen_probabilities.register
def _(state: DensityMatrix, eigvecs: Array) -> Array:
"""Obtain the probabilities using an input quantum density matrix
and the eigenvectors decomposition
Expand Down Expand Up @@ -97,7 +97,7 @@ def eigenval_decomposition_sampling(
mat_obs = [observable_to_matrix(observable, n_qubits, values) for observable in observables]
eigs = [jnp.linalg.eigh(mat) for mat in mat_obs]
eigvecs, eigvals = align_eigenvectors(eigs)
probs = probs_from_eigenvectors_state(state, eigvecs)
probs = eigen_probabilities(state, eigvecs)
return jax.random.choice(key=key, a=eigvals, p=probs, shape=(n_shots,)).mean(axis=0)


Expand Down
52 changes: 29 additions & 23 deletions tests/test_noise.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from horqrux.api import expectation, run, sample
from horqrux.apply import apply_gate
from horqrux.noise import NoiseInstance, NoiseType
from horqrux.noise import DigitalNoiseInstance, DigitalNoiseType
from horqrux.parametric import PHASE, RX, RY, RZ
from horqrux.primitive import NOT, H, I, S, T, X, Y, Z
from horqrux.utils import ForwardMode, density_mat, product_state, random_state
Expand All @@ -18,70 +18,76 @@
PRIMITIVE_GATES = (NOT, H, X, Y, Z, I, S, T)

NOISE_single_prob = (
NoiseType.BITFLIP,
NoiseType.PHASEFLIP,
NoiseType.DEPOLARIZING,
NoiseType.AMPLITUDE_DAMPING,
NoiseType.PHASE_DAMPING,
DigitalNoiseType.BITFLIP,
DigitalNoiseType.PHASEFLIP,
DigitalNoiseType.DEPOLARIZING,
DigitalNoiseType.AMPLITUDE_DAMPING,
DigitalNoiseType.PHASE_DAMPING,
)
ALL_NOISES = list(NoiseType)
ALL_NOISES = list(DigitalNoiseType)


def noise_instance(noise_type: NoiseType) -> NoiseInstance:
def noise_instance(noise_type: DigitalNoiseType) -> DigitalNoiseInstance:
if noise_type in NOISE_single_prob:
errors = 0.1
elif noise_type == NoiseType.PAULI_CHANNEL:
elif noise_type == DigitalNoiseType.PAULI_CHANNEL:
errors = (0.4, 0.5, 0.1)
else:
errors = (0.2, 0.8)

return NoiseInstance(noise_type, error_probability=errors)
return DigitalNoiseInstance(noise_type, error_probability=errors)


@pytest.mark.parametrize("noise_type", NOISE_single_prob)
def test_error_prob(noise_type: NoiseType):
def test_error_prob(noise_type: DigitalNoiseType):
with pytest.raises(ValueError):
noise = NoiseInstance(noise_type, error_probability=-0.5).kraus
noise = DigitalNoiseInstance(noise_type, error_probability=-0.5).kraus
with pytest.raises(ValueError):
noise = NoiseInstance(noise_type, error_probability=1.1).kraus
noise = DigitalNoiseInstance(noise_type, error_probability=1.1).kraus


def test_error_paulichannel():
with pytest.raises(ValueError):
noise = NoiseInstance(NoiseType.PAULI_CHANNEL, error_probability=(0.4, 0.5, 1.1)).kraus
noise = DigitalNoiseInstance(
DigitalNoiseType.PAULI_CHANNEL, error_probability=(0.4, 0.5, 1.1)
).kraus

for p in range(3):
probas = [1.0 / 3.0] * 3
probas[p] = -0.1
with pytest.raises(ValueError):
noise = NoiseInstance(NoiseType.PAULI_CHANNEL, error_probability=probas).kraus
noise = DigitalNoiseInstance(
DigitalNoiseType.PAULI_CHANNEL, error_probability=probas
).kraus

probas = [0.0] * 3
probas[p] = 1.1
with pytest.raises(ValueError):
noise = NoiseInstance(NoiseType.PAULI_CHANNEL, error_probability=probas).kraus
noise = DigitalNoiseInstance(
DigitalNoiseType.PAULI_CHANNEL, error_probability=probas
).kraus


def test_error_prob_GeneralizedAmplitudeDamping():
for p in range(2):
probas = [1.0 / 2.0] * 2
probas[p] = -0.1
with pytest.raises(ValueError):
noise = NoiseInstance(
NoiseType.GENERALIZED_AMPLITUDE_DAMPING, error_probability=probas
noise = DigitalNoiseInstance(
DigitalNoiseType.GENERALIZED_AMPLITUDE_DAMPING, error_probability=probas
).kraus

probas = [0.0] * 2
probas[p] = 1.1
with pytest.raises(ValueError):
noise = NoiseInstance(
NoiseType.GENERALIZED_AMPLITUDE_DAMPING, error_probability=probas
noise = DigitalNoiseInstance(
DigitalNoiseType.GENERALIZED_AMPLITUDE_DAMPING, error_probability=probas
).kraus


@pytest.mark.parametrize("gate_fn", PRIMITIVE_GATES)
@pytest.mark.parametrize("noise_type", ALL_NOISES)
def test_noisy_primitive(gate_fn: Callable, noise_type: NoiseType) -> None:
def test_noisy_primitive(gate_fn: Callable, noise_type: DigitalNoiseType) -> None:
target = np.random.randint(0, MAX_QUBITS)
noise = noise_instance(noise_type)

Expand Down Expand Up @@ -111,7 +117,7 @@ def test_noisy_primitive(gate_fn: Callable, noise_type: NoiseType) -> None:

@pytest.mark.parametrize("gate_fn", PARAMETRIC_GATES)
@pytest.mark.parametrize("noise_type", ALL_NOISES)
def test_noisy_parametric(gate_fn: Callable, noise_type: NoiseType) -> None:
def test_noisy_parametric(gate_fn: Callable, noise_type: DigitalNoiseType) -> None:
target = np.random.randint(0, MAX_QUBITS)
noise = noise_instance(noise_type)
noisy_gate = gate_fn("theta", target, noise=(noise,))
Expand Down Expand Up @@ -140,7 +146,7 @@ def test_noisy_parametric(gate_fn: Callable, noise_type: NoiseType) -> None:


def simple_depolarizing_test() -> None:
noise = (NoiseInstance(NoiseType.DEPOLARIZING, 0.1),)
noise = (DigitalNoiseInstance(DigitalNoiseType.DEPOLARIZING, 0.1),)
ops = [X(0, noise=noise), X(1)]
state = product_state("00")
state_output = run(ops, state)
Expand Down

0 comments on commit 07db5e0

Please # to comment.