qadence 1.7.8__py3-none-any.whl → 1.9.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 (75) hide show
  1. qadence/__init__.py +1 -1
  2. qadence/analog/device.py +1 -1
  3. qadence/analog/parse_analog.py +1 -2
  4. qadence/backend.py +3 -3
  5. qadence/backends/gpsr.py +8 -2
  6. qadence/backends/horqrux/backend.py +3 -3
  7. qadence/backends/pulser/backend.py +21 -38
  8. qadence/backends/pulser/convert_ops.py +2 -2
  9. qadence/backends/pyqtorch/backend.py +85 -10
  10. qadence/backends/pyqtorch/config.py +10 -3
  11. qadence/backends/pyqtorch/convert_ops.py +245 -233
  12. qadence/backends/utils.py +9 -1
  13. qadence/blocks/abstract.py +1 -1
  14. qadence/blocks/embedding.py +21 -11
  15. qadence/blocks/matrix.py +3 -1
  16. qadence/blocks/primitive.py +37 -11
  17. qadence/circuit.py +1 -1
  18. qadence/constructors/__init__.py +2 -1
  19. qadence/constructors/ansatze.py +176 -0
  20. qadence/engines/differentiable_backend.py +3 -3
  21. qadence/engines/jax/differentiable_backend.py +2 -2
  22. qadence/engines/jax/differentiable_expectation.py +2 -2
  23. qadence/engines/torch/differentiable_backend.py +2 -2
  24. qadence/engines/torch/differentiable_expectation.py +2 -2
  25. qadence/execution.py +14 -16
  26. qadence/extensions.py +1 -1
  27. qadence/log_config.yaml +10 -0
  28. qadence/measurements/shadow.py +101 -133
  29. qadence/measurements/tomography.py +2 -2
  30. qadence/measurements/utils.py +4 -4
  31. qadence/mitigations/analog_zne.py +8 -7
  32. qadence/mitigations/protocols.py +2 -2
  33. qadence/mitigations/readout.py +14 -5
  34. qadence/ml_tools/__init__.py +4 -8
  35. qadence/ml_tools/callbacks/__init__.py +30 -0
  36. qadence/ml_tools/callbacks/callback.py +451 -0
  37. qadence/ml_tools/callbacks/callbackmanager.py +214 -0
  38. qadence/ml_tools/{saveload.py → callbacks/saveload.py} +11 -11
  39. qadence/ml_tools/callbacks/writer_registry.py +430 -0
  40. qadence/ml_tools/config.py +132 -258
  41. qadence/ml_tools/constructors.py +2 -2
  42. qadence/ml_tools/data.py +7 -3
  43. qadence/ml_tools/loss/__init__.py +10 -0
  44. qadence/ml_tools/loss/loss.py +87 -0
  45. qadence/ml_tools/models.py +7 -7
  46. qadence/ml_tools/optimize_step.py +45 -10
  47. qadence/ml_tools/stages.py +46 -0
  48. qadence/ml_tools/train_utils/__init__.py +7 -0
  49. qadence/ml_tools/train_utils/base_trainer.py +548 -0
  50. qadence/ml_tools/train_utils/config_manager.py +184 -0
  51. qadence/ml_tools/trainer.py +692 -0
  52. qadence/model.py +6 -6
  53. qadence/noise/__init__.py +2 -2
  54. qadence/noise/protocols.py +188 -36
  55. qadence/operations/control_ops.py +37 -22
  56. qadence/operations/ham_evo.py +88 -26
  57. qadence/operations/parametric.py +32 -10
  58. qadence/operations/primitive.py +61 -29
  59. qadence/overlap.py +0 -6
  60. qadence/parameters.py +3 -2
  61. qadence/transpile/__init__.py +2 -1
  62. qadence/transpile/noise.py +53 -0
  63. qadence/types.py +39 -3
  64. {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/METADATA +5 -9
  65. {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/RECORD +67 -63
  66. {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/WHEEL +1 -1
  67. qadence/backends/braket/__init__.py +0 -4
  68. qadence/backends/braket/backend.py +0 -234
  69. qadence/backends/braket/config.py +0 -22
  70. qadence/backends/braket/convert_ops.py +0 -116
  71. qadence/ml_tools/printing.py +0 -153
  72. qadence/ml_tools/train_grad.py +0 -395
  73. qadence/ml_tools/train_no_grad.py +0 -199
  74. qadence/noise/readout.py +0 -218
  75. {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,40 +1,37 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections import Counter
4
- from functools import reduce
5
-
6
3
  import numpy as np
7
4
  import torch
8
5
  from torch import Tensor
9
6
 
10
7
  from qadence.backend import Backend
11
8
  from qadence.backends.pyqtorch import Backend as PyQBackend
12
- from qadence.blocks import AbstractBlock, chain, kron
13
- from qadence.blocks.block_to_tensor import HMAT, IMAT, SDAGMAT, ZMAT, block_to_tensor
9
+ from qadence.blocks import AbstractBlock, KronBlock, chain, kron
10
+ from qadence.blocks.block_to_tensor import HMAT, IMAT, SDAGMAT
14
11
  from qadence.blocks.composite import CompositeBlock
15
12
  from qadence.blocks.primitive import PrimitiveBlock
16
13
  from qadence.blocks.utils import get_pauli_blocks, unroll_block_with_scaling
17
14
  from qadence.circuit import QuantumCircuit
18
15
  from qadence.engines.differentiable_backend import DifferentiableBackend
19
- from qadence.noise import Noise
20
- from qadence.operations import X, Y, Z
16
+ from qadence.measurements.utils import get_qubit_indices_for_op
17
+ from qadence.noise import NoiseHandler
18
+ from qadence.operations import H, I, SDagger, X, Y, Z
21
19
  from qadence.types import Endianness
22
- from qadence.utils import P0_MATRIX, P1_MATRIX
23
20
 
24
21
  pauli_gates = [X, Y, Z]
25
-
22
+ pauli_rotations = [
23
+ lambda index: H(index),
24
+ lambda index: SDagger(index) * H(index),
25
+ lambda index: None,
26
+ ]
26
27
 
27
28
  UNITARY_TENSOR = [
28
- ZMAT @ HMAT,
29
- SDAGMAT.squeeze(dim=0) @ HMAT,
29
+ HMAT,
30
+ HMAT @ SDAGMAT,
30
31
  IMAT,
31
32
  ]
32
33
 
33
34
 
34
- def identity(n_qubits: int) -> Tensor:
35
- return torch.eye(2**n_qubits, dtype=torch.complex128)
36
-
37
-
38
35
  def _max_observable_weight(observable: AbstractBlock) -> int:
39
36
  """
40
37
  Get the maximal weight for the given observable.
@@ -88,27 +85,40 @@ def number_of_samples(
88
85
  return N, K
89
86
 
90
87
 
91
- def local_shadow(sample: Counter, unitary_ids: list) -> Tensor:
88
+ def nested_operator_indexing(
89
+ idx_array: np.ndarray,
90
+ ) -> list:
91
+ """Obtain the list of rotation operators from indices.
92
+
93
+ Args:
94
+ idx_array (np.ndarray): Indices for obtaining the operators.
95
+
96
+ Returns:
97
+ list: Map of rotations.
92
98
  """
93
- Compute local shadow by inverting the quantum channel for each projector state.
99
+ if idx_array.ndim == 1:
100
+ return [pauli_rotations[int(ind_pauli)](i) for i, ind_pauli in enumerate(idx_array)] # type: ignore[abstract]
101
+ return [nested_operator_indexing(sub_array) for sub_array in idx_array]
102
+
103
+
104
+ def kron_if_non_empty(list_operations: list) -> KronBlock | None:
105
+ filtered_op: list = list(filter(None, list_operations))
106
+ return kron(*filtered_op) if len(filtered_op) > 0 else None
94
107
 
95
- See https://arxiv.org/pdf/2002.08953.pdf
96
- Supplementary Material 1 and Eqs. (S17,S44).
97
108
 
98
- Expects a sample bitstring in ILO.
109
+ def extract_operators(unitary_ids: np.ndarray, n_qubits: int) -> list:
110
+ """Sample `shadow_size` rotations of `n_qubits`.
111
+
112
+ Args:
113
+ unitary_ids (np.ndarray): Indices for obtaining the operators.
114
+ n_qubits (int): Number of qubits
115
+ Returns:
116
+ list: Pauli strings.
99
117
  """
100
- bitstring = list(sample.keys())[0]
101
- local_density_matrices = []
102
- for bit, unitary_id in zip(bitstring, unitary_ids):
103
- proj_mat = P0_MATRIX if bit == "0" else P1_MATRIX
104
- unitary_tensor = UNITARY_TENSOR[unitary_id].squeeze(dim=0)
105
- local_density_matrices.append(
106
- 3 * (unitary_tensor.adjoint() @ proj_mat @ unitary_tensor) - identity(1)
107
- )
108
- if len(local_density_matrices) == 1:
109
- return local_density_matrices[0]
110
- else:
111
- return reduce(torch.kron, local_density_matrices)
118
+ operations = nested_operator_indexing(unitary_ids)
119
+ if n_qubits > 1:
120
+ operations = [kron_if_non_empty(ops) for ops in operations]
121
+ return operations
112
122
 
113
123
 
114
124
  def classical_shadow(
@@ -117,28 +127,23 @@ def classical_shadow(
117
127
  param_values: dict,
118
128
  state: Tensor | None = None,
119
129
  backend: Backend | DifferentiableBackend = PyQBackend(),
120
- # FIXME: Changed below from Little to Big, double-check when Roland is back
121
- noise: Noise | None = None,
130
+ noise: NoiseHandler | None = None,
122
131
  endianness: Endianness = Endianness.BIG,
123
- ) -> list:
124
- shadow: list = []
125
- # TODO: Parallelize embarrassingly parallel loop.
126
- for _ in range(shadow_size):
127
- unitary_ids = np.random.randint(0, 3, size=(1, circuit.n_qubits))[0]
128
- random_unitary = [
129
- pauli_gates[unitary_ids[qubit]](qubit) for qubit in range(circuit.n_qubits)
130
- ]
131
- if len(random_unitary) == 1:
132
- random_unitary_block = random_unitary[0]
132
+ ) -> tuple[np.ndarray, list[Tensor]]:
133
+ unitary_ids = np.random.randint(0, 3, size=(shadow_size, circuit.n_qubits))
134
+ shadow: list = list()
135
+ all_rotations = extract_operators(unitary_ids, circuit.n_qubits)
136
+
137
+ for i in range(shadow_size):
138
+ if all_rotations[i]:
139
+ rotated_circuit = QuantumCircuit(
140
+ circuit.register, chain(circuit.block, all_rotations[i])
141
+ )
133
142
  else:
134
- random_unitary_block = kron(*random_unitary)
135
- rotated_circuit = QuantumCircuit(
136
- circuit.n_qubits,
137
- chain(circuit.block, random_unitary_block),
138
- )
143
+ rotated_circuit = circuit
139
144
  # Reverse endianness to get sample bitstrings in ILO.
140
145
  conv_circ = backend.circuit(rotated_circuit)
141
- samples = backend.sample(
146
+ batch_samples = backend.sample(
142
147
  circuit=conv_circ,
143
148
  param_values=param_values,
144
149
  n_shots=1,
@@ -146,97 +151,61 @@ def classical_shadow(
146
151
  noise=noise,
147
152
  endianness=endianness,
148
153
  )
149
- batched_shadow = []
150
- for batch in samples:
151
- batched_shadow.append(local_shadow(sample=batch, unitary_ids=unitary_ids))
152
- shadow.append(batched_shadow)
153
-
154
- # Reshape the shadow by batches of samples instead of samples of batches.
155
- # FIXME: Improve performance.
156
- return [list(s) for s in zip(*shadow)]
157
-
158
-
159
- def reconstruct_state(shadow: list) -> Tensor:
160
- """Reconstruct the state density matrix for the given shadow."""
161
- return reduce(torch.add, shadow) / len(shadow)
162
-
163
-
164
- def compute_traces(
165
- qubit_support: tuple,
166
- N: int,
167
- K: int,
168
- shadow: list,
169
- observable: AbstractBlock,
170
- endianness: Endianness = Endianness.BIG,
171
- ) -> list:
172
- floor = int(np.floor(N / K))
173
- traces = []
174
- # TODO: Parallelize embarrassingly parallel loop.
175
- for k in range(K):
176
- reconstructed_state = reconstruct_state(shadow=shadow[k * floor : (k + 1) * floor])
177
- # Reshape the observable matrix to fit the density matrix dimensions
178
- # by filling indentites.
179
- # Please note the endianness is also flipped to get results in LE.
180
- # FIXME: Changed below from Little to Big, double-check when Roland is back
181
- # FIXME: Correct these comments.
182
- trace = (
183
- (
184
- block_to_tensor(
185
- block=observable,
186
- qubit_support=qubit_support,
187
- endianness=Endianness.BIG,
188
- ).squeeze(dim=0)
189
- @ reconstructed_state
190
- )
191
- .trace()
192
- .real
193
- )
194
- traces.append(trace)
195
- return traces
154
+ shadow.append(batch_samples)
155
+ bitstrings = list()
156
+ batchsize = len(batch_samples)
157
+ for b in range(batchsize):
158
+ bitstrings.append([list(batch[b].keys())[0] for batch in shadow])
159
+ bitstrings_torch = [
160
+ 1 - 2 * torch.stack([torch.tensor([int(b_i) for b_i in sample]) for sample in batch])
161
+ for batch in bitstrings
162
+ ]
163
+ return unitary_ids, bitstrings_torch
196
164
 
197
165
 
198
166
  def estimators(
199
- qubit_support: tuple,
200
167
  N: int,
201
168
  K: int,
202
- shadow: list,
169
+ unitary_shadow_ids: np.ndarray,
170
+ shadow_samples: Tensor,
203
171
  observable: AbstractBlock,
204
- endianness: Endianness = Endianness.BIG,
205
172
  ) -> Tensor:
206
173
  """
207
- Return estimators (traces of observable times mean density matrix).
208
-
209
- for K equally-sized shadow partitions.
174
+ Return trace estimators from the samples for K equally-sized shadow partitions.
210
175
 
211
176
  See https://arxiv.org/pdf/2002.08953.pdf
212
177
  Algorithm 1.
213
178
  """
214
- # If there is no Pauli-Z operator in the observable,
215
- # the sample can't "hit" that measurement.
179
+
180
+ obs_qubit_support = observable.qubit_support
216
181
  if isinstance(observable, PrimitiveBlock):
217
- if type(observable) == Z:
218
- traces = compute_traces(
219
- qubit_support=qubit_support,
220
- N=N,
221
- K=K,
222
- shadow=shadow,
223
- observable=observable,
224
- endianness=endianness,
225
- )
226
- else:
227
- traces = [torch.tensor(0.0)]
182
+ if isinstance(observable, I):
183
+ return torch.tensor(1.0, dtype=torch.get_default_dtype())
184
+ obs_to_pauli_index = [pauli_gates.index(type(observable))]
185
+
228
186
  elif isinstance(observable, CompositeBlock):
229
- if Z in observable:
230
- traces = compute_traces(
231
- qubit_support=qubit_support,
232
- N=N,
233
- K=K,
234
- shadow=shadow,
235
- observable=observable,
236
- endianness=endianness,
237
- )
187
+ obs_to_pauli_index = [
188
+ pauli_gates.index(type(p)) for p in observable.blocks if not isinstance(p, I) # type: ignore[arg-type]
189
+ ]
190
+ ind_I = set(get_qubit_indices_for_op((observable, 1.0), I(0)))
191
+ obs_qubit_support = tuple([ind for ind in observable.qubit_support if ind not in ind_I])
192
+
193
+ floor = int(np.floor(N / K))
194
+ traces = []
195
+ for k in range(K):
196
+ indices_match = np.all(
197
+ unitary_shadow_ids[k * floor : (k + 1) * floor, obs_qubit_support]
198
+ == obs_to_pauli_index,
199
+ axis=1,
200
+ )
201
+ if indices_match.sum() > 0:
202
+ trace = torch.prod(
203
+ shadow_samples[k * floor : (k + 1) * floor][indices_match][:, obs_qubit_support],
204
+ axis=-1,
205
+ ).sum() / sum(indices_match)
206
+ traces.append(trace)
238
207
  else:
239
- traces = [torch.tensor(0.0)]
208
+ traces.append(torch.tensor(0.0))
240
209
  return torch.tensor(traces, dtype=torch.get_default_dtype())
241
210
 
242
211
 
@@ -249,7 +218,7 @@ def estimations(
249
218
  confidence: float = 0.1,
250
219
  state: Tensor | None = None,
251
220
  backend: Backend | DifferentiableBackend = PyQBackend(),
252
- noise: Noise | None = None,
221
+ noise: NoiseHandler | None = None,
253
222
  endianness: Endianness = Endianness.BIG,
254
223
  ) -> Tensor:
255
224
  """Compute expectation values for all local observables using median of means."""
@@ -259,7 +228,7 @@ def estimations(
259
228
  N, K = number_of_samples(observables=observables, accuracy=accuracy, confidence=confidence)
260
229
  if shadow_size is not None:
261
230
  N = shadow_size
262
- shadow = classical_shadow(
231
+ unitaries_ids, batch_shadow_samples = classical_shadow(
263
232
  shadow_size=N,
264
233
  circuit=circuit,
265
234
  param_values=param_values,
@@ -272,18 +241,17 @@ def estimations(
272
241
  for observable in observables:
273
242
  pauli_decomposition = unroll_block_with_scaling(observable)
274
243
  batch_estimations = []
275
- for batch in shadow:
244
+ for batch in batch_shadow_samples:
276
245
  pauli_term_estimations = []
277
246
  for pauli_term in pauli_decomposition:
278
247
  # Get the estimators for the current Pauli term.
279
248
  # This is a tensor<float> of size K.
280
249
  estimation = estimators(
281
- qubit_support=circuit.block.qubit_support,
282
250
  N=N,
283
251
  K=K,
284
- shadow=batch,
252
+ unitary_shadow_ids=unitaries_ids,
253
+ shadow_samples=batch,
285
254
  observable=pauli_term[0],
286
- endianness=endianness,
287
255
  )
288
256
  # Compute the median of means for the current Pauli term.
289
257
  # Weigh the median by the Pauli term scaling.
@@ -302,7 +270,7 @@ def compute_expectation(
302
270
  options: dict,
303
271
  state: Tensor | None = None,
304
272
  backend: Backend | DifferentiableBackend = PyQBackend(),
305
- noise: Noise | None = None,
273
+ noise: NoiseHandler | None = None,
306
274
  endianness: Endianness = Endianness.BIG,
307
275
  ) -> Tensor:
308
276
  """
@@ -10,7 +10,7 @@ from qadence.blocks.utils import unroll_block_with_scaling
10
10
  from qadence.circuit import QuantumCircuit
11
11
  from qadence.engines.differentiable_backend import DifferentiableBackend
12
12
  from qadence.measurements.utils import iterate_pauli_decomposition
13
- from qadence.noise import Noise
13
+ from qadence.noise import NoiseHandler
14
14
  from qadence.utils import Endianness
15
15
 
16
16
 
@@ -21,7 +21,7 @@ def compute_expectation(
21
21
  options: dict,
22
22
  state: Tensor | None = None,
23
23
  backend: Backend | DifferentiableBackend = PyQBackend(),
24
- noise: Noise | None = None,
24
+ noise: NoiseHandler | None = None,
25
25
  endianness: Endianness = Endianness.BIG,
26
26
  ) -> Tensor:
27
27
  """Basic tomography protocol with rotations.
@@ -14,8 +14,8 @@ from qadence.backends.pyqtorch import Backend as PyQBackend
14
14
  from qadence.blocks import AbstractBlock, PrimitiveBlock, chain
15
15
  from qadence.circuit import QuantumCircuit
16
16
  from qadence.engines.differentiable_backend import DifferentiableBackend
17
- from qadence.noise import Noise
18
- from qadence.operations import H, SDagger, X, Y, Z
17
+ from qadence.noise import NoiseHandler
18
+ from qadence.operations import H, I, SDagger, X, Y
19
19
  from qadence.parameters import evaluate
20
20
  from qadence.utils import Endianness
21
21
 
@@ -113,7 +113,7 @@ def rotate(circuit: QuantumCircuit, pauli_term: Tuple[AbstractBlock, Basic]) ->
113
113
 
114
114
  rotations = []
115
115
 
116
- for op, gate in [(X(0), Z), (Y(0), SDagger)]:
116
+ for op, gate in [(X(0), I), (Y(0), SDagger)]:
117
117
  qubit_indices = get_qubit_indices_for_op(pauli_term, op=op)
118
118
  for index in qubit_indices:
119
119
  rotations.append(gate(index) * H(index))
@@ -128,7 +128,7 @@ def iterate_pauli_decomposition(
128
128
  n_shots: int,
129
129
  state: Tensor | None = None,
130
130
  backend: Backend | DifferentiableBackend = PyQBackend(),
131
- noise: Noise | None = None,
131
+ noise: NoiseHandler | None = None,
132
132
  endianness: Endianness = Endianness.BIG,
133
133
  ) -> Tensor:
134
134
  """Estimate total expectation value by averaging all Pauli terms.
@@ -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()
@@ -10,8 +10,9 @@ from numpy.linalg import inv, matrix_rank, pinv
10
10
  from scipy.linalg import norm
11
11
  from scipy.optimize import LinearConstraint, minimize
12
12
 
13
+ from qadence.backends.pyqtorch.convert_ops import convert_readout_noise
13
14
  from qadence.mitigations.protocols import Mitigations
14
- from qadence.noise.protocols import Noise
15
+ from qadence.noise.protocols import NoiseHandler
15
16
  from qadence.types import ReadOutOptimization
16
17
 
17
18
 
@@ -69,7 +70,7 @@ def matrix_inv(K: npt.NDArray) -> npt.NDArray:
69
70
 
70
71
 
71
72
  def mitigation_minimization(
72
- noise: Noise,
73
+ noise: NoiseHandler,
73
74
  mitigation: Mitigations,
74
75
  samples: list[Counter],
75
76
  ) -> list[Counter]:
@@ -88,10 +89,18 @@ def mitigation_minimization(
88
89
  Returns:
89
90
  Mitigated counts computed by the algorithm
90
91
  """
91
- noise_matrices = noise.options.get("noise_matrix", noise.options["confusion_matrices"])
92
- optimization_type = mitigation.options.get("optimization_type", ReadOutOptimization.MLE)
92
+
93
93
  n_qubits = len(list(samples[0].keys())[0])
94
+ readout_noise = convert_readout_noise(n_qubits, noise)
95
+ if readout_noise is None:
96
+ raise ValueError("Specify a noise source of type NoiseProtocol.READOUT.")
94
97
  n_shots = sum(samples[0].values())
98
+ noise_matrices = readout_noise.confusion_matrix
99
+ if noise_matrices.numel() == 0:
100
+ readout_noise.create_noise_matrix(n_shots)
101
+ noise_matrices = readout_noise.confusion_matrix
102
+ optimization_type = mitigation.options.get("optimization_type", ReadOutOptimization.MLE)
103
+
95
104
  corrected_counters: list[Counter] = []
96
105
 
97
106
  if optimization_type == ReadOutOptimization.CONSTRAINED:
@@ -156,5 +165,5 @@ def mitigation_minimization(
156
165
  return corrected_counters
157
166
 
158
167
 
159
- def mitigate(noise: Noise, mitigation: Mitigations, samples: list[Counter]) -> list[Counter]:
168
+ def mitigate(noise: NoiseHandler, mitigation: Mitigations, samples: list[Counter]) -> list[Counter]:
160
169
  return mitigation_minimization(noise=noise, mitigation=mitigation, samples=samples)
@@ -1,16 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
- from .config import AnsatzConfig, Callback, FeatureMapConfig, TrainConfig
3
+ from .callbacks.saveload import load_checkpoint, load_model, write_checkpoint
4
+ from .config import AnsatzConfig, FeatureMapConfig, TrainConfig
4
5
  from .constructors import create_ansatz, create_fm_blocks, observable_from_config
5
6
  from .data import DictDataLoader, InfiniteTensorDataset, OptimizeResult, to_dataloader
6
7
  from .models import QNN
7
8
  from .optimize_step import optimize_step as default_optimize_step
8
9
  from .parameters import get_parameters, num_parameters, set_parameters
9
- from .printing import print_metrics, write_tensorboard
10
- from .saveload import load_checkpoint, load_model, write_checkpoint
11
10
  from .tensors import numpy_to_tensor, promote_to, promote_to_tensor
12
- from .train_grad import train as train_with_grad
13
- from .train_no_grad import train as train_gradient_free
11
+ from .trainer import Trainer
14
12
 
15
13
  # Modules to be automatically added to the qadence namespace
16
14
  __all__ = [
@@ -24,8 +22,6 @@ __all__ = [
24
22
  "QNN",
25
23
  "TrainConfig",
26
24
  "OptimizeResult",
27
- "Callback",
28
- "train_with_grad",
29
- "train_gradient_free",
25
+ "Trainer",
30
26
  "write_checkpoint",
31
27
  ]
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from .callback import (
4
+ Callback,
5
+ LoadCheckpoint,
6
+ LogHyperparameters,
7
+ LogModelTracker,
8
+ PlotMetrics,
9
+ PrintMetrics,
10
+ SaveBestCheckpoint,
11
+ SaveCheckpoint,
12
+ WriteMetrics,
13
+ )
14
+ from .callbackmanager import CallbacksManager
15
+ from .writer_registry import get_writer
16
+
17
+ # Modules to be automatically added to the qadence.ml_tools.callbacks namespace
18
+ __all__ = [
19
+ "CallbacksManager",
20
+ "Callback",
21
+ "LoadCheckpoint",
22
+ "LogHyperparameters",
23
+ "LogModelTracker",
24
+ "PlotMetrics",
25
+ "PrintMetrics",
26
+ "SaveBestCheckpoint",
27
+ "SaveCheckpoint",
28
+ "WriteMetrics",
29
+ "get_writer",
30
+ ]