qadence 1.6.3__py3-none-any.whl → 1.7.1__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 (39) hide show
  1. qadence/__init__.py +2 -2
  2. qadence/backends/api.py +47 -60
  3. qadence/backends/gpsr.py +1 -0
  4. qadence/backends/pyqtorch/backend.py +1 -2
  5. qadence/backends/pyqtorch/config.py +5 -0
  6. qadence/backends/pyqtorch/convert_ops.py +83 -10
  7. qadence/backends/utils.py +62 -7
  8. qadence/blocks/abstract.py +7 -0
  9. qadence/blocks/embedding.py +17 -12
  10. qadence/blocks/matrix.py +1 -1
  11. qadence/blocks/primitive.py +1 -1
  12. qadence/constructors/__init__.py +2 -0
  13. qadence/constructors/hamiltonians.py +38 -1
  14. qadence/draw/utils.py +1 -1
  15. qadence/execution.py +11 -3
  16. qadence/extensions.py +62 -36
  17. qadence/ml_tools/__init__.py +11 -3
  18. qadence/ml_tools/config.py +283 -2
  19. qadence/ml_tools/constructors.py +796 -0
  20. qadence/ml_tools/models.py +373 -251
  21. qadence/ml_tools/printing.py +5 -2
  22. qadence/ml_tools/saveload.py +42 -18
  23. qadence/ml_tools/train_grad.py +48 -14
  24. qadence/ml_tools/utils.py +2 -8
  25. qadence/{models/quantum_model.py → model.py} +178 -10
  26. qadence/operations/ham_evo.py +10 -0
  27. qadence/overlap.py +1 -1
  28. qadence/parameters.py +10 -1
  29. qadence/register.py +98 -22
  30. qadence/serialization.py +6 -6
  31. qadence/types.py +44 -0
  32. qadence/utils.py +2 -8
  33. {qadence-1.6.3.dist-info → qadence-1.7.1.dist-info}/METADATA +7 -6
  34. {qadence-1.6.3.dist-info → qadence-1.7.1.dist-info}/RECORD +36 -38
  35. {qadence-1.6.3.dist-info → qadence-1.7.1.dist-info}/WHEEL +1 -1
  36. qadence/finitediff.py +0 -47
  37. qadence/models/__init__.py +0 -7
  38. qadence/models/qnn.py +0 -265
  39. {qadence-1.6.3.dist-info → qadence-1.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,320 +1,442 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections import Counter
3
4
  from logging import getLogger
4
- from typing import Any, Counter, List
5
+ from typing import Any, Callable
5
6
 
6
- import numpy as np
7
+ import sympy
7
8
  import torch
8
- from torch import Tensor
9
- from torch.nn import Parameter as TorchParam
9
+ from torch import Tensor, nn
10
10
 
11
- from qadence.backend import ConvertedObservable
11
+ from qadence.backend import BackendConfiguration, ConvertedObservable
12
+ from qadence.backends.api import config_factory
13
+ from qadence.blocks.abstract import AbstractBlock
14
+ from qadence.circuit import QuantumCircuit
12
15
  from qadence.measurements import Measurements
13
- from qadence.ml_tools import promote_to_tensor
14
- from qadence.models import QNN, QuantumModel
16
+ from qadence.mitigations import Mitigations
17
+ from qadence.ml_tools.config import AnsatzConfig, FeatureMapConfig
18
+ from qadence.model import QuantumModel
15
19
  from qadence.noise import Noise
16
- from qadence.utils import Endianness
20
+ from qadence.register import Register
21
+ from qadence.types import BackendName, DiffMode, Endianness, InputDiffMode, ParamDictType
17
22
 
18
23
  logger = getLogger(__name__)
19
24
 
20
25
 
21
- def _set_fixed_operation(
22
- dim: int,
23
- x: float | np.ndarray | Tensor | None = None,
24
- operation_name: str = "scale",
25
- ) -> Tensor:
26
- dim = dim if dim > 0 else 1
27
- if x is None:
28
- if operation_name == "shift":
29
- x = torch.zeros(dim)
30
- elif operation_name == "scale":
31
- x = torch.ones(dim)
32
- else:
33
- NotImplementedError
34
- res = promote_to_tensor(x, requires_grad=False).squeeze(0)
35
- assert (
36
- res.numel() == dim
37
- ), f"Number of {operation_name} values is {res.numel()}\
38
- and does not match number of dimensions = {dim}."
39
- return res
26
+ def _torch_derivative(
27
+ ufa: Callable, x: torch.Tensor, derivative_indices: tuple[int, ...]
28
+ ) -> torch.Tensor:
29
+ y = ufa(x)
30
+ for idx in derivative_indices:
31
+ out = torch.autograd.grad(y, x, torch.ones_like(y), create_graph=True)[0]
32
+ y = out[:, idx]
33
+ return y.reshape(-1, 1)
40
34
 
41
35
 
42
- class TransformedModule(torch.nn.Module):
43
- """
44
- This class accepts a torch.nn.Module or a QuantumModel/QNN.
45
-
46
- Wraps it with either non-trainble or trainable scaling and shifting parameters
47
- for both input and output. When given a torch.nn.Module,
48
- in_features and out_features need to be passed.
49
-
50
- Args:
51
- model: The original model to transform.
52
- in_features: The number of input dimensions of the model.
53
- out_features: The number of output dimensions of the model.
54
- input_scaling: The rescaling factor for the model input. Defaults to None.
55
- input_shifting: The translation factor for the model input. Defaults to None.
56
- output_scaling: The rescaling factor for the model output. Defaults to None.
57
- output_shifting: The translation factor for the model output. Defaults to None.
58
-
59
- Example:
36
+ def derivative(ufa: torch.nn.Module, x: Tensor, derivative_indices: tuple[int, ...]) -> Tensor:
37
+ """Compute derivatives w.r.t.
38
+
39
+ inputs of a UFA with a single output. The
40
+ `derivative_indices` specify which derivative(s) are computed. E.g.
41
+ `derivative_indices=(1,2)` would compute the a second order derivative w.r.t
42
+ to the indices `1` and `2` of the input tensor.
43
+
44
+ Arguments:
45
+ ufa: The model for which we want to compute the derivative.
46
+ x (Tensor): (batch_size, input_size) input tensor.
47
+ derivative_indices (tuple): Define which derivatives to compute.
48
+
49
+ Examples:
50
+ If we create a UFA with three inputs and denote the first, second, and third
51
+ input with `x`, `y`, and `z` we can compute the following derivatives w.r.t
52
+ to those inputs:
53
+ ```py exec="on" source="material-block"
54
+ import torch
55
+ from qadence.ml_tools.models import derivative, QNN
56
+ from qadence.ml_tools.config import FeatureMapConfig, AnsatzConfig
57
+ from qadence.constructors.hamiltonians import ObservableConfig
58
+ from qadence.operations import Z
59
+
60
+ fm_config = FeatureMapConfig(num_features=3, inputs=["x", "y", "z"])
61
+ ansatz_config = AnsatzConfig()
62
+ obs_config = ObservableConfig(detuning=Z)
63
+
64
+ f = QNN.from_configs(
65
+ register=3, obs_config=obs_config, fm_config=fm_config, ansatz_config=ansatz_config,
66
+ )
67
+ inputs = torch.rand(5,3,requires_grad=True)
68
+
69
+ # df_dx
70
+ derivative(f, inputs, (0,))
71
+
72
+ # d2f_dydz
73
+ derivative(f, inputs, (1,2))
74
+
75
+ # d3fdy2dx
76
+ derivative(f, inputs, (1,1,0))
60
77
  ```
78
+ """
79
+ assert ufa.out_features == 1, "Can only call `derivative` on models with 1D output."
80
+ return ufa._derivative(x, derivative_indices)
81
+
82
+
83
+ def format_to_dict_fn(
84
+ inputs: list[sympy.Symbol | str] = [],
85
+ ) -> Callable[[Tensor | ParamDictType], ParamDictType]:
86
+ """Format an input tensor into the format required by the forward pass.
87
+
88
+ The tensor is assumed to have dimensions: n_batches x in_features where in_features
89
+ corresponds to the number of input features of the QNN
90
+ """
91
+ in_features = len(inputs)
92
+
93
+ def tensor_to_dict(values: Tensor | ParamDictType) -> ParamDictType:
94
+ if isinstance(values, Tensor):
95
+ values = values.reshape(-1, 1) if len(values.size()) == 1 else values
96
+ if not values.shape[1] == in_features:
97
+ raise ValueError(
98
+ f"Model expects in_features={in_features} but got {values.shape[1]}."
99
+ )
100
+ values = {fparam.name: values[:, inputs.index(fparam)] for fparam in inputs} # type: ignore[union-attr]
101
+ return values
102
+
103
+ return tensor_to_dict
104
+
105
+
106
+ class QNN(QuantumModel):
107
+ """Quantum neural network model for n-dimensional inputs.
108
+
109
+ Examples:
110
+ ```python exec="on" source="material-block" result="json"
61
111
  import torch
62
- from torch.nn import Parameter as TorchParam
63
- from qadence.models import QNN, TransformedModule
64
- from qadence.circuit import QuantumCircuit
65
- from qadence.blocks import chain
66
- from qadence.constructors import hamiltonian_factory, hea
67
- from qadence import Parameter, QuantumCircuit, Z
68
-
69
- n_qubits = 2
70
- phi = Parameter("phi", trainable=False)
71
- fm = chain(*[RY(i, phi) for i in range(n_qubits)])
72
- ansatz = hea(n_qubits=n_qubits, depth=3)
73
- observable = hamiltonian_factory(n_qubits, detuning = Z)
112
+ from qadence import QuantumCircuit, QNN, Z
113
+ from qadence import hea, feature_map, hamiltonian_factory, kron
114
+
115
+ # create the circuit
116
+ n_qubits, depth = 2, 4
117
+ fm = kron(
118
+ feature_map(1, support=(0,), param="x"),
119
+ feature_map(1, support=(1,), param="y")
120
+ )
121
+ ansatz = hea(n_qubits=n_qubits, depth=depth)
74
122
  circuit = QuantumCircuit(n_qubits, fm, ansatz)
123
+ obs_base = hamiltonian_factory(n_qubits, detuning=Z)
75
124
 
76
- model = QNN(circuit, observable, backend="pyqtorch", diff_mode="ad")
77
- batch_size = 1
78
- input_values = {"phi": torch.rand(batch_size, requires_grad=True)}
79
- pred = model(input_values)
80
- assert not torch.isnan(pred)
81
-
82
- transformed_model = TransformedModule(
83
- model=model,
84
- in_features=None,
85
- out_features=None,
86
- input_scaling=TorchParam(torch.tensor(1.0)),
87
- input_shifting=0.0,
88
- output_scaling=1.0,
89
- output_shifting=TorchParam(torch.tensor(0.0))
90
- )
91
- pred_transformed = transformed_model(input_values)
125
+ # the QNN will yield two outputs
126
+ obs = [2.0 * obs_base, 4.0 * obs_base]
127
+
128
+ # initialize and use the model
129
+ qnn = QNN(circuit, obs, inputs=["x", "y"])
130
+ y = qnn(torch.rand(3, 2))
131
+ print(str(y)) # markdown-exec: hide
92
132
  ```
93
133
  """
94
134
 
95
135
  def __init__(
96
136
  self,
97
- model: torch.nn.Module | QuantumModel | QNN,
98
- in_features: int | None = None,
99
- out_features: int | None = None,
100
- input_scaling: TorchParam | float | int | torch.Tensor | None = None,
101
- input_shifting: TorchParam | float | int | torch.Tensor | None = None,
102
- output_scaling: TorchParam | float | int | torch.Tensor | None = None,
103
- output_shifting: TorchParam | float | int | torch.Tensor | None = None,
104
- ) -> None:
105
- super().__init__()
106
- self.model = model
107
- if in_features is None and out_features is None:
108
- assert isinstance(model, (QuantumModel, QNN))
109
- self.in_features = model.in_features
110
- self.out_features = model.out_features if model.out_features else 1
111
- else:
112
- self.in_features = in_features # type: ignore[assignment]
113
- self.out_features = out_features # type: ignore[assignment]
114
- if not isinstance(input_scaling, torch.Tensor):
115
- self.register_buffer(
116
- "_input_scaling",
117
- _set_fixed_operation(self.in_features, input_scaling, "scale"),
118
- )
119
- else:
120
- self._input_scaling = input_scaling
121
- if not isinstance(input_shifting, torch.Tensor):
122
- self.register_buffer(
123
- "_input_shifting",
124
- _set_fixed_operation(self.in_features, input_shifting, "shift"),
125
- )
126
- else:
127
- self._input_shifting = input_shifting
128
- if not isinstance(output_scaling, torch.Tensor):
129
- self.register_buffer(
130
- "_output_scaling",
131
- _set_fixed_operation(self.out_features, output_scaling, "scale"),
132
- )
137
+ circuit: QuantumCircuit,
138
+ observable: list[AbstractBlock] | AbstractBlock,
139
+ backend: BackendName = BackendName.PYQTORCH,
140
+ diff_mode: DiffMode = DiffMode.AD,
141
+ measurement: Measurements | None = None,
142
+ noise: Noise | None = None,
143
+ configuration: BackendConfiguration | dict | None = None,
144
+ inputs: list[sympy.Basic | str] | None = None,
145
+ input_diff_mode: InputDiffMode | str = InputDiffMode.AD,
146
+ ):
147
+ """Initialize the QNN.
148
+
149
+ The number of inputs is determined by the feature parameters in the input
150
+ quantum circuit while the number of outputs is determined by how many
151
+ observables are provided as input
152
+
153
+ Args:
154
+ circuit: The quantum circuit to use for the QNN.
155
+ observable: The observable.
156
+ backend: The chosen quantum backend.
157
+ diff_mode: The differentiation engine to use. Choices 'gpsr' or 'ad'.
158
+ measurement: optional measurement protocol. If None,
159
+ use exact expectation value with a statevector simulator
160
+ noise: A noise model to use.
161
+ configuration: optional configuration for the backend
162
+ inputs: List that indicates the order of variables of the tensors that are passed
163
+ to the model. Given input tensors `xs = torch.rand(batch_size, input_size:=2)` a QNN
164
+ with `inputs=["t", "x"]` will assign `t, x = xs[:,0], xs[:,1]`.
165
+ input_diff_mode: The differentiation mode for the input tensor.
166
+ """
167
+ super().__init__(
168
+ circuit,
169
+ observable=observable,
170
+ backend=backend,
171
+ diff_mode=diff_mode,
172
+ measurement=measurement,
173
+ configuration=configuration,
174
+ noise=noise,
175
+ )
176
+ if self._observable is None:
177
+ raise ValueError("You need to provide at least one observable in the QNN constructor")
178
+ if (inputs is not None) and (len(self.inputs) == len(inputs)):
179
+ self.inputs = [sympy.symbols(x) if isinstance(x, str) else x for x in inputs] # type: ignore[union-attr]
180
+ elif (inputs is None) and len(self.inputs) <= 1:
181
+ self.inputs = [sympy.symbols(x) if isinstance(x, str) else x for x in self.inputs] # type: ignore[union-attr]
133
182
  else:
134
- self._output_scaling = output_scaling
135
- if not isinstance(output_shifting, torch.Tensor):
136
- self.register_buffer(
137
- "_output_shifting",
138
- _set_fixed_operation(self.out_features, output_shifting, "shift"),
183
+ raise ValueError(
184
+ """
185
+ Your QNN has more than one input. Please provide a list of inputs in the order of
186
+ your tensor domain. For example, if you want to pass
187
+ `xs = torch.rand(batch_size, input_size:=3)` to you QNN, where
188
+ ```
189
+ t = x[:,0]
190
+ x = x[:,1]
191
+ y = x[:,2]
192
+ ```
193
+ you have to specify
194
+ ```
195
+ QNN(circuit, observable, inputs=["t", "x", "y"])
196
+ ```
197
+ You can also pass a list of sympy symbols.
198
+ """
139
199
  )
200
+ self.format_to_dict = format_to_dict_fn(self.inputs) # type: ignore[arg-type]
201
+ self.input_diff_mode = InputDiffMode(input_diff_mode)
202
+ if self.input_diff_mode == InputDiffMode.FD:
203
+ from qadence.backends.utils import finitediff
204
+
205
+ self.__derivative = finitediff
206
+ elif self.input_diff_mode == InputDiffMode.AD:
207
+ self.__derivative = _torch_derivative # type: ignore[assignment]
140
208
  else:
141
- self._output_shifting = output_shifting
209
+ raise ValueError(f"Unkown forward diff mode: {self.input_diff_mode}")
142
210
 
143
- def _format_to_dict(self, values: Tensor) -> dict[str, Tensor]:
144
- """Format an input tensor into the format required by the forward pass.
211
+ @classmethod
212
+ def from_configs(
213
+ cls,
214
+ register: int | Register,
215
+ obs_config: Any,
216
+ fm_config: Any = FeatureMapConfig(),
217
+ ansatz_config: Any = AnsatzConfig(),
218
+ backend: BackendName = BackendName.PYQTORCH,
219
+ diff_mode: DiffMode = DiffMode.AD,
220
+ measurement: Measurements | None = None,
221
+ noise: Noise | None = None,
222
+ configuration: BackendConfiguration | dict | None = None,
223
+ input_diff_mode: InputDiffMode | str = InputDiffMode.AD,
224
+ ) -> QNN:
225
+ """Create a QNN from a set of configurations.
226
+
227
+ Args:
228
+ register (int | Register): The number of qubits or a register object.
229
+ obs_config (list[ObservableConfig] | ObservableConfig): The configuration(s)
230
+ for the observable(s).
231
+ fm_config (FeatureMapConfig): The configuration for the feature map.
232
+ Defaults to no feature encoding block.
233
+ ansatz_config (AnsatzConfig): The configuration for the ansatz.
234
+ Defaults to a single layer of hardware efficient ansatz.
235
+ backend (BackendName): The chosen quantum backend.
236
+ diff_mode (DiffMode): The differentiation engine to use. Choices are
237
+ 'gpsr' or 'ad'.
238
+ measurement (Measurements): Optional measurement protocol. If None,
239
+ use exact expectation value with a statevector simulator.
240
+ noise (Noise): A noise model to use.
241
+ configuration (BackendConfiguration | dict): Optional backend configuration.
242
+ input_diff_mode (InputDiffMode): The differentiation mode for the input tensor.
145
243
 
146
- The tensor is assumed to have dimensions: n_batches x in_features where in_features
147
- corresponds to the number of input features of the QNN
148
- """
244
+ Returns:
245
+ A QNN object.
246
+
247
+ Raises:
248
+ ValueError: If the observable configuration is not provided.
249
+
250
+ Example:
251
+ ```python exec="on" source="material-block" result="json"
252
+ import torch
253
+ from qadence.ml_tools.config import AnsatzConfig, FeatureMapConfig
254
+ from qadence.ml_tools import QNN
255
+ from qadence.constructors import ObservableConfig
256
+ from qadence.operations import Z
257
+ from qadence.types import (
258
+ AnsatzType, BackendName, BasisSet, ObservableTransform, ReuploadScaling, Strategy
259
+ )
149
260
 
150
- if len(values.size()) == 1:
151
- values = values.reshape(-1, 1)
152
- if len(values.size()) != 2 or values.shape[1] != len(self.model.inputs):
153
- raise ValueError(
154
- f"Model expects in_features={self.model.in_features} but got {values.size()[1]}."
155
- )
156
- names = [p.name for p in self.model.inputs]
157
- res = {}
158
- for i, name in enumerate(names):
159
- res[name] = values[:, i]
160
- return res
261
+ register = 4
262
+ obs_config = ObservableConfig(
263
+ detuning=Z,
264
+ scale=5.0,
265
+ shift=0.0,
266
+ transformation_type=ObservableTransform.SCALE,
267
+ trainable_transform=None,
268
+ )
269
+ fm_config = FeatureMapConfig(
270
+ num_features=2,
271
+ inputs=["x", "y"],
272
+ basis_set=BasisSet.FOURIER,
273
+ reupload_scaling=ReuploadScaling.CONSTANT,
274
+ feature_range={
275
+ "x": (-1.0, 1.0),
276
+ "y": (0.0, 1.0),
277
+ },
278
+ )
279
+ ansatz_config = AnsatzConfig(
280
+ depth=2,
281
+ ansatz_type=AnsatzType.HEA,
282
+ ansatz_strategy=Strategy.DIGITAL,
283
+ )
161
284
 
162
- def _transform_x(self, x: dict[str, torch.Tensor] | Tensor) -> dict[str, Tensor] | Tensor:
163
- """
164
- X can either be a torch Tensor in when using torch.nn.Module, or a standard values dict.
285
+ qnn = QNN.from_configs(
286
+ register, obs_config, fm_config, ansatz_config, backend=BackendName.PYQTORCH
287
+ )
165
288
 
166
- Scales and shifts the tensors in the values dict, containing Featureparameters.
167
- Transformation of inputs can be used to speed up training and avoid potential issues
168
- with numerical stability that can arise due to differing feature scales.
169
- If none are provided, it uses 0. for shifting and 1. for scaling (hence, identity).
289
+ x = torch.rand(2, 2)
290
+ y = qnn(x)
291
+ print(str(y)) # markdown-exec: hide
292
+ ```
293
+ """
294
+ from .constructors import build_qnn_from_configs
295
+
296
+ return build_qnn_from_configs(
297
+ register=register,
298
+ observable_config=obs_config,
299
+ fm_config=fm_config,
300
+ ansatz_config=ansatz_config,
301
+ backend=backend,
302
+ diff_mode=diff_mode,
303
+ measurement=measurement,
304
+ noise=noise,
305
+ configuration=configuration,
306
+ input_diff_mode=input_diff_mode,
307
+ )
170
308
 
171
- Arguments:
172
- values: A torch Tensor or a dict containing values for Featureparameters.
309
+ def forward(
310
+ self,
311
+ values: dict[str, Tensor] | Tensor = None,
312
+ state: Tensor | None = None,
313
+ measurement: Measurements | None = None,
314
+ noise: Noise | None = None,
315
+ endianness: Endianness = Endianness.BIG,
316
+ ) -> Tensor:
317
+ """Forward pass of the model.
318
+
319
+ This returns the (differentiable) expectation value of the given observable
320
+ operator defined in the constructor. Differently from the base QuantumModel
321
+ class, the QNN accepts also a tensor as input for the forward pass. The
322
+ tensor is expected to have shape: `n_batches x in_features` where `n_batches`
323
+ is the number of data points and `in_features` is the dimensionality of the problem
324
+
325
+ The output of the forward pass is the expectation value of the input
326
+ observable(s). If a single observable is given, the output shape is
327
+ `n_batches` while if multiple observables are given the output shape
328
+ is instead `n_batches x n_observables`
329
+
330
+ Args:
331
+ values: the values of the feature parameters
332
+ state: Initial state.
333
+ measurement: optional measurement protocol. If None,
334
+ use exact expectation value with a statevector simulator
335
+ noise: A noise model to use.
336
+ endianness: Endianness of the resulting bit strings.
173
337
 
174
338
  Returns:
175
- A Tensor or dict containing transformed (scaled and/or shifted) Featureparameters.
339
+ Tensor: a tensor with the expectation value of the observables passed
340
+ in the constructor of the model
176
341
  """
177
-
178
- if isinstance(self.model, (QuantumModel, QNN)):
179
- if not isinstance(x, dict):
180
- x = self._format_to_dict(x)
181
- if self.in_features == 1:
182
- return {
183
- key: self._input_scaling * (val + self._input_shifting)
184
- for key, val in x.items()
185
- }
186
- else:
187
- return {
188
- key: self._input_scaling[idx] * (val + self._input_shifting[idx])
189
- for idx, (key, val) in enumerate(x.items())
190
- }
191
-
192
- else:
193
- assert isinstance(self.model, torch.nn.Module) and isinstance(x, Tensor)
194
- return self._input_scaling * (x + self._input_shifting)
195
-
196
- def forward(self, x: dict[str, Tensor] | Tensor, *args: Any, **kwargs: Any) -> Tensor:
197
- y = self.model(self._transform_x(x), *args, **kwargs)
198
- return self._output_scaling * y + self._output_shifting
342
+ return self.expectation(
343
+ values, state=state, measurement=measurement, noise=noise, endianness=endianness
344
+ )
199
345
 
200
346
  def run(
201
347
  self,
202
- values: dict[str, torch.Tensor],
203
- state: torch.Tensor | None = None,
348
+ values: Tensor | dict[str, Tensor] = None,
349
+ state: Tensor | None = None,
204
350
  endianness: Endianness = Endianness.BIG,
205
351
  ) -> Tensor:
206
- return self.model.run(values=self._transform_x(values), state=state, endianness=endianness)
352
+ return super().run(
353
+ values=self.format_to_dict(values),
354
+ state=state,
355
+ endianness=endianness,
356
+ )
207
357
 
208
358
  def sample(
209
359
  self,
210
- values: dict[str, torch.Tensor],
360
+ values: Tensor | dict[str, Tensor] = {},
211
361
  n_shots: int = 1000,
212
- state: torch.Tensor | None = None,
362
+ state: Tensor | None = None,
213
363
  noise: Noise | None = None,
364
+ mitigation: Mitigations | None = None,
214
365
  endianness: Endianness = Endianness.BIG,
215
366
  ) -> list[Counter]:
216
- return self.model.sample( # type: ignore[no-any-return]
217
- values=self._transform_x(values),
367
+ return super().sample(
368
+ values=self.format_to_dict(values),
218
369
  n_shots=n_shots,
219
370
  state=state,
220
- endianness=endianness,
221
371
  noise=noise,
372
+ mitigation=mitigation,
373
+ endianness=endianness,
222
374
  )
223
375
 
224
376
  def expectation(
225
377
  self,
226
- values: dict[str, torch.Tensor],
227
- observable: List[ConvertedObservable] | ConvertedObservable | None = None,
228
- state: torch.Tensor | None = None,
378
+ values: Tensor | dict[str, Tensor] = {},
379
+ observable: list[ConvertedObservable] | ConvertedObservable | None = None,
380
+ state: Tensor | None = None,
229
381
  measurement: Measurements | None = None,
230
382
  noise: Noise | None = None,
383
+ mitigation: Mitigations | None = None,
231
384
  endianness: Endianness = Endianness.BIG,
232
385
  ) -> Tensor:
233
- """
234
- Computes standard expectation.
235
-
236
- However, scales and shifts the output tensor of the underlying model.
237
- If none are provided, it uses 0. for shifting and 1. for scaling.
238
- Transformation of ouputs can be used if the magnitude
239
- of the targets exceeds the domain (-1,1).
240
- """
241
- exp = self.model.expectation(
242
- values=self._transform_x(values),
243
- observable=observable if observable is not None else self.model._observable,
386
+ if values is None:
387
+ values = {}
388
+ if measurement is None:
389
+ measurement = self._measurement
390
+ if noise is None:
391
+ noise = self._noise
392
+ return super().expectation(
393
+ values=self.format_to_dict(values),
244
394
  state=state,
245
395
  measurement=measurement,
246
- noise=noise,
247
396
  endianness=endianness,
397
+ noise=noise,
248
398
  )
249
- return self._output_scaling * exp + self._output_shifting
250
-
251
- def _to_dict(self, save_params: bool = True) -> dict:
252
- from qadence.serialization import serialize
253
-
254
- def store_fn(x: torch.Tensor) -> list[float]:
255
- res: list[float]
256
- if x.requires_grad:
257
- res = x.detach().numpy().tolist()
258
- else:
259
- res = x.numpy().tolist()
260
- return res # type: ignore[no-any-return]
261
-
262
- _d = serialize(self.model, save_params=save_params)
263
-
264
- return {
265
- self.__class__.__name__: _d,
266
- "in_features": self.in_features,
267
- "out_features": self.out_features,
268
- "_input_scaling": store_fn(self._input_scaling),
269
- "_output_scaling": store_fn(self._output_scaling),
270
- "_input_shifting": store_fn(self._input_shifting),
271
- "_output_shifting": store_fn(self._output_shifting),
272
- }
399
+
400
+ def _derivative(self, x: Tensor, derivative_indices: tuple[int, ...]) -> Tensor:
401
+ return self.__derivative(self, x, derivative_indices)
402
+
403
+ def _to_dict(self, save_params: bool = False) -> dict:
404
+ d = dict()
405
+ try:
406
+ d = super()._to_dict(save_params)
407
+ d[self.__class__.__name__]["inputs"] = [str(i) for i in self.inputs]
408
+ logger.debug(f"{self.__class__.__name__} serialized to {d}.")
409
+ except Exception as e:
410
+ logger.warning(f"Unable to serialize {self.__class__.__name__} due to {e}.")
411
+ return d
273
412
 
274
413
  @classmethod
275
- def _from_dict(cls, d: dict, as_torch: bool = False) -> TransformedModule:
414
+ def _from_dict(cls, d: dict, as_torch: bool = False) -> QNN:
276
415
  from qadence.serialization import deserialize
277
416
 
278
- _m: QuantumModel | QNN = deserialize(d[cls.__name__], as_torch) # type: ignore[assignment]
279
- return cls(
280
- _m,
281
- in_features=d["in_features"],
282
- out_features=d["out_features"],
283
- input_scaling=torch.tensor(d["_input_scaling"]),
284
- output_scaling=torch.tensor(d["_output_scaling"]),
285
- input_shifting=torch.tensor(d["_input_shifting"]),
286
- output_shifting=torch.tensor(d["_output_shifting"]),
287
- )
288
-
289
- def to(self, *args: Any, **kwargs: Any) -> TransformedModule:
417
+ qnn: QNN
290
418
  try:
291
- self.model = self.model.to(*args, **kwargs)
292
- if isinstance(self.model, QuantumModel):
293
- device = self.model._circuit.native.device
294
- dtype = (
295
- torch.float64
296
- if self.model._circuit.native.dtype == torch.cdouble
297
- else torch.float32
298
- )
419
+ qm_dict = d[cls.__name__]
420
+ qnn = cls(
421
+ circuit=QuantumCircuit._from_dict(qm_dict["circuit"]),
422
+ observable=[deserialize(q_obs) for q_obs in qm_dict["observable"]], # type: ignore[misc]
423
+ backend=qm_dict["backend"],
424
+ diff_mode=qm_dict["diff_mode"],
425
+ measurement=Measurements._from_dict(qm_dict["measurement"]),
426
+ noise=Noise._from_dict(qm_dict["noise"]),
427
+ configuration=config_factory(qm_dict["backend"], qm_dict["backend_configuration"]),
428
+ inputs=qm_dict["inputs"],
429
+ )
430
+
431
+ if as_torch:
432
+ conv_pd = nn.ParameterDict()
433
+ param_dict = d["param_dict"]
434
+ for n, param in param_dict.items():
435
+ conv_pd[n] = nn.Parameter(param)
436
+ qnn._params = conv_pd
437
+ logger.debug(f"Initialized {cls.__name__} from {d}.")
299
438
 
300
- self._input_scaling = self._input_scaling.to(device=device, dtype=dtype)
301
- self._input_shifting = self._input_shifting.to(device=device, dtype=dtype)
302
- self._output_scaling = self._output_scaling.to(device=device, dtype=dtype)
303
- self._output_shifting = self._output_shifting.to(device=device, dtype=dtype)
304
- elif isinstance(self.model, torch.nn.Module):
305
- self._input_scaling = self._input_scaling.to(*args, **kwargs)
306
- self._input_shifting = self._input_shifting.to(*args, **kwargs)
307
- self._output_scaling = self._output_scaling.to(*args, **kwargs)
308
- self._output_shifting = self._output_shifting.to(*args, **kwargs)
309
- logger.debug(f"Moved {self} to {args}, {kwargs}.")
310
439
  except Exception as e:
311
- logger.warning(f"Unable to move {self} to {args}, {kwargs} due to {e}.")
312
- return self
313
-
314
- @property
315
- def device(self) -> torch.device:
316
- return (
317
- self.model.device
318
- if isinstance(self.model, QuantumModel)
319
- else self._input_scaling.device
320
- )
440
+ logger.warning(f"Unable to deserialize object {d} to {cls.__name__} due to {e}.")
441
+
442
+ return qnn