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

[Feature] Implementation of noise protocol. #128

Merged
merged 95 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 93 commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
8c93af0
First attempt at error protocol.
RolandMacDoland Oct 23, 2023
04f34a8
change errors to exceptions, rename protocol
gvelikova Oct 24, 2023
1210b96
protocols renaming
gvelikova Oct 25, 2023
404ebaa
cleaned outstanding naming issues
gvelikova Oct 26, 2023
872b5f1
draft readout corruption, needs polishing and tests
gvelikova Oct 27, 2023
bf1d15b
added a simple test
gvelikova Oct 27, 2023
70103e0
slight change to the test
gvelikova Oct 27, 2023
8656c77
some non-ideal fixes for mypy
gvelikova Oct 27, 2023
3d30092
Merge branch 'main' into rg/errors-module
gvelikova Oct 27, 2023
5c81f01
linting
gvelikova Oct 27, 2023
d84bfe4
linting
gvelikova Oct 27, 2023
a0c5470
error definition
gvelikova Oct 27, 2023
b2842dd
cleaning up
gvelikova Oct 30, 2023
9cee440
tidying
gvelikova Oct 30, 2023
54d644a
Add error option for expectation.
RolandMacDoland Nov 1, 2023
db9ce1e
Add error for expectation and warning in no measurement case.
RolandMacDoland Nov 1, 2023
9f4219b
Make options optional.
RolandMacDoland Nov 1, 2023
fd62673
Pass readout error for tomography.
RolandMacDoland Nov 1, 2023
8935b3d
Correct logger import.
RolandMacDoland Nov 1, 2023
b2d6647
Fix name.
RolandMacDoland Nov 1, 2023
d63e9b1
First attempt to test readout errors with tomography.
RolandMacDoland Nov 1, 2023
f6360b6
Harmonise subclasses to base class.
RolandMacDoland Nov 2, 2023
b4f542f
Correct logging.
RolandMacDoland Nov 2, 2023
770e13d
Default options and correct function name.
RolandMacDoland Nov 2, 2023
1049895
Extract bitstring corruption functionality and pass options.
RolandMacDoland Nov 2, 2023
f3a3275
Test bitstring corruption and measurement protocols.
RolandMacDoland Nov 2, 2023
cd653e8
Merge branch 'main' into rg/errors-module
RolandMacDoland Nov 2, 2023
0f3a9ec
Tests for integration with measurements and bitstring corruption.
RolandMacDoland Nov 2, 2023
44332cc
fix incorrect counts after readout
gvelikova Nov 2, 2023
aa306dc
fixes
gvelikova Nov 2, 2023
8267b82
Moving old bitstring corruption functionality.
RolandMacDoland Nov 3, 2023
bc5756a
Simplify types and use old bitstring corruption.
RolandMacDoland Nov 3, 2023
022c788
Propagate error protocol for shadows.
RolandMacDoland Nov 3, 2023
b47dbd6
Add tests for bitstring corruption and integration with measurements.
RolandMacDoland Nov 3, 2023
7e82e4e
Merge branch 'main' into rg/errors-module
RolandMacDoland Nov 3, 2023
bae8eb3
Fix conflicts.
RolandMacDoland Nov 3, 2023
439f9f8
changed back the readout incorporation function and tests
gvelikova Nov 3, 2023
d6f0bee
Add docs section about protocols for realistic simulations.
RolandMacDoland Nov 3, 2023
fb51ed3
Fix lint.
RolandMacDoland Nov 3, 2023
4b26502
Remove obsolete code.
RolandMacDoland Nov 3, 2023
3651cbd
Merge branch 'main' into rg/errors-module
RolandMacDoland Nov 3, 2023
23989c5
added placeholder readmes on noisy simulations/error mitigation
gvelikova Nov 6, 2023
35f95d3
adressing minor issues
gvelikova Nov 6, 2023
83257de
placeholder index.md
gvelikova Nov 6, 2023
4538497
some cleaning up
gvelikova Nov 6, 2023
e8e2773
Add doc files.
RolandMacDoland Nov 6, 2023
64b7a0d
simplify corruption function
gvelikova Nov 6, 2023
1d170c8
improve on docstrings
gvelikova Nov 6, 2023
cc85eb3
Merge branch 'main' into rg/errors-module
gvelikova Nov 6, 2023
bd0d046
renamed errors to noise
gvelikova Nov 6, 2023
7f09e26
Comment and better logging condition.
RolandMacDoland Nov 6, 2023
9038c09
Better logging.
RolandMacDoland Nov 6, 2023
ccf5b6d
Better logging.
RolandMacDoland Nov 6, 2023
c713808
Remove unnecessary comment.
RolandMacDoland Nov 6, 2023
6b9d806
Clean-up.
RolandMacDoland Nov 6, 2023
2338fdb
rename the remaining errors to noise
gvelikova Nov 6, 2023
0a50a8d
merged
gvelikova Nov 6, 2023
9c5a10f
adding noise to models
gvelikova Nov 6, 2023
674f1c3
adding noise to qnn
gvelikova Nov 7, 2023
99fb61c
readme renamed to noise
gvelikova Nov 7, 2023
2ca1ae6
changed warning
gvelikova Nov 7, 2023
b50c531
[Refactor] Train loop; make sure qadence runs on GPUs correctly (#135)
dominikandreasseitz Nov 6, 2023
69abe6c
[Fix] Braket CPHASE always having fixed parameters (#162)
dominikandreasseitz Nov 6, 2023
1164a31
fix documentation warning
gvelikova Nov 7, 2023
af3299f
Merge branch 'main' into rg/errors-module
gvelikova Nov 7, 2023
882d730
added a noise.md
gvelikova Nov 7, 2023
1c2d226
Abstract noise application in module.
RolandMacDoland Nov 7, 2023
95a9f43
Add test for non-deterministic bit flipping.
RolandMacDoland Nov 7, 2023
ab02534
Better tests.
RolandMacDoland Nov 7, 2023
74dfcd3
correct the warning
gvelikova Nov 7, 2023
79db467
Merge branch 'main' into rg/errors-module
gvelikova Nov 7, 2023
0838d74
A section about noise usage.
RolandMacDoland Nov 7, 2023
8d13531
Re-run probabilitic test.
RolandMacDoland Nov 7, 2023
72a334b
Better docstring.
RolandMacDoland Nov 7, 2023
3995d82
Merge branch 'main' into rg/errors-module
RolandMacDoland Nov 7, 2023
1c6d6c0
Correct argument passing to superclass.
RolandMacDoland Nov 7, 2023
37f7644
Fix lint.
RolandMacDoland Nov 7, 2023
a6830f8
Enable sessions for variable reuse.
RolandMacDoland Nov 7, 2023
f0a73fd
Merge branch 'main' into rg/errors-module
RolandMacDoland Nov 8, 2023
2d81353
Fix docstrings.
RolandMacDoland Nov 8, 2023
72e1f75
changed to torch.Tensor
gvelikova Nov 8, 2023
211a7b8
refactoring
gvelikova Nov 8, 2023
000e47b
functorch for bit flipping
gvelikova Nov 8, 2023
8f1685c
docstring
gvelikova Nov 8, 2023
01be3f7
some shortening
gvelikova Nov 8, 2023
4ab9c5e
arg type
gvelikova Nov 8, 2023
37b2473
torch imports
gvelikova Nov 8, 2023
57c30cb
minor fixes
gvelikova Nov 9, 2023
c7718a1
change in the bit flipping function for performance
gvelikova Nov 9, 2023
3d6eb93
Log the file path.
RolandMacDoland Nov 9, 2023
246c0aa
Merge branch 'main' into rg/errors-module
RolandMacDoland Nov 9, 2023
4072050
Fix imports.
RolandMacDoland Nov 9, 2023
180606c
Merge branch 'main' into rg/errors-module
RolandMacDoland Nov 9, 2023
1c600ce
minor docstring-related fixes
gvelikova Nov 9, 2023
b77e820
removing numpy.array as an output/input option
gvelikova Nov 9, 2023
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
1 change: 1 addition & 0 deletions docs/realistic_sims/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This section describes how to perform realistic simulations in Qadence.
1 change: 1 addition & 0 deletions docs/realistic_sims/measurements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This section introduces the various measurement protocols.
1 change: 1 addition & 0 deletions docs/realistic_sims/mitigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This section introduces mitigation protocols.
63 changes: 63 additions & 0 deletions docs/realistic_sims/noise.md
RolandMacDoland marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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
corresponding error mitigation techniques whenever possible.

## Readout errors

State Preparation and Measurement (SPAM) in the hardware is a major source of noise in the execution of
quantum programs. Qadence offers to simulate readout errors with the `Noise` protocol 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))
circuit = QuantumCircuit(2, block)
observable = hamiltonian_factory(circuit.n_qubits, detuning=Z)

