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.
- qadence/__init__.py +2 -2
- qadence/backends/api.py +47 -60
- qadence/backends/gpsr.py +1 -0
- qadence/backends/pyqtorch/backend.py +1 -2
- qadence/backends/pyqtorch/config.py +5 -0
- qadence/backends/pyqtorch/convert_ops.py +83 -10
- qadence/backends/utils.py +62 -7
- qadence/blocks/abstract.py +7 -0
- qadence/blocks/embedding.py +17 -12
- qadence/blocks/matrix.py +1 -1
- qadence/blocks/primitive.py +1 -1
- qadence/constructors/__init__.py +2 -0
- qadence/constructors/hamiltonians.py +38 -1
- qadence/draw/utils.py +1 -1
- qadence/execution.py +11 -3
- qadence/extensions.py +62 -36
- qadence/ml_tools/__init__.py +11 -3
- qadence/ml_tools/config.py +283 -2
- qadence/ml_tools/constructors.py +796 -0
- qadence/ml_tools/models.py +373 -251
- qadence/ml_tools/printing.py +5 -2
- qadence/ml_tools/saveload.py +42 -18
- qadence/ml_tools/train_grad.py +48 -14
- qadence/ml_tools/utils.py +2 -8
- qadence/{models/quantum_model.py → model.py} +178 -10
- qadence/operations/ham_evo.py +10 -0
- qadence/overlap.py +1 -1
- qadence/parameters.py +10 -1
- qadence/register.py +98 -22
- qadence/serialization.py +6 -6
- qadence/types.py +44 -0
- qadence/utils.py +2 -8
- {qadence-1.6.3.dist-info → qadence-1.7.1.dist-info}/METADATA +7 -6
- {qadence-1.6.3.dist-info → qadence-1.7.1.dist-info}/RECORD +36 -38
- {qadence-1.6.3.dist-info → qadence-1.7.1.dist-info}/WHEEL +1 -1
- qadence/finitediff.py +0 -47
- qadence/models/__init__.py +0 -7
- qadence/models/qnn.py +0 -265
- {qadence-1.6.3.dist-info → qadence-1.7.1.dist-info}/licenses/LICENSE +0 -0
qadence/ml_tools/models.py
CHANGED
@@ -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,
|
5
|
+
from typing import Any, Callable
|
5
6
|
|
6
|
-
import
|
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.
|
14
|
-
from qadence.
|
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.
|
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
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
43
|
-
"""
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
63
|
-
from qadence
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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.
|
209
|
+
raise ValueError(f"Unkown forward diff mode: {self.input_diff_mode}")
|
142
210
|
|
143
|
-
|
144
|
-
|
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
|
-
|
147
|
-
|
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
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
285
|
+
qnn = QNN.from_configs(
|
286
|
+
register, obs_config, fm_config, ansatz_config, backend=BackendName.PYQTORCH
|
287
|
+
)
|
165
288
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
172
|
-
|
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
|
-
|
339
|
+
Tensor: a tensor with the expectation value of the observables passed
|
340
|
+
in the constructor of the model
|
176
341
|
"""
|
177
|
-
|
178
|
-
|
179
|
-
|
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,
|
203
|
-
state:
|
348
|
+
values: Tensor | dict[str, Tensor] = None,
|
349
|
+
state: Tensor | None = None,
|
204
350
|
endianness: Endianness = Endianness.BIG,
|
205
351
|
) -> Tensor:
|
206
|
-
return
|
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,
|
360
|
+
values: Tensor | dict[str, Tensor] = {},
|
211
361
|
n_shots: int = 1000,
|
212
|
-
state:
|
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
|
217
|
-
values=self.
|
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,
|
227
|
-
observable:
|
228
|
-
state:
|
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
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
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) ->
|
414
|
+
def _from_dict(cls, d: dict, as_torch: bool = False) -> QNN:
|
276
415
|
from qadence.serialization import deserialize
|
277
416
|
|
278
|
-
|
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
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
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
|
312
|
-
|
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
|