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

[Refactoring] [Testing] [Feature] NoiseHandler #591

Merged
merged 58 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
cf44bbb
adding test transpile
Oct 23, 2024
3e6b9df
change for digitalnoisetype
Oct 23, 2024
b84e423
rm types in noise
Oct 23, 2024
6a18c0b
change noise to noisesource
Oct 23, 2024
f76cd12
update in docs
Oct 23, 2024
21f4e2e
make noise config class
Oct 23, 2024
608ea55
adding list of noise sources instantiation
Oct 23, 2024
0e28c4e
addins instantiation from NoiseSource
Oct 23, 2024
b4fda47
raise error if both digital and analog are present
Oct 23, 2024
ebe5bc3
readout at the end only one
Oct 23, 2024
669b1df
apply_noise to check if readout
Oct 23, 2024
b86dffa
only allow readout with digital
Oct 24, 2024
ba68a4e
check protocol is available when creating noiseSource
Oct 24, 2024
7b70433
use NoiseConfig everywhere
Oct 24, 2024
db4eee6
verify options when creating Noisesource
Oct 24, 2024
ad777bb
update docs
Oct 24, 2024
0edc2b2
check error_prob is None instead
Oct 24, 2024
25d3973
refactor check options
Oct 24, 2024
fa2ed65
NoiseHandler
Oct 24, 2024
a5bfd7f
rm noisesource typing for only noisehandler
Oct 24, 2024
d96b7aa
change to apply_readout_noise
Oct 24, 2024
9398e87
change type to noise_type
Oct 25, 2024
a1f96c2
adding several ways to instantiate noisehandler
Oct 25, 2024
af02fdc
use noisehandler in ops and convert_ops
Oct 25, 2024
c05bbff
change docs with noisehandler
Oct 25, 2024
9c0367a
add small test expectation for checking pyq conversion
Oct 25, 2024
e25add6
serialization fix
Oct 25, 2024
23cd24a
fix docs import Noisetype
Oct 25, 2024
688d237
fix types import + add str rep protocol
Oct 25, 2024
4c8c3e7
keep Union in execution
Oct 25, 2024
af19db2
union with comma
Oct 25, 2024
88ea5b1
minor fixes
Oct 26, 2024
4397ad9
fix readout mitigation
Oct 26, 2024
e72aab0
fix _from_dict in protocols
Oct 26, 2024
cbc1e17
make nested noiseprotocol
Oct 28, 2024
44092a2
fix noise doc
Oct 28, 2024
b7529e8
add equal for serialization test
Oct 28, 2024
d9ecd31
add noisy sim on Pulser in docs
Oct 28, 2024
c19c533
bump version
Oct 28, 2024
5617a11
Update qadence/backends/pulser/backend.py
chMoussa Oct 28, 2024
0c105ee
Update docs/tutorials/realistic_sims/noise.md
chMoussa Oct 28, 2024
185e12f
bump minor
Oct 28, 2024
8d285e5
Merge branch 'cm/noise_new_interface' of github.com:pasqal-io/qadence…
Oct 28, 2024
1eecb66
remove check block.noise when converting within function
Oct 28, 2024
2e3b2b0
avoing readout.readout
Oct 29, 2024
29f559a
add a composition of noise in test
Oct 29, 2024
ba05f6d
add method append and tests
Oct 29, 2024
f3ec831
add api noise
Oct 29, 2024
ebe6ef0
lint
Oct 29, 2024
777c42f
fix api
Oct 29, 2024
6c78c78
remove noise source class
Oct 29, 2024
db9b958
add test readout on predefined function readout
Oct 29, 2024
580dfec
add test equality digital noise bitflip
Oct 29, 2024
5c662f8
fix docs
Oct 29, 2024
2766aec
add list typing in Noisehandler docstring
Oct 29, 2024
bfcd97b
change docstring
Oct 29, 2024
723f9a1
fix protocols -> protocol
Oct 30, 2024
524a64d
fix docstr too
Oct 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/api/noise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Noise for simulations

### ::: qadence.noise.protocols
6 changes: 3 additions & 3 deletions docs/tutorials/realistic_sims/mitigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ subjected to physicality constraints $0 \leq p_{corr}(x) \leq 1$ and $\lVert p_{
```python exec="on" source="material-block" session="mitigation" result="json"
from qadence import QuantumModel, QuantumCircuit, kron, H, Z
from qadence import hamiltonian_factory
from qadence.noise import Noise
from qadence.noise import NoiseHandler
from qadence.mitigations import Mitigations
from qadence.types import ReadOutOptimization
from qadence.types import ReadOutOptimization, NoiseProtocol

# Simple circuit and observable construction.
block = kron(H(0), Z(1))
Expand All @@ -45,7 +45,7 @@ observable = hamiltonian_factory(circuit.n_qubits, detuning=Z)
model = QuantumModel(circuit=circuit, observable=observable)

# Define a noise model to use:
noise = Noise(protocol=Noise.READOUT)
noise = NoiseHandler(NoiseProtocol.READOUT)
# Define the mitigation method solving the minimization problem:
options={"optimization_type": ReadOutOptimization.CONSTRAINED} # ReadOutOptimization.MLE for the alternative method.
mitigation = Mitigations(protocol=Mitigations.READOUT, options=options)
Expand Down
112 changes: 101 additions & 11 deletions docs/tutorials/realistic_sims/noise.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,61 @@
Running programs on NISQ devices often leads to partially useful results due to the presence of noise.
In order to perform realistic simulations, a number of noise models are supported in Qadence and
In order to perform realistic simulations, a number of noise models (for digital operations, analog operations and simulated readout errors) are supported in Qadence through their implementation in backends and
corresponding error mitigation techniques whenever possible.

# NoiseHandler

Noise models can be defined via the `NoiseHandler`. It is a container of several noise instances which require to specify a `protocols` and
a dictionary of `options` (or lists). The `protocol` field is to be instantiated from `NoiseProtocol`.

```python exec="on" source="material-block" session="noise" result="json"
from qadence import NoiseHandler
from qadence.types import NoiseProtocol

analog_noise = NoiseHandler(protocol=NoiseProtocol.ANALOG.DEPOLARIZING, options={"noise_probs": 0.1})
digital_noise = NoiseHandler(protocol=NoiseProtocol.DIGITAL.DEPOLARIZING, options={"error_probability": 0.1})
readout_noise = NoiseHandler(protocol=NoiseProtocol.READOUT, options={"error_probability": 0.1, "seed": 0})
```

One can also define a `NoiseHandler` passing a list of protocols and a list of options (careful with the order):

```python exec="on" source="material-block" session="noise" result="json"
from qadence import NoiseHandler
from qadence.types import NoiseProtocol

protocols = [NoiseProtocol.DIGITAL.DEPOLARIZING, NoiseProtocol.READOUT]
options = [{"error_probability": 0.1}, {"error_probability": 0.1, "seed": 0}]

noise_combination = NoiseHandler(protocols, options)
print(noise_combination)
```

One can also append to a `NoiseHandler` other `NoiseHandler` instances:

```python exec="on" source="material-block" session="noise" result="json"
from qadence import NoiseHandler
from qadence.types import NoiseProtocol

depo_noise = NoiseHandler(protocol=NoiseProtocol.DIGITAL.DEPOLARIZING, options={"error_probability": 0.1})
readout_noise = NoiseHandler(protocol=NoiseProtocol.READOUT, options={"error_probability": 0.1, "seed": 0})

noise_combination = NoiseHandler(protocol=NoiseProtocol.DIGITAL.BITFLIP, options={"error_probability": 0.1})
noise_combination.append([depo_noise, readout_noise])
print(noise_combination)
```

Finally, one can add directly a few pre-defined types using several `NoiseHandler` methods:

```python exec="on" source="material-block" session="noise" result="json"
from qadence import NoiseHandler
from qadence.types import NoiseProtocol
noise_combination = NoiseHandler(protocol=NoiseProtocol.DIGITAL.BITFLIP, options={"error_probability": 0.1})
noise_combination.digital_depolarizing({"error_probability": 0.1}).readout({"error_probability": 0.1, "seed": 0})
print(noise_combination)
```

!!! warning "NoiseHandler scope"
Note it is not possible to define a `NoiseHandler` instances with both digital and analog noises, both readout and analog noises, several analog noises, several readout noises, or a readout noise that is not the last defined protocol within `NoiseHandler`.

## Readout errors

State Preparation and Measurement (SPAM) in the hardware is a major source of noise in the execution of
chMoussa marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -12,13 +66,12 @@ T(x|x')=\delta_{xx'}
$$


Qadence offers to simulate readout errors with the `Noise` protocol to corrupt the output
Qadence offers to simulate readout errors with the `NoiseHandler` to corrupt the output
samples of a simulation, through execution via a `QuantumModel`:

```python exec="on" source="material-block" session="noise" result="json"
from qadence import QuantumModel, QuantumCircuit, kron, H, Z
from qadence import hamiltonian_factory
from qadence.noise import Noise

# Simple circuit and observable construction.
block = kron(H(0), Z(1))
Expand All @@ -29,7 +82,7 @@ observable = hamiltonian_factory(circuit.n_qubits, detuning=Z)
model = QuantumModel(circuit=circuit, observable=observable)

# Define a noise model to use.
noise = Noise(protocol=Noise.READOUT)
noise = NoiseHandler(protocol=NoiseProtocol.READOUT)

# Run noiseless and noisy simulations.
noiseless_samples = model.sample(n_shots=100)
Expand All @@ -39,6 +92,16 @@ print(f"noiseless = {noiseless_samples}") # markdown-exec: hide
print(f"noisy = {noisy_samples}") # markdown-exec: hide
```

Note we can apply directly the method `apply_readout_noise` to the noiseless samples as follows:

```python exec="on" source="material-block" session="noise" result="json"
from qadence.noise import apply_readout_noise
altered_samples = apply_readout_noise(noise, noiseless_samples)

print(f"noiseless = {noiseless_samples}") # markdown-exec: hide
print(f"noisy = {noisy_samples}") # markdown-exec: hide
```

It is possible to pass options to the noise model. In the previous example, a noise matrix is implicitly computed from a
uniform distribution. The `option` dictionary argument accepts the following options:

Expand All @@ -55,7 +118,7 @@ from qadence.measurements import Measurements

# Define a noise model with options.
options = {"error_probability": 0.01}
noise = Noise(protocol=Noise.READOUT, options=options)
noise = NoiseHandler(protocol=NoiseProtocol.READOUT, options=options)

# Define a tomographical measurement protocol with options.
options = {"n_shots": 10000}
Expand All @@ -69,17 +132,44 @@ print(f"noiseless = {noiseless_exp}") # markdown-exec: hide
print(f"noisy = {noisy_exp}") # markdown-exec: hide
```

## Analog noisy simulation

At the moment, analog noisy simulations are only compatable with the Pulser backend.
```python exec="on" source="material-block" session="noise" result="json"
from qadence import DiffMode, NoiseHandler, QuantumModel
from qadence.blocks import chain, kron
from qadence.circuit import QuantumCircuit
from qadence.operations import AnalogRX, AnalogRZ, Z
from qadence.types import PI, BackendName, NoiseProtocol


analog_block = chain(AnalogRX(PI / 2.0), AnalogRZ(PI))
observable = Z(0) + Z(1)
circuit = QuantumCircuit(2, analog_block)

options = {"noise_probs": 0.1}
noise = NoiseHandler(protocol=NoiseProtocol.ANALOG.DEPOLARIZING, options=options)
model_noisy = QuantumModel(
circuit=circuit,
observable=observable,
backend=BackendName.PULSER,
diff_mode=DiffMode.GPSR,
noise=noise,
)
noisy_expectation = model_noisy.expectation()
print(f"noisy = {noisy_expectation}") # markdown-exec: hide
```


## Digital noisy simulation

When dealing with programs involving only digital operations, several options are made available from [PyQTorch](https://pasqal-io.github.io/pyqtorch/latest/noise/) via the `NoiseType`. One can define noisy digital operations with `DigitalNoise`as follows:
When dealing with programs involving only digital operations, several options are made available from [PyQTorch](https://pasqal-io.github.io/pyqtorch/latest/noise/) via the `NoiseProtocol.DIGITAL`. One can define noisy digital operations as follows:

```python exec="on" source="material-block" session="noise" result="json"
from qadence import NoiseType, DigitalNoise, RX, run
from qadence import NoiseProtocol, RX, run
import torch

noise = DigitalNoise(NoiseType.BITFLIP, error_probability = 0.2)
noise = DigitalNoise.bitflip(error_probability = 0.2) # equivalent

noise = NoiseHandler(NoiseProtocol.DIGITAL.BITFLIP, {"error_probability": 0.2})
op = RX(0, torch.pi, noise = noise)

print(run(op))
Expand All @@ -94,7 +184,7 @@ n_qubits = 2

block = chain(RX(i, f"theta_{i}") for i in range(n_qubits))

noise = DigitalNoise.bitflip(error_probability = 0.1)
noise = NoiseHandler(NoiseProtocol.DIGITAL.BITFLIP, {"error_probability": 0.1})

# The function changes the block in place:
set_noise(block, noise)
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ nav:
- QuantumCircuit: api/quantumcircuit.md
- Parameters: api/parameters.md
- State preparation: api/states.md
- Noise: api/noise.md
- Constructors: api/constructors.md
- Transpilation: api/transpile.md
- Execution: api/execution.md
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ authors = [
]
requires-python = ">=3.9"
license = { text = "Apache 2.0" }
version = "1.7.8"
version = "1.8.0"
classifiers = [
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
Expand Down
6 changes: 3 additions & 3 deletions qadence/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from qadence.circuit import QuantumCircuit
from qadence.measurements import Measurements
from qadence.mitigations import Mitigations
from qadence.noise import Noise
from qadence.noise import NoiseHandler
from qadence.parameters import stringify
from qadence.types import ArrayLike, BackendName, DiffMode, Endianness, Engine, ParamDictType

Expand Down Expand Up @@ -240,7 +240,7 @@ def sample(
param_values: dict[str, Tensor] = {},
n_shots: int = 1000,
state: ArrayLike | None = None,
noise: Noise | None = None,
noise: NoiseHandler | None = None,
mitigation: Mitigations | None = None,
endianness: Endianness = Endianness.BIG,
) -> list[Counter]:
Expand Down Expand Up @@ -290,7 +290,7 @@ def expectation(
param_values: ParamDictType = {},
state: ArrayLike | None = None,
measurement: Measurements | None = None,
noise: Noise | None = None,
noise: NoiseHandler | None = None,
mitigation: Mitigations | None = None,
endianness: Endianness = Endianness.BIG,
) -> ArrayLike:
Expand Down
10 changes: 5 additions & 5 deletions qadence/backends/braket/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
from qadence.measurements import Measurements
from qadence.mitigations import Mitigations
from qadence.mitigations.protocols import apply_mitigation
from qadence.noise import Noise
from qadence.noise.protocols import apply_noise
from qadence.noise import NoiseHandler
from qadence.noise.protocols import apply_readout_noise
from qadence.overlap import overlap_exact
from qadence.transpile import transpile
from qadence.types import BackendName, Engine
Expand Down Expand Up @@ -137,7 +137,7 @@ def sample(
param_values: dict[str, Tensor] = {},
n_shots: int = 1,
state: Tensor | None = None,
noise: Noise | None = None,
noise: NoiseHandler | None = None,
mitigation: Mitigations | None = None,
endianness: Endianness = Endianness.BIG,
) -> list[Counter]:
Expand All @@ -164,7 +164,7 @@ def sample(

samples = invert_endianness(samples)
if noise is not None:
samples = apply_noise(noise=noise, samples=samples)
samples = apply_readout_noise(noise=noise, samples=samples)
if mitigation is not None:
logger.warning(
"Mitigation protocol is deprecated. Use qadence-protocols instead.",
Expand All @@ -180,7 +180,7 @@ def expectation(
param_values: dict[str, Tensor] = {},
state: Tensor | None = None,
measurement: Measurements | None = None,
noise: Noise | None = None,
noise: NoiseHandler | None = None,
mitigation: Mitigations | None = None,
endianness: Endianness = Endianness.BIG,
) -> Tensor:
Expand Down
6 changes: 3 additions & 3 deletions qadence/backends/horqrux/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from qadence.circuit import QuantumCircuit
from qadence.measurements import Measurements
from qadence.mitigations import Mitigations
from qadence.noise import Noise
from qadence.noise import NoiseHandler
from qadence.transpile import flatten, scale_primitive_blocks_only, transpile
from qadence.types import BackendName, Endianness, Engine, ParamDictType
from qadence.utils import int_to_basis
Expand Down Expand Up @@ -114,7 +114,7 @@ def expectation(
param_values: ParamDictType = {},
state: ArrayLike | None = None,
measurement: Measurements | None = None,
noise: Noise | None = None,
noise: NoiseHandler | None = None,
mitigation: Mitigations | None = None,
endianness: Endianness = Endianness.BIG,
) -> ArrayLike:
Expand Down Expand Up @@ -163,7 +163,7 @@ def sample(
param_values: ParamDictType = {},
n_shots: int = 1,
state: ArrayLike | None = None,
noise: Noise | None = None,
noise: NoiseHandler | None = None,
mitigation: Mitigations | None = None,
endianness: Endianness = Endianness.BIG,
) -> list[Counter]:
Expand Down
33 changes: 16 additions & 17 deletions qadence/backends/pulser/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
from qadence.measurements import Measurements
from qadence.mitigations import Mitigations
from qadence.mitigations.protocols import apply_mitigation
from qadence.noise import Noise
from qadence.noise.protocols import apply_noise
from qadence.noise import NoiseHandler
from qadence.noise.protocols import apply_readout_noise
chMoussa marked this conversation as resolved.
Show resolved Hide resolved
from qadence.overlap import overlap_exact
from qadence.register import Register
from qadence.transpile import transpile
from qadence.types import BackendName, DeviceType, Endianness, Engine
from qadence.types import BackendName, DeviceType, Endianness, Engine, NoiseProtocol

from .channels import GLOBAL_CHANNEL, LOCAL_CHANNEL
from .cloud import get_client
Expand Down Expand Up @@ -187,7 +187,7 @@ def run(
param_values: dict[str, Tensor] = {},
state: Tensor | None = None,
endianness: Endianness = Endianness.BIG,
noise: Noise | None = None,
noise: NoiseHandler | None = None,
) -> Tensor:
vals = to_list_of_dicts(param_values)

Expand Down Expand Up @@ -235,27 +235,26 @@ def run(
def _run_noisy(
self,
circuit: ConvertedCircuit,
noise: Noise,
noise: NoiseHandler,
param_values: dict[str, Tensor] = dict(),
state: Tensor | None = None,
endianness: Endianness = Endianness.BIG,
) -> Tensor:
vals = to_list_of_dicts(param_values)
noise_probs = noise.options.get("noise_probs", None)
if noise_probs is None:
KeyError("A `noise probs` option should be passed to the <class QuantumModel>.")
if not (isinstance(noise_probs, float) or isinstance(noise_probs, Iterable)):
KeyError(
"A single or a range of noise probabilities"
" should be passed. Got {type(noise_probs)}."
)
if not isinstance(noise.protocol[-1], NoiseProtocol.ANALOG):
raise TypeError("Noise must be of type `NoiseProtocol.ANALOG`.")
noise_probs = noise.options[-1].get("noise_probs", None)

def run_noisy_sim(noise_prob: float) -> Tensor:
batched_dm = np.zeros(
(len(vals), 2**circuit.abstract.n_qubits, 2**circuit.abstract.n_qubits),
dtype=np.complex128,
)
sim_config = {"noise": noise.protocol, noise.protocol + "_rate": noise_prob}
# pulser requires lower letters
sim_config = {
"noise": noise.protocol[-1].lower(),
noise.protocol[-1].lower() + "_rate": noise_prob,
}
self.config.sim_config = SimConfig(**sim_config)

for i, param_values_el in enumerate(vals):
Expand Down Expand Up @@ -289,7 +288,7 @@ def sample(
param_values: dict[str, Tensor] = {},
n_shots: int = 1,
state: Tensor | None = None,
noise: Noise | None = None,
noise: NoiseHandler | None = None,
mitigation: Mitigations | None = None,
endianness: Endianness = Endianness.BIG,
) -> list[Counter]:
Expand All @@ -313,7 +312,7 @@ def sample(

samples = invert_endianness(samples)
if noise is not None:
samples = apply_noise(noise=noise, samples=samples)
samples = apply_readout_noise(noise=noise, samples=samples)
if mitigation is not None:
logger.warning(
"Mitigation protocol is deprecated. Use qadence-protocols instead.",
Expand All @@ -329,7 +328,7 @@ def expectation(
param_values: dict[str, Tensor] = {},
state: Tensor | None = None,
measurement: Measurements | None = None,
noise: Noise | None = None,
noise: NoiseHandler | None = None,
mitigation: Mitigations | None = None,
endianness: Endianness = Endianness.BIG,
) -> Tensor:
Expand Down
Loading