# Construct a quantum model.
model = QuantumModel(circuit=circuit, observable=observable)

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

# Run noiseless and noisy simulations.
noiseless_samples = model.sample(n_shots=100)
noisy_samples = model.sample(noise=noise, n_shots=100)

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:

- `seed`: defaulted to `None`, for reproducibility purposes
- `error_probability`: defaulted to 0.1, a bit flip probability
- `noise_distribution`: defaulted to `WhiteNoise.UNIFORM`, for non-uniform noise distributions
- `noise_matrix`: defaulted to `None`, if the noise matrix is known from third-party experiments, _i.e._ hardware calibration.

Noisy simulations go hand-in-hand with measurement protocols discussed in the previous [section](measurements.md), to assess the impact of noise on expectation values. In this case, both measurement and noise protocols have to be defined appropriately. Please note that a noise protocol without a measurement protocol will be ignored for expectation values computations.


```python exec="on" source="material-block" session="noise" result="json"
from qadence.measurements import Measurements

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

# Define a tomographical measurement protocol with options.
options = {"n_shots": 10000}
measurement = Measurements(protocol=Measurements.TOMOGRAPHY, options=options)

# Run noiseless and noisy simulations.
noiseless_exp = model.expectation(measurement=measurement)
noisy_exp = model.expectation(measurement=measurement, noise=noise)

print(f"noiseless = {noiseless_exp}") # markdown-exec: hide
print(f"noisy = {noisy_exp}") # markdown-exec: hide
```
6 changes: 6 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ nav:
- Quantum circuits differentiation: advanced_tutorials/differentiability.md
- Custom quantum models: advanced_tutorials/custom-models.md

