Skip to content

Commit

Permalink
[Refactoring] Use Readout from Pyqtorch (#599)
Browse files Browse the repository at this point in the history
  • Loading branch information
chMoussa authored Nov 7, 2024
1 parent 6c2d309 commit 7f73072
Show file tree
Hide file tree
Showing 11 changed files with 76 additions and 351 deletions.
11 changes: 0 additions & 11 deletions docs/tutorials/realistic_sims/noise.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,23 +92,12 @@ 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:

- `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.

Expand Down
3 changes: 0 additions & 3 deletions qadence/backends/pulser/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from qadence.mitigations import Mitigations
from qadence.mitigations.protocols import apply_mitigation
from qadence.noise import NoiseHandler
from qadence.noise.protocols import apply_readout_noise
from qadence.overlap import overlap_exact
from qadence.register import Register
from qadence.transpile import transpile
Expand Down Expand Up @@ -311,8 +310,6 @@ def sample(
from qadence.transpile import invert_endianness

samples = invert_endianness(samples)
if noise is not None:
samples = apply_readout_noise(noise=noise, samples=samples)
if mitigation is not None:
logger.warning(
"Mitigation protocol is deprecated. Use qadence-protocols instead.",
Expand Down
27 changes: 22 additions & 5 deletions qadence/backends/pyqtorch/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from qadence.measurements import Measurements
from qadence.mitigations.protocols import Mitigations, apply_mitigation
from qadence.noise import NoiseHandler
from qadence.noise.protocols import apply_readout_noise
from qadence.transpile import (
chain_single_qubit_ops,
flatten,
Expand All @@ -34,7 +33,7 @@
from qadence.types import BackendName, Endianness, Engine

from .config import Configuration, default_passes
from .convert_ops import convert_block
from .convert_ops import convert_block, convert_readout_noise

logger = getLogger(__name__)

Expand Down Expand Up @@ -65,7 +64,16 @@ def circuit(self, circuit: QuantumCircuit) -> ConvertedCircuit:
circuit = transpile(*passes)(circuit)

ops = convert_block(circuit.block, n_qubits=circuit.n_qubits, config=self.config)
native = pyq.QuantumCircuit(circuit.n_qubits, ops)
readout_noise = (
convert_readout_noise(circuit.n_qubits, self.config.noise)
if self.config.noise
else None
)
native = pyq.QuantumCircuit(
circuit.n_qubits,
ops,
readout_noise,
)
return ConvertedCircuit(native=native, abstract=circuit, original=original_circ)

def observable(self, observable: AbstractBlock, n_qubits: int) -> ConvertedObservable:
Expand Down Expand Up @@ -116,6 +124,9 @@ def _batched_expectation(
noise: NoiseHandler | None = None,
endianness: Endianness = Endianness.BIG,
) -> Tensor:
if noise and circuit.native.readout_noise is None:
readout = convert_readout_noise(circuit.abstract.n_qubits, noise)
circuit.native.readout_noise = readout
state = self.run(
circuit,
param_values=param_values,
Expand Down Expand Up @@ -152,6 +163,11 @@ def _looped_expectation(
"Looping expectation does not make sense with batched initial state. "
"Define your initial state with `batch_size=1`"
)

if noise and circuit.native.readout_noise is None:
readout = convert_readout_noise(circuit.abstract.n_qubits, noise)
circuit.native.readout_noise = readout

list_expvals = []
observables = observable if isinstance(observable, list) else [observable]
for vals in to_list_of_dicts(param_values):
Expand Down Expand Up @@ -206,12 +222,13 @@ def sample(
elif state is not None and pyqify_state:
n_qubits = circuit.abstract.n_qubits
state = pyqify(state, n_qubits) if pyqify_state else state
if noise and circuit.native.readout_noise is None:
readout = convert_readout_noise(circuit.abstract.n_qubits, noise)
circuit.native.readout_noise = readout
samples: list[Counter] = circuit.native.sample(
state=state, values=param_values, n_shots=n_shots
)
samples = invert_endianness(samples) if endianness != Endianness.BIG else samples
if noise is not None:
samples = apply_readout_noise(noise=noise, samples=samples)
if mitigation is not None:
logger.warning(
"Mitigation protocol is deprecated. Use qadence-protocols instead.",
Expand Down
4 changes: 4 additions & 0 deletions qadence/backends/pyqtorch/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from qadence.analog import add_background_hamiltonian
from qadence.backend import BackendConfiguration
from qadence.noise import NoiseHandler
from qadence.transpile import (
blockfn_to_circfn,
chain_single_qubit_ops,
Expand Down Expand Up @@ -63,3 +64,6 @@ class Configuration(BackendConfiguration):
Loop over the batch of parameters to only allocate a single wavefunction at any given time.
"""

noise: NoiseHandler | None = None
"""NoiseHandler containing readout noise applied in backend."""
28 changes: 27 additions & 1 deletion qadence/backends/pyqtorch/convert_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,16 @@ def convert_block(
)


def convert_digital_noise(noise: NoiseHandler) -> pyq.noise.NoiseProtocol:
def convert_digital_noise(noise: NoiseHandler) -> pyq.noise.NoiseProtocol | None:
"""Convert the digital noise into pyqtorch NoiseProtocol.
Args:
noise (NoiseHandler): Noise to convert.
Returns:
pyq.noise.NoiseProtocol | None: Pyqtorch native noise protocol
if there are any digital noise protocols.
"""
digital_part = noise.filter(NoiseProtocol.DIGITAL)
if digital_part is None:
return None
Expand All @@ -331,3 +340,20 @@ def convert_digital_noise(noise: NoiseHandler) -> pyq.noise.NoiseProtocol:
for proto, option in zip(digital_part.protocol, digital_part.options)
]
)


def convert_readout_noise(n_qubits: int, noise: NoiseHandler) -> pyq.noise.ReadoutNoise | None:
"""Convert the readout noise into pyqtorch ReadoutNoise.
Args:
n_qubits (int): Number of qubits
noise (NoiseHandler): Noise to convert.
Returns:
pyq.noise.ReadoutNoise | None: Pyqtorch native ReadoutNoise instance
if readout is is noise.
"""
readout_part = noise.filter(NoiseProtocol.READOUT)
if readout_part is None:
return None
return pyq.noise.ReadoutNoise(n_qubits, **readout_part.options[0])
18 changes: 12 additions & 6 deletions qadence/mitigations/readout.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
from scipy.linalg import norm
from scipy.optimize import LinearConstraint, minimize

from qadence.backends.pyqtorch.convert_ops import convert_readout_noise
from qadence.mitigations.protocols import Mitigations
from qadence.noise.protocols import NoiseHandler
from qadence.types import NoiseProtocol, ReadOutOptimization
from qadence.types import ReadOutOptimization


def corrected_probas(p_corr: npt.NDArray, T: npt.NDArray, p_raw: npt.NDArray) -> np.double:
Expand Down Expand Up @@ -88,13 +89,18 @@ def mitigation_minimization(
Returns:
Mitigated counts computed by the algorithm
"""
protocol, options = noise.protocol[-1], noise.options[-1]
if protocol != NoiseProtocol.READOUT:
raise ValueError("Specify a noise source of type NoiseProtocol.READOUT.")
noise_matrices = options.get("noise_matrix", options["confusion_matrices"])
optimization_type = mitigation.options.get("optimization_type", ReadOutOptimization.MLE)

n_qubits = len(list(samples[0].keys())[0])
readout_noise = convert_readout_noise(n_qubits, noise)
if readout_noise is None:
raise ValueError("Specify a noise source of type NoiseProtocol.READOUT.")
n_shots = sum(samples[0].values())
noise_matrices = readout_noise.confusion_matrices
if readout_noise.confusion_matrices.numel() == 0:
readout_noise.create_noise_matrix(n_shots)
noise_matrices = readout_noise.confusion_matrices
optimization_type = mitigation.options.get("optimization_type", ReadOutOptimization.MLE)

corrected_counters: list[Counter] = []

if optimization_type == ReadOutOptimization.CONSTRAINED:
Expand Down
4 changes: 2 additions & 2 deletions qadence/noise/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from .protocols import NoiseHandler, apply_readout_noise
from .protocols import NoiseHandler

# Modules to be automatically added to the qadence namespace
__all__ = ["NoiseHandler", "apply_readout_noise"]
__all__ = ["NoiseHandler"]
45 changes: 3 additions & 42 deletions qadence/noise/protocols.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
from __future__ import annotations

import importlib
from itertools import compress
from typing import Any, Callable, Counter, cast
from typing import Any

from qadence.types import NoiseEnum, NoiseProtocol

PROTOCOL_TO_MODULE = {
"Readout": "qadence.noise.readout",
}


class NoiseHandler:
"""A container for multiple sources of noise.
Expand Down Expand Up @@ -106,16 +101,6 @@ def __repr__(self) -> str:
]
)

def get_noise_fn(self, index_protocol: int) -> Callable:
try:
module = importlib.import_module(PROTOCOL_TO_MODULE[self.protocol[index_protocol]])
except KeyError:
ImportError(
f"The module for the protocol {self.protocol[index_protocol]} is not found."
)
fn = getattr(module, "add_noise")
return cast(Callable, fn)

def append(self, other: NoiseHandler | list[NoiseHandler]) -> None:
"""Append noises.
Expand Down Expand Up @@ -163,8 +148,8 @@ def _from_dict(cls, d: dict | None) -> NoiseHandler | None:
def list(cls) -> list:
return list(filter(lambda el: not el.startswith("__"), dir(cls)))

def filter(self, protocol: NoiseEnum) -> NoiseHandler | None:
is_protocol: list = [isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type]
def filter(self, protocol: NoiseEnum | str) -> NoiseHandler | None:
is_protocol: list = [p == protocol or isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type]
return (
NoiseHandler(
list(compress(self.protocol, is_protocol)),
Expand Down Expand Up @@ -215,27 +200,3 @@ def dephasing(self, *args: Any, **kwargs: Any) -> NoiseHandler:
def readout(self, *args: Any, **kwargs: Any) -> NoiseHandler:
self.append(NoiseHandler(NoiseProtocol.READOUT, *args, **kwargs))
return self


def apply_readout_noise(noise: NoiseHandler, samples: list[Counter]) -> list[Counter]:
"""Apply readout noise to samples if provided.
Args:
noise (NoiseHandler): Noise to apply.
samples (list[Counter]): Samples to alter
Returns:
list[Counter]: Altered samples.
"""
if noise.protocol[-1] == NoiseProtocol.READOUT:
error_fn = noise.get_noise_fn(-1)
# Get the number of qubits from the sample keys.
n_qubits = len(list(samples[0].keys())[0])
# Get the number of shots from the sample values.
n_shots = sum(samples[0].values())
noisy_samples: list = error_fn(
counters=samples, n_qubits=n_qubits, options=noise.options[-1], n_shots=n_shots
)
return noisy_samples
else:
return samples
Loading

0 comments on commit 7f73072

Please sign in to comment.