qadence 1.7.8__py3-none-any.whl → 1.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. qadence/analog/device.py +1 -1
  2. qadence/backend.py +3 -3
  3. qadence/backends/horqrux/backend.py +3 -3
  4. qadence/backends/pulser/backend.py +16 -17
  5. qadence/backends/pulser/convert_ops.py +2 -2
  6. qadence/backends/pyqtorch/backend.py +7 -7
  7. qadence/backends/pyqtorch/convert_ops.py +191 -240
  8. qadence/backends/utils.py +9 -1
  9. qadence/blocks/abstract.py +1 -1
  10. qadence/blocks/embedding.py +21 -11
  11. qadence/blocks/matrix.py +3 -1
  12. qadence/blocks/primitive.py +36 -11
  13. qadence/circuit.py +1 -1
  14. qadence/constructors/__init__.py +2 -1
  15. qadence/constructors/ansatze.py +176 -0
  16. qadence/engines/differentiable_backend.py +3 -3
  17. qadence/engines/jax/differentiable_backend.py +2 -2
  18. qadence/engines/jax/differentiable_expectation.py +2 -2
  19. qadence/engines/torch/differentiable_backend.py +2 -2
  20. qadence/engines/torch/differentiable_expectation.py +2 -2
  21. qadence/execution.py +14 -14
  22. qadence/extensions.py +1 -1
  23. qadence/measurements/shadow.py +4 -5
  24. qadence/measurements/tomography.py +2 -2
  25. qadence/measurements/utils.py +2 -2
  26. qadence/mitigations/analog_zne.py +8 -7
  27. qadence/mitigations/protocols.py +2 -2
  28. qadence/mitigations/readout.py +8 -5
  29. qadence/ml_tools/constructors.py +2 -2
  30. qadence/ml_tools/models.py +7 -7
  31. qadence/ml_tools/printing.py +2 -1
  32. qadence/model.py +5 -5
  33. qadence/noise/__init__.py +2 -2
  34. qadence/noise/protocols.py +216 -29
  35. qadence/operations/control_ops.py +37 -22
  36. qadence/operations/ham_evo.py +1 -0
  37. qadence/operations/parametric.py +32 -10
  38. qadence/operations/primitive.py +61 -29
  39. qadence/overlap.py +0 -6
  40. qadence/parameters.py +3 -2
  41. qadence/transpile/__init__.py +2 -1
  42. qadence/transpile/noise.py +46 -0
  43. qadence/types.py +26 -2
  44. {qadence-1.7.8.dist-info → qadence-1.8.0.dist-info}/METADATA +5 -8
  45. {qadence-1.7.8.dist-info → qadence-1.8.0.dist-info}/RECORD +47 -50
  46. qadence/backends/braket/__init__.py +0 -4
  47. qadence/backends/braket/backend.py +0 -234
  48. qadence/backends/braket/config.py +0 -22
  49. qadence/backends/braket/convert_ops.py +0 -116
  50. {qadence-1.7.8.dist-info → qadence-1.8.0.dist-info}/WHEEL +0 -0
  51. {qadence-1.7.8.dist-info → qadence-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,7 +15,7 @@ from qadence.blocks.analog import ConstantAnalogRotation, InteractionBlock
15
15
  from qadence.circuit import QuantumCircuit
16
16
  from qadence.measurements import Measurements
17
17
  from qadence.mitigations import Mitigations
18
- from qadence.noise import Noise
18
+ from qadence.noise import NoiseHandler
19
19
  from qadence.operations import AnalogRot
20
20
  from qadence.transpile import apply_fn_to_blocks
21
21
  from qadence.utils import Endianness
@@ -44,7 +44,7 @@ def pulse_experiment(
44
44
  circuit: QuantumCircuit,
45
45
  observable: list[AbstractBlock],
46
46
  param_values: dict[str, Tensor],
47
- noise: Noise,
47
+ noise: NoiseHandler,
48
48
  stretches: Tensor,
49
49
  endianness: Endianness,
50
50
  state: Tensor | None = None,
@@ -116,11 +116,12 @@ def noise_level_experiment(
116
116
  circuit: QuantumCircuit,
117
117
  observable: list[AbstractBlock],
118
118
  param_values: dict[str, Tensor],
119
- noise: Noise,
119
+ noise: NoiseHandler,
120
120
  endianness: Endianness,
121
121
  state: Tensor | None = None,
122
122
  ) -> Tensor:
123
- noise_probs = noise.options.get("noise_probs")
123
+ protocol, options = noise.protocol[-1], noise.options[-1]
124
+ noise_probs = options.get("noise_probs")
124
125
  zne_datasets: list = []
125
126
  # Get noisy density matrices.
126
127
  conv_circuit = backend.circuit(circuit)
@@ -152,7 +153,7 @@ def analog_zne(
152
153
  param_values: dict[str, Tensor] = {},
153
154
  state: Tensor | None = None,
154
155
  measurement: Measurements | None = None,
155
- noise: Noise | None = None,
156
+ noise: NoiseHandler | None = None,
156
157
  mitigation: Mitigations | None = None,
157
158
  endianness: Endianness = Endianness.BIG,
158
159
  ) -> Tensor:
@@ -162,7 +163,7 @@ def analog_zne(
162
163
  backend = cast(Backend, backend)
163
164
  noise_model = mitigation.options.get("noise_model", None)
164
165
  if noise_model is None:
165
- KeyError(f"A noise model should be choosen from {Noise.list()}. Got {noise_model}.")
166
+ raise KeyError("A noise model should be specified.")
166
167
  stretches = mitigation.options.get("stretches", None)
167
168
  if stretches is not None:
168
169
  extrapolated_exp_values = pulse_experiment(
@@ -195,7 +196,7 @@ def mitigate(
195
196
  param_values: dict[str, Tensor] = {},
196
197
  state: Tensor | None = None,
197
198
  measurement: Measurements | None = None,
198
- noise: Noise | None = None,
199
+ noise: NoiseHandler | None = None,
199
200
  mitigation: Mitigations | None = None,
200
201
  endianness: Endianness = Endianness.BIG,
201
202
  ) -> Tensor:
@@ -5,7 +5,7 @@ from collections import Counter
5
5
  from dataclasses import dataclass
6
6
  from typing import Callable, cast
7
7
 
8
- from qadence.noise.protocols import Noise
8
+ from qadence.noise.protocols import NoiseHandler
9
9
 
10
10
  PROTOCOL_TO_MODULE = {
11
11
  "readout": "qadence.mitigations.readout",
@@ -45,7 +45,7 @@ class Mitigations:
45
45
 
46
46
 
47
47
  def apply_mitigation(
48
- noise: Noise, mitigation: Mitigations, samples: list[Counter]
48
+ noise: NoiseHandler, mitigation: Mitigations, samples: list[Counter]
49
49
  ) -> list[Counter]:
50
50
  """Apply mitigation to samples."""
51
51
  mitigation_fn = mitigation.get_mitigation_fn()
@@ -11,8 +11,8 @@ from scipy.linalg import norm
11
11
  from scipy.optimize import LinearConstraint, minimize
12
12
 
13
13
  from qadence.mitigations.protocols import Mitigations
14
- from qadence.noise.protocols import Noise
15
- from qadence.types import ReadOutOptimization
14
+ from qadence.noise.protocols import NoiseHandler
15
+ from qadence.types import NoiseProtocol, ReadOutOptimization
16
16
 
17
17
 
18
18
  def corrected_probas(p_corr: npt.NDArray, T: npt.NDArray, p_raw: npt.NDArray) -> np.double:
@@ -69,7 +69,7 @@ def matrix_inv(K: npt.NDArray) -> npt.NDArray:
69
69
 
70
70
 
71
71
  def mitigation_minimization(
72
- noise: Noise,
72
+ noise: NoiseHandler,
73
73
  mitigation: Mitigations,
74
74
  samples: list[Counter],
75
75
  ) -> list[Counter]:
@@ -88,7 +88,10 @@ def mitigation_minimization(
88
88
  Returns:
89
89
  Mitigated counts computed by the algorithm
90
90
  """
91
- noise_matrices = noise.options.get("noise_matrix", noise.options["confusion_matrices"])
91
+ protocol, options = noise.protocol[-1], noise.options[-1]
92
+ if protocol != NoiseProtocol.READOUT:
93
+ raise ValueError("Specify a noise source of type NoiseProtocol.READOUT.")
94
+ noise_matrices = options.get("noise_matrix", options["confusion_matrices"])
92
95
  optimization_type = mitigation.options.get("optimization_type", ReadOutOptimization.MLE)
93
96
  n_qubits = len(list(samples[0].keys())[0])
94
97
  n_shots = sum(samples[0].values())
@@ -156,5 +159,5 @@ def mitigation_minimization(
156
159
  return corrected_counters
157
160
 
158
161
 
159
- def mitigate(noise: Noise, mitigation: Mitigations, samples: list[Counter]) -> list[Counter]:
162
+ def mitigate(noise: NoiseHandler, mitigation: Mitigations, samples: list[Counter]) -> list[Counter]:
160
163
  return mitigation_minimization(noise=noise, mitigation=mitigation, samples=samples)
@@ -21,7 +21,7 @@ from qadence.constructors import (
21
21
  from qadence.constructors.ansatze import hea_digital, hea_sDAQC
22
22
  from qadence.constructors.hamiltonians import ObservableConfig, TDetuning
23
23
  from qadence.measurements import Measurements
24
- from qadence.noise import Noise
24
+ from qadence.noise import NoiseHandler
25
25
  from qadence.operations import CNOT, RX, RY, I, N, Z
26
26
  from qadence.parameters import Parameter
27
27
  from qadence.register import Register
@@ -740,7 +740,7 @@ def build_qnn_from_configs(
740
740
  backend: BackendName = BackendName.PYQTORCH,
741
741
  diff_mode: DiffMode = DiffMode.AD,
742
742
  measurement: Measurements | None = None,
743
- noise: Noise | None = None,
743
+ noise: NoiseHandler | None = None,
744
744
  configuration: BackendConfiguration | dict | None = None,
745
745
  input_diff_mode: InputDiffMode | str = InputDiffMode.AD,
746
746
  ) -> QNN:
@@ -16,7 +16,7 @@ from qadence.measurements import Measurements
16
16
  from qadence.mitigations import Mitigations
17
17
  from qadence.ml_tools.config import AnsatzConfig, FeatureMapConfig
18
18
  from qadence.model import QuantumModel
19
- from qadence.noise import Noise
19
+ from qadence.noise import NoiseHandler
20
20
  from qadence.register import Register
21
21
  from qadence.types import BackendName, DiffMode, Endianness, InputDiffMode, ParamDictType
22
22
 
@@ -139,7 +139,7 @@ class QNN(QuantumModel):
139
139
  backend: BackendName = BackendName.PYQTORCH,
140
140
  diff_mode: DiffMode = DiffMode.AD,
141
141
  measurement: Measurements | None = None,
142
- noise: Noise | None = None,
142
+ noise: NoiseHandler | None = None,
143
143
  configuration: BackendConfiguration | dict | None = None,
144
144
  inputs: list[sympy.Basic | str] | None = None,
145
145
  input_diff_mode: InputDiffMode | str = InputDiffMode.AD,
@@ -218,7 +218,7 @@ class QNN(QuantumModel):
218
218
  backend: BackendName = BackendName.PYQTORCH,
219
219
  diff_mode: DiffMode = DiffMode.AD,
220
220
  measurement: Measurements | None = None,
221
- noise: Noise | None = None,
221
+ noise: NoiseHandler | None = None,
222
222
  configuration: BackendConfiguration | dict | None = None,
223
223
  input_diff_mode: InputDiffMode | str = InputDiffMode.AD,
224
224
  ) -> QNN:
@@ -311,7 +311,7 @@ class QNN(QuantumModel):
311
311
  values: dict[str, Tensor] | Tensor = None,
312
312
  state: Tensor | None = None,
313
313
  measurement: Measurements | None = None,
314
- noise: Noise | None = None,
314
+ noise: NoiseHandler | None = None,
315
315
  endianness: Endianness = Endianness.BIG,
316
316
  ) -> Tensor:
317
317
  """Forward pass of the model.
@@ -360,7 +360,7 @@ class QNN(QuantumModel):
360
360
  values: Tensor | dict[str, Tensor] = {},
361
361
  n_shots: int = 1000,
362
362
  state: Tensor | None = None,
363
- noise: Noise | None = None,
363
+ noise: NoiseHandler | None = None,
364
364
  mitigation: Mitigations | None = None,
365
365
  endianness: Endianness = Endianness.BIG,
366
366
  ) -> list[Counter]:
@@ -379,7 +379,7 @@ class QNN(QuantumModel):
379
379
  observable: list[ConvertedObservable] | ConvertedObservable | None = None,
380
380
  state: Tensor | None = None,
381
381
  measurement: Measurements | None = None,
382
- noise: Noise | None = None,
382
+ noise: NoiseHandler | None = None,
383
383
  mitigation: Mitigations | None = None,
384
384
  endianness: Endianness = Endianness.BIG,
385
385
  ) -> Tensor:
@@ -423,7 +423,7 @@ class QNN(QuantumModel):
423
423
  backend=qm_dict["backend"],
424
424
  diff_mode=qm_dict["diff_mode"],
425
425
  measurement=Measurements._from_dict(qm_dict["measurement"]),
426
- noise=Noise._from_dict(qm_dict["noise"]),
426
+ noise=NoiseHandler._from_dict(qm_dict["noise"]),
427
427
  configuration=config_factory(qm_dict["backend"], qm_dict["backend_configuration"]),
428
428
  inputs=qm_dict["inputs"],
429
429
  )
@@ -27,8 +27,9 @@ def print_metrics(loss: float | None, metrics: dict, iteration: int) -> None:
27
27
 
28
28
 
29
29
  def write_tensorboard(
30
- writer: SummaryWriter, loss: float = None, metrics: dict = {}, iteration: int = 0
30
+ writer: SummaryWriter, loss: float = None, metrics: dict | None = None, iteration: int = 0
31
31
  ) -> None:
32
+ metrics = metrics or dict()
32
33
  if loss is not None:
33
34
  writer.add_scalar("loss", loss, iteration)
34
35
  for key, arg in metrics.items():
qadence/model.py CHANGED
@@ -24,7 +24,7 @@ from qadence.circuit import QuantumCircuit
24
24
  from qadence.engines.differentiable_backend import DifferentiableBackend
25
25
  from qadence.measurements import Measurements
26
26
  from qadence.mitigations import Mitigations
27
- from qadence.noise import Noise
27
+ from qadence.noise import NoiseHandler
28
28
  from qadence.parameters import Parameter
29
29
  from qadence.types import DiffMode, Endianness
30
30
 
@@ -83,7 +83,7 @@ class QuantumModel(nn.Module):
83
83
  backend: BackendName | str = BackendName.PYQTORCH,
84
84
  diff_mode: DiffMode = DiffMode.AD,
85
85
  measurement: Measurements | None = None,
86
- noise: Noise | None = None,
86
+ noise: NoiseHandler | None = None,
87
87
  mitigation: Mitigations | None = None,
88
88
  configuration: BackendConfiguration | dict | None = None,
89
89
  ):
@@ -249,7 +249,7 @@ class QuantumModel(nn.Module):
249
249
  values: dict[str, torch.Tensor] = {},
250
250
  n_shots: int = 1000,
251
251
  state: torch.Tensor | None = None,
252
- noise: Noise | None = None,
252
+ noise: NoiseHandler | None = None,
253
253
  mitigation: Mitigations | None = None,
254
254
  endianness: Endianness = Endianness.BIG,
255
255
  ) -> list[Counter]:
@@ -287,7 +287,7 @@ class QuantumModel(nn.Module):
287
287
  observable: list[ConvertedObservable] | ConvertedObservable | None = None,
288
288
  state: Optional[Tensor] = None,
289
289
  measurement: Measurements | None = None,
290
- noise: Noise | None = None,
290
+ noise: NoiseHandler | None = None,
291
291
  mitigation: Mitigations | None = None,
292
292
  endianness: Endianness = Endianness.BIG,
293
293
  ) -> Tensor:
@@ -415,7 +415,7 @@ class QuantumModel(nn.Module):
415
415
  backend=qm_dict["backend"],
416
416
  diff_mode=qm_dict["diff_mode"],
417
417
  measurement=Measurements._from_dict(qm_dict["measurement"]),
418
- noise=Noise._from_dict(qm_dict["noise"]),
418
+ noise=NoiseHandler._from_dict(qm_dict["noise"]),
419
419
  configuration=config_factory(qm_dict["backend"], qm_dict["backend_configuration"]),
420
420
  )
421
421
 
qadence/noise/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from .protocols import Noise
3
+ from .protocols import NoiseHandler, apply_readout_noise
4
4
 
5
5
  # Modules to be automatically added to the qadence namespace
6
- __all__ = ["Noise"]
6
+ __all__ = ["NoiseHandler", "apply_readout_noise"]
@@ -1,54 +1,241 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import importlib
4
- from dataclasses import dataclass
5
- from typing import Callable, Counter, cast
4
+ from itertools import compress
5
+ from typing import Any, Callable, Counter, cast
6
+
7
+ from qadence.types import NoiseEnum, NoiseProtocol
6
8
 
7
9
  PROTOCOL_TO_MODULE = {
8
- "readout": "qadence.noise.readout",
10
+ "Readout": "qadence.noise.readout",
9
11
  }
10
12
 
11
13
 
12
- @dataclass
13
- class Noise:
14
- DEPHASING = "dephasing"
15
- DEPOLARIZING = "depolarizing"
16
- READOUT = "readout"
14
+ class NoiseHandler:
15
+ """A container for multiple sources of noise.
16
+
17
+ Note `NoiseProtocol.ANALOG` and `NoiseProtocol.DIGITAL` sources cannot be both present.
18
+ Also `NoiseProtocol.READOUT` can only be present once as the last noise sources, and only
19
+ exclusively with `NoiseProtocol.DIGITAL` sources.
20
+
21
+ Args:
22
+ protocol: The protocol(s) applied. To be defined from `NoiseProtocol`.
23
+ options: A list of options defining the protocol.
24
+ For `NoiseProtocol.ANALOG`, options should contain a field `noise_probs`.
25
+ For `NoiseProtocol.DIGITAL`, options should contain a field `error_probability`.
26
+
27
+ Examples:
28
+ ```
29
+ from qadence import NoiseProtocol, NoiseHandler
30
+
31
+ analog_options = {"noise_probs": 0.1}
32
+ digital_options = {"error_probability": 0.1}
33
+ readout_options = {"error_probability": 0.1, "seed": 0}
34
+
35
+ # single noise sources
36
+ analog_noise = NoiseHandler(NoiseProtocol.ANALOG.DEPOLARIZING, analog_options)
37
+ digital_depo_noise = NoiseHandler(NoiseProtocol.DIGITAL.DEPOLARIZING, digital_options)
38
+ readout_noise = NoiseHandler(NoiseProtocol.READOUT, readout_options)
39
+
40
+ # init from multiple sources
41
+ protocols: list = [NoiseProtocol.DIGITAL.DEPOLARIZING, NoiseProtocol.READOUT]
42
+ options: list = [digital_options, readout_noise]
43
+ noise_combination = NoiseHandler(protocols, options)
44
+
45
+ # Appending noise sources
46
+ noise_combination = NoiseHandler(NoiseProtocol.DIGITAL.BITFLIP, digital_options)
47
+ noise_combination.append([digital_depo_noise, readout_noise])
48
+ ```
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ protocol: NoiseEnum | list[NoiseEnum],
54
+ options: dict | list[dict] = dict(),
55
+ ) -> None:
56
+ self.protocol = protocol if isinstance(protocol, list) else [protocol]
57
+ self.options = options if isinstance(options, list) else [options] * len(self.protocol)
58
+ self.verify_all_protocols()
17
59
 
18
- def __init__(self, protocol: str, options: dict = dict()) -> None:
19
- self.protocol: str = protocol
20
- self.options: dict = options
60
+ def _verify_single_protocol(self, protocol: NoiseEnum, option: dict) -> None:
61
+ if protocol != NoiseProtocol.READOUT:
62
+ name_mandatory_option = (
63
+ "noise_probs" if isinstance(protocol, NoiseProtocol.ANALOG) else "error_probability"
64
+ )
65
+ noise_probs = option.get(name_mandatory_option, None)
66
+ if noise_probs is None:
67
+ error_txt = f"A `{name_mandatory_option}` option"
68
+ error_txt += f"should be passed for protocol {protocol}."
69
+ raise KeyError(error_txt)
21
70
 
22
- def get_noise_fn(self) -> Callable:
71
+ def verify_all_protocols(self) -> None:
72
+ """Make sure all protocols are correct in terms and their combination too."""
73
+
74
+ if len(self.protocol) == 0:
75
+ raise ValueError("NoiseHandler should be specified with one valid configuration.")
76
+
77
+ if len(self.protocol) != len(self.options):
78
+ raise ValueError("Specify lists of same length when defining noises.")
79
+
80
+ for protocol, option in zip(self.protocol, self.options):
81
+ self._verify_single_protocol(protocol, option)
82
+
83
+ types = [type(p) for p in self.protocol]
84
+ unique_types = set(types)
85
+ if NoiseProtocol.DIGITAL in unique_types and NoiseProtocol.ANALOG in unique_types:
86
+ raise ValueError("Cannot define a config with both Digital and Analog noises.")
87
+
88
+ if NoiseProtocol.ANALOG in unique_types:
89
+ if NoiseProtocol.READOUT in unique_types:
90
+ raise ValueError("Cannot define a config with both READOUT and Analog noises.")
91
+ if types.count(NoiseProtocol.ANALOG) > 1:
92
+ raise ValueError("Multiple Analog Noises are not supported yet.")
93
+
94
+ if NoiseProtocol.READOUT in self.protocol:
95
+ if (
96
+ self.protocol[-1] != NoiseProtocol.READOUT
97
+ or self.protocol.count(NoiseProtocol.READOUT) > 1
98
+ ):
99
+ raise ValueError("Only define a NoiseHandler with one READOUT as the last Noise.")
100
+
101
+ def __repr__(self) -> str:
102
+ return "\n".join(
103
+ [
104
+ f"Noise({protocol}, {str(option)})"
105
+ for protocol, option in zip(self.protocol, self.options)
106
+ ]
107
+ )
108
+
109
+ def get_noise_fn(self, index_protocol: int) -> Callable:
23
110
  try:
24
- module = importlib.import_module(PROTOCOL_TO_MODULE[self.protocol])
111
+ module = importlib.import_module(PROTOCOL_TO_MODULE[self.protocol[index_protocol]])
25
112
  except KeyError:
26
- ImportError(f"The module corresponding to the protocol {self.protocol} is not found.")
113
+ ImportError(
114
+ f"The module for the protocol {self.protocol[index_protocol]} is not found."
115
+ )
27
116
  fn = getattr(module, "add_noise")
28
117
  return cast(Callable, fn)
29
118
 
119
+ def append(self, other: NoiseHandler | list[NoiseHandler]) -> None:
120
+ """Append noises.
121
+
122
+ Args:
123
+ other (NoiseHandler | list[NoiseHandler]): The noises to add.
124
+ """
125
+ # To avoid overwriting the noise_sources list if an error is raised, make a copy
126
+ other_list = other if isinstance(other, list) else [other]
127
+ protocols = self.protocol[:]
128
+ options = self.options[:]
129
+
130
+ for noise in other_list:
131
+ protocols += noise.protocol
132
+ options += noise.options
133
+
134
+ # init may raise an error
135
+ temp_handler = NoiseHandler(protocols, options)
136
+ # if verify passes, replace protocols and options
137
+ self.protocol = temp_handler.protocol
138
+ self.options = temp_handler.options
139
+
140
+ def __eq__(self, other: object) -> bool:
141
+ if not isinstance(other, NoiseHandler):
142
+ raise TypeError(f"Cant compare {type(self)} to {type(other)}")
143
+ if isinstance(other, type(self)):
144
+ protocols_equal = all([p1 == p2 for p1, p2 in zip(self.protocol, other.protocol)])
145
+ options_equal = all([o1 == o2 for o1, o2 in zip(self.options, other.options)])
146
+ return protocols_equal and options_equal
147
+
148
+ return False
149
+
30
150
  def _to_dict(self) -> dict:
31
- return {"protocol": self.protocol, "options": self.options}
151
+ return {
152
+ "protocol": self.protocol,
153
+ "options": self.options,
154
+ }
32
155
 
33
156
  @classmethod
34
- def _from_dict(cls, d: dict) -> Noise | None:
35
- if d:
36
- return cls(d["protocol"], **d["options"])
157
+ def _from_dict(cls, d: dict | None) -> NoiseHandler | None:
158
+ if d is not None and d.get("protocol", None):
159
+ return cls(d["protocol"], d["options"])
37
160
  return None
38
161
 
39
162
  @classmethod
40
163
  def list(cls) -> list:
41
164
  return list(filter(lambda el: not el.startswith("__"), dir(cls)))
42
165
 
166
+ def filter(self, protocol: NoiseEnum) -> NoiseHandler | None:
167
+ is_protocol: list = [isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type]
168
+ return (
169
+ NoiseHandler(
170
+ list(compress(self.protocol, is_protocol)),
171
+ list(compress(self.options, is_protocol)),
172
+ )
173
+ if len(is_protocol) > 0
174
+ else None
175
+ )
176
+
177
+ def bitflip(self, *args: Any, **kwargs: Any) -> NoiseHandler:
178
+ self.append(NoiseHandler(NoiseProtocol.DIGITAL.BITFLIP, *args, **kwargs))
179
+ return self
180
+
181
+ def phaseflip(self, *args: Any, **kwargs: Any) -> NoiseHandler:
182
+ self.append(NoiseHandler(NoiseProtocol.DIGITAL.PHASEFLIP, *args, **kwargs))
183
+ return self
184
+
185
+ def digital_depolarizing(self, *args: Any, **kwargs: Any) -> NoiseHandler:
186
+ self.append(NoiseHandler(NoiseProtocol.DIGITAL.DEPOLARIZING, *args, **kwargs))
187
+ return self
188
+
189
+ def pauli_channel(self, *args: Any, **kwargs: Any) -> NoiseHandler:
190
+ self.append(NoiseHandler(NoiseProtocol.DIGITAL.PAULI_CHANNEL, *args, **kwargs))
191
+ return self
192
+
193
+ def amplitude_damping(self, *args: Any, **kwargs: Any) -> NoiseHandler:
194
+ self.append(NoiseHandler(NoiseProtocol.DIGITAL.AMPLITUDE_DAMPING, *args, **kwargs))
195
+ return self
196
+
197
+ def phase_damping(self, *args: Any, **kwargs: Any) -> NoiseHandler:
198
+ self.append(NoiseHandler(NoiseProtocol.DIGITAL.PHASE_DAMPING, *args, **kwargs))
199
+ return self
200
+
201
+ def generalized_amplitude_damping(self, *args: Any, **kwargs: Any) -> NoiseHandler:
202
+ self.append(
203
+ NoiseHandler(NoiseProtocol.DIGITAL.GENERALIZED_AMPLITUDE_DAMPING, *args, **kwargs)
204
+ )
205
+ return self
206
+
207
+ def analog_depolarizing(self, *args: Any, **kwargs: Any) -> NoiseHandler:
208
+ self.append(NoiseHandler(NoiseProtocol.ANALOG.DEPOLARIZING, *args, **kwargs))
209
+ return self
210
+
211
+ def dephasing(self, *args: Any, **kwargs: Any) -> NoiseHandler:
212
+ self.append(NoiseHandler(NoiseProtocol.ANALOG.DEPHASING, *args, **kwargs))
213
+ return self
214
+
215
+ def readout(self, *args: Any, **kwargs: Any) -> NoiseHandler:
216
+ self.append(NoiseHandler(NoiseProtocol.READOUT, *args, **kwargs))
217
+ return self
218
+
219
+
220
+ def apply_readout_noise(noise: NoiseHandler, samples: list[Counter]) -> list[Counter]:
221
+ """Apply readout noise to samples if provided.
222
+
223
+ Args:
224
+ noise (NoiseHandler): Noise to apply.
225
+ samples (list[Counter]): Samples to alter
43
226
 
44
- def apply_noise(noise: Noise, samples: list[Counter]) -> list[Counter]:
45
- """Apply noise to samples."""
46
- error_fn = noise.get_noise_fn()
47
- # Get the number of qubits from the sample keys.
48
- n_qubits = len(list(samples[0].keys())[0])
49
- # Get the number of shots from the sample values.
50
- n_shots = sum(samples[0].values())
51
- noisy_samples: list = error_fn(
52
- counters=samples, n_qubits=n_qubits, options=noise.options, n_shots=n_shots
53
- )
54
- return noisy_samples
227
+ Returns:
228
+ list[Counter]: Altered samples.
229
+ """
230
+ if noise.protocol[-1] == NoiseProtocol.READOUT:
231
+ error_fn = noise.get_noise_fn(-1)
232
+ # Get the number of qubits from the sample keys.
233
+ n_qubits = len(list(samples[0].keys())[0])
234
+ # Get the number of shots from the sample values.
235
+ n_shots = sum(samples[0].values())
236
+ noisy_samples: list = error_fn(
237
+ counters=samples, n_qubits=n_qubits, options=noise.options[-1], n_shots=n_shots
238
+ )
239
+ return noisy_samples
240
+ else:
241
+ return samples