- Realistic simulations:
- realistic_sims/index.md
- Measurement protocols: realistic_sims/measurements.md
- Simulated errors: realistic_sims/noise.md
- Error mitigation: realistic_sims/mitigation.md

- API:
- Block system: qadence/blocks.md
- Operations: qadence/operations.md
Expand Down
6 changes: 4 additions & 2 deletions qadence/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
from .blocks import *
from .circuit import *
from .constructors import *
from .errors import *
from .exceptions import *
from .execution import *
from .measurements import *
from .ml_tools import *
from .models import *
from .noise import *
from .operations import *
from .overlap import *
from .parameters import *
Expand Down Expand Up @@ -46,7 +47,8 @@
".blocks",
".circuit",
".constructors",
".errors",
".noise",
".exceptions",
".execution",
".measurements",
".ml_tools",
Expand Down
19 changes: 13 additions & 6 deletions qadence/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
from qadence.circuit import QuantumCircuit
from qadence.logger import get_logger
from qadence.measurements import Measurements
from qadence.noise import Noise
from qadence.parameters import stringify
from qadence.types import BackendName, DiffMode, Endianness

logger = get_logger(__name__)
logger = get_logger(__file__)
RolandMacDoland marked this conversation as resolved.
Show resolved Hide resolved


@dataclass
Expand Down Expand Up @@ -167,9 +168,9 @@ def observable(self, observable: AbstractBlock, n_qubits: int) -> ConvertedObser
def convert(
self, circuit: QuantumCircuit, observable: list[AbstractBlock] | AbstractBlock | None = None
) -> Converted:
"""Convert an abstract circuit (and optionally and observable) to their native.
"""Convert an abstract circuit and an optional observable to their native representation.
gvelikova marked this conversation as resolved.
Show resolved Hide resolved

representation. Additionally this function constructs an embedding function which maps from
Additionally this function constructs an embedding function which maps from
user-facing parameters to device parameters (read more on parameter embedding
[here][qadence.blocks.embedding.embedding]).
"""
Expand Down Expand Up @@ -233,6 +234,7 @@ def sample(
param_values: dict[str, Tensor] = {},
n_shots: int = 1000,
state: Tensor | None = None,
noise: Noise | None = None,
endianness: Endianness = Endianness.BIG,
) -> list[Counter]:
"""Sample bit strings.
Expand All @@ -243,7 +245,8 @@ def sample(
[`embedding`][qadence.blocks.embedding.embedding] for more info.
n_shots: Number of shots to sample.
state: Initial state.
endianness: Endianness of the resulting bitstrings.
noise: A noise model to use.
endianness: Endianness of the resulting bit strings.
"""
raise NotImplementedError

Expand Down Expand Up @@ -277,7 +280,8 @@ def expectation(
observable: list[ConvertedObservable] | ConvertedObservable,
param_values: dict[str, Tensor] = {},
state: Tensor | None = None,
protocol: Measurements | None = None,
measurement: Measurements | None = None,
noise: Noise | None = None,
endianness: Endianness = Endianness.BIG,
) -> Tensor:
"""Compute the expectation value of the `circuit` with the given `observable`.
Expand All @@ -287,7 +291,10 @@ def expectation(
param_values: _**Already embedded**_ parameters of the circuit. See
[`embedding`][qadence.blocks.embedding.embedding] for more info.
state: Initial state.
endianness: Endianness of the resulting bitstrings.
measurement: Optional measurement protocol. If None, use
exact expectation value with a statevector simulator.
noise: A noise model to use.
endianness: Endianness of the resulting bit strings.
"""
raise NotImplementedError

Expand Down
18 changes: 17 additions & 1 deletion qadence/backends/braket/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
from qadence.backends.utils import to_list_of_dicts
from qadence.blocks import AbstractBlock, block_to_tensor
from qadence.circuit import QuantumCircuit
from qadence.logger import get_logger
from qadence.measurements import Measurements
from qadence.noise import Noise
from qadence.noise.protocols import apply
from qadence.overlap import overlap_exact
from qadence.transpile import transpile
from qadence.types import BackendName
Expand All @@ -24,6 +27,8 @@
from .config import Configuration, default_passes
from .convert_ops import convert_block

logger = get_logger(__file__)


def promote_parameters(parameters: dict[str, Tensor | float]) -> dict[str, float]:
float_params = {}
Expand Down Expand Up @@ -127,6 +132,7 @@ def sample(
param_values: dict[str, Tensor] = {},
n_shots: int = 1,
state: Tensor | None = None,
noise: Noise | None = None,
endianness: Endianness = Endianness.BIG,
) -> list[Counter]:
"""Execute the circuit and return samples of the resulting wavefunction."""
Expand All @@ -141,6 +147,7 @@ def sample(
raise NotImplementedError

# loop over all values in the batch

samples = []
for vals in to_list_of_dicts(param_values):
final_circuit = self.assign_parameters(circuit, vals)
Expand All @@ -150,6 +157,8 @@ def sample(
from qadence.transpile import invert_endianness

samples = invert_endianness(samples)
if noise is not None:
samples = apply(noise=noise, samples=samples)
return samples

def expectation(
Expand All @@ -158,9 +167,16 @@ def expectation(
observable: list[ConvertedObservable] | ConvertedObservable,
param_values: dict[str, Tensor] = {},
state: Tensor | None = None,
protocol: Measurements | None = None,
measurement: Measurements | None = None,
noise: Noise | None = None,
endianness: Endianness = Endianness.BIG,
) -> Tensor:
# Noise is ignored if measurement protocol is not provided.
if noise is not None and measurement is None:
logger.warning(
f"Errors of type {noise} are not implemented for exact expectation yet. "
"This is ignored for now."
)
# Do not flip endianness here because then we would have to reverse the observable
wfs = self.run(circuit, param_values, state=state, endianness=Endianness.BIG)

Expand Down
2 changes: 1 addition & 1 deletion qadence/backends/braket/convert_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from braket.parametric import FreeParameter

from qadence.blocks import AbstractBlock, CompositeBlock, PrimitiveBlock
from qadence.errors import NotSupportedError
from qadence.exceptions import NotSupportedError
from qadence.operations import OpName
from qadence.parameters import evaluate

Expand Down
19 changes: 16 additions & 3 deletions qadence/backends/pulser/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
from qadence.backends.utils import to_list_of_dicts
from qadence.blocks import AbstractBlock
from qadence.circuit import QuantumCircuit
from qadence.logger import get_logger
from qadence.measurements import Measurements
from qadence.noise import Noise
from qadence.noise.protocols import apply
from qadence.overlap import overlap_exact
from qadence.register import Register
from qadence.transpile import transpile
from qadence.types import BackendName
from qadence.utils import Endianness, get_logger
from qadence.types import BackendName, Endianness

from .channels import GLOBAL_CHANNEL, LOCAL_CHANNEL
from .cloud import get_client
Expand Down Expand Up @@ -240,6 +242,7 @@ def sample(
param_values: dict[str, Tensor] = {},
n_shots: int = 1,
state: Tensor | None = None,
noise: Noise | None = None,
endianness: Endianness = Endianness.BIG,
) -> list[Counter]:
if n_shots < 1:
Expand All @@ -257,6 +260,8 @@ def sample(
from qadence.transpile import invert_endianness

samples = invert_endianness(samples)
if noise is not None:
samples = apply(noise=noise, samples=samples)
return samples

def expectation(
Expand All @@ -265,9 +270,17 @@ def expectation(
observable: list[ConvertedObservable] | ConvertedObservable,
param_values: dict[str, Tensor] = {},
state: Tensor | None = None,
protocol: Measurements | None = None,
measurement: Measurements | None = None,
noise: Noise | None = None,
endianness: Endianness = Endianness.BIG,
) -> Tensor:
# Noise is ignored if measurement protocol is not provided.
if noise is not None and measurement is None:
logger.warning(
f"Errors of type {noise} are not implemented for exact expectation yet. "
"This is ignored for now."
)

state = self.run(circuit, param_values=param_values, state=state, endianness=endianness)

observables = observable if isinstance(observable, list) else [observable]
Expand Down
29 changes: 24 additions & 5 deletions qadence/backends/pyqtorch/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
from qadence.backends.utils import to_list_of_dicts
from qadence.blocks import AbstractBlock
from qadence.circuit import QuantumCircuit
from qadence.logger import get_logger
from qadence.measurements import Measurements
from qadence.noise import Noise
from qadence.noise.protocols import apply
from qadence.overlap import overlap_exact
from qadence.states import zero_state
from qadence.transpile import (
Expand All @@ -28,6 +31,8 @@
from .config import Configuration, default_passes
from .convert_ops import convert_block, convert_observable

logger = get_logger(__name__)


@dataclass(frozen=True, eq=True)
class Backend(BackendInterface):
Expand Down Expand Up @@ -123,7 +128,8 @@ def _batched_expectation(
observable: list[ConvertedObservable] | ConvertedObservable,
param_values: dict[str, Tensor] = {},
state: Tensor | None = None,
protocol: Measurements | None = None,
measurement: Measurements | None = None,
noise: Noise | None = None,
endianness: Endianness = Endianness.BIG,
) -> Tensor:
state = self.run(
Expand All @@ -147,7 +153,8 @@ def _looped_expectation(
observable: list[ConvertedObservable] | ConvertedObservable,
param_values: dict[str, Tensor] = {},
state: Tensor | None = None,
protocol: Measurements | None = None,
measurement: Measurements | None = None,
noise: Noise | None = None,
endianness: Endianness = Endianness.BIG,
) -> Tensor:
state = zero_state(circuit.abstract.n_qubits, batch_size=1) if state is None else state
Expand All @@ -173,16 +180,24 @@ def expectation(
observable: list[ConvertedObservable] | ConvertedObservable,
param_values: dict[str, Tensor] = {},
state: Tensor | None = None,
protocol: Measurements | None = None,
measurement: Measurements | None = None,
noise: Noise | None = None,
endianness: Endianness = Endianness.BIG,
) -> Tensor:
# Noise is ignored if measurement protocol is not provided.
if noise is not None and measurement is None:
logger.warning(
f"Errors of type {noise} are not implemented for exact expectation yet. "
"This is ignored for now."
)
fn = self._looped_expectation if self.config.loop_expectation else self._batched_expectation
return fn(
circuit=circuit,
observable=observable,
param_values=param_values,
state=state,
protocol=protocol,
measurement=measurement,
noise=noise,
endianness=endianness,
)

Expand All @@ -192,6 +207,7 @@ def sample(
param_values: dict[str, Tensor] = {},
n_shots: int = 1,
state: Tensor | None = None,
noise: Noise | None = None,
endianness: Endianness = Endianness.BIG,
) -> list[Counter]:
if n_shots < 1:
Expand All @@ -212,7 +228,7 @@ def _sample(_probs: Tensor, n_shots: int, endianness: Endianness, n_qubits: int)

wf = self.run(circuit=circuit, param_values=param_values, state=state)
probs = torch.abs(torch.pow(wf, 2))
return list(
samples = list(
map(
lambda _probs: _sample(
_probs=_probs,
Expand All @@ -223,6 +239,9 @@ def _sample(_probs: Tensor, n_shots: int, endianness: Endianness, n_qubits: int)
probs,
)
)
if noise is not None:
samples = apply(noise=noise, samples=samples)
return samples

def assign_parameters(self, circuit: ConvertedCircuit, param_values: dict[str, Tensor]) -> Any:
raise NotImplementedError
Expand Down
Loading
Loading