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.
- qadence/__init__.py +1 -1
- qadence/analog/device.py +1 -1
- qadence/analog/parse_analog.py +1 -2
- qadence/backend.py +3 -3
- qadence/backends/gpsr.py +8 -2
- qadence/backends/horqrux/backend.py +3 -3
- qadence/backends/pulser/backend.py +21 -38
- qadence/backends/pulser/convert_ops.py +2 -2
- qadence/backends/pyqtorch/backend.py +85 -10
- qadence/backends/pyqtorch/config.py +10 -3
- qadence/backends/pyqtorch/convert_ops.py +245 -233
- qadence/backends/utils.py +9 -1
- qadence/blocks/abstract.py +1 -1
- qadence/blocks/embedding.py +21 -11
- qadence/blocks/matrix.py +3 -1
- qadence/blocks/primitive.py +37 -11
- qadence/circuit.py +1 -1
- qadence/constructors/__init__.py +2 -1
- qadence/constructors/ansatze.py +176 -0
- qadence/engines/differentiable_backend.py +3 -3
- qadence/engines/jax/differentiable_backend.py +2 -2
- qadence/engines/jax/differentiable_expectation.py +2 -2
- qadence/engines/torch/differentiable_backend.py +2 -2
- qadence/engines/torch/differentiable_expectation.py +2 -2
- qadence/execution.py +14 -16
- qadence/extensions.py +1 -1
- qadence/log_config.yaml +10 -0
- qadence/measurements/shadow.py +101 -133
- qadence/measurements/tomography.py +2 -2
- qadence/measurements/utils.py +4 -4
- qadence/mitigations/analog_zne.py +8 -7
- qadence/mitigations/protocols.py +2 -2
- qadence/mitigations/readout.py +14 -5
- qadence/ml_tools/__init__.py +4 -8
- qadence/ml_tools/callbacks/__init__.py +30 -0
- qadence/ml_tools/callbacks/callback.py +451 -0
- qadence/ml_tools/callbacks/callbackmanager.py +214 -0
- qadence/ml_tools/{saveload.py → callbacks/saveload.py} +11 -11
- qadence/ml_tools/callbacks/writer_registry.py +430 -0
- qadence/ml_tools/config.py +132 -258
- qadence/ml_tools/constructors.py +2 -2
- qadence/ml_tools/data.py +7 -3
- qadence/ml_tools/loss/__init__.py +10 -0
- qadence/ml_tools/loss/loss.py +87 -0
- qadence/ml_tools/models.py +7 -7
- qadence/ml_tools/optimize_step.py +45 -10
- qadence/ml_tools/stages.py +46 -0
- qadence/ml_tools/train_utils/__init__.py +7 -0
- qadence/ml_tools/train_utils/base_trainer.py +548 -0
- qadence/ml_tools/train_utils/config_manager.py +184 -0
- qadence/ml_tools/trainer.py +692 -0
- qadence/model.py +6 -6
- qadence/noise/__init__.py +2 -2
- qadence/noise/protocols.py +188 -36
- qadence/operations/control_ops.py +37 -22
- qadence/operations/ham_evo.py +88 -26
- qadence/operations/parametric.py +32 -10
- qadence/operations/primitive.py +61 -29
- qadence/overlap.py +0 -6
- qadence/parameters.py +3 -2
- qadence/transpile/__init__.py +2 -1
- qadence/transpile/noise.py +53 -0
- qadence/types.py +39 -3
- {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/METADATA +5 -9
- {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/RECORD +67 -63
- {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/WHEEL +1 -1
- qadence/backends/braket/__init__.py +0 -4
- qadence/backends/braket/backend.py +0 -234
- qadence/backends/braket/config.py +0 -22
- qadence/backends/braket/convert_ops.py +0 -116
- qadence/ml_tools/printing.py +0 -153
- qadence/ml_tools/train_grad.py +0 -395
- qadence/ml_tools/train_no_grad.py +0 -199
- qadence/noise/readout.py +0 -218
- {qadence-1.7.8.dist-info → qadence-1.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,37 +1,23 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import re
|
4
|
+
from functools import partial, reduce
|
3
5
|
from itertools import chain as flatten
|
4
|
-
from
|
5
|
-
from typing import Any, Sequence, Tuple
|
6
|
+
from typing import Any, Callable, Sequence
|
6
7
|
|
7
8
|
import pyqtorch as pyq
|
8
9
|
import sympy
|
9
10
|
import torch
|
10
|
-
from pyqtorch.embed import
|
11
|
-
from pyqtorch.matrices import _dagger
|
12
|
-
from pyqtorch.time_dependent.sesolve import sesolve
|
13
|
-
from pyqtorch.utils import is_diag
|
11
|
+
from pyqtorch.embed import ConcretizedCallable
|
14
12
|
from torch import (
|
15
13
|
Tensor,
|
16
14
|
cdouble,
|
17
15
|
complex64,
|
18
|
-
diag_embed,
|
19
|
-
diagonal,
|
20
|
-
exp,
|
21
16
|
float64,
|
22
|
-
linalg,
|
23
17
|
tensor,
|
24
|
-
transpose,
|
25
|
-
)
|
26
|
-
from torch import device as torch_device
|
27
|
-
from torch import dtype as torch_dtype
|
28
|
-
from torch.nn import Module, ParameterDict
|
29
|
-
|
30
|
-
from qadence.backends.utils import (
|
31
|
-
finitediff,
|
32
|
-
pyqify,
|
33
|
-
unpyqify,
|
34
18
|
)
|
19
|
+
from torch.nn import Module
|
20
|
+
|
35
21
|
from qadence.blocks import (
|
36
22
|
AbstractBlock,
|
37
23
|
AddBlock,
|
@@ -43,11 +29,8 @@ from qadence.blocks import (
|
|
43
29
|
ScaleBlock,
|
44
30
|
TimeEvolutionBlock,
|
45
31
|
)
|
46
|
-
from qadence.blocks.block_to_tensor import (
|
47
|
-
_block_to_tensor_embedded,
|
48
|
-
)
|
49
32
|
from qadence.blocks.primitive import ProjectorBlock
|
50
|
-
from qadence.
|
33
|
+
from qadence.noise import NoiseHandler
|
51
34
|
from qadence.operations import (
|
52
35
|
U,
|
53
36
|
multi_qubit_gateset,
|
@@ -56,10 +39,28 @@ from qadence.operations import (
|
|
56
39
|
three_qubit_gateset,
|
57
40
|
two_qubit_gateset,
|
58
41
|
)
|
59
|
-
from qadence.types import OpName
|
42
|
+
from qadence.types import NoiseProtocol, OpName
|
60
43
|
|
61
44
|
from .config import Configuration
|
62
45
|
|
46
|
+
SYMPY_TO_PYQ_MAPPING = {
|
47
|
+
sympy.Pow: "pow",
|
48
|
+
sympy.cos: "cos",
|
49
|
+
sympy.Add: "add",
|
50
|
+
sympy.Mul: "mul",
|
51
|
+
sympy.sin: "sin",
|
52
|
+
sympy.log: "log",
|
53
|
+
sympy.tan: "tan",
|
54
|
+
sympy.tanh: "tanh",
|
55
|
+
sympy.Heaviside: "hs",
|
56
|
+
sympy.Abs: "abs",
|
57
|
+
sympy.exp: "exp",
|
58
|
+
sympy.acos: "acos",
|
59
|
+
sympy.asin: "asin",
|
60
|
+
sympy.atan: "atan",
|
61
|
+
}
|
62
|
+
|
63
|
+
|
63
64
|
# Tdagger is not supported currently
|
64
65
|
supported_gates = list(set(OpName.list()) - set([OpName.TDAGGER]))
|
65
66
|
"""The set of supported gates.
|
@@ -98,9 +99,99 @@ def extract_parameter(block: ScaleBlock | ParametricBlock, config: Configuration
|
|
98
99
|
return config.get_param_name(block)[0]
|
99
100
|
|
100
101
|
|
102
|
+
def replace_underscore_floats(s: str) -> str:
|
103
|
+
"""Replace underscores with periods for all floats in given string.
|
104
|
+
|
105
|
+
Needed for correct parsing of string by sympy parser.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
s (str): string expression
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
str: transformed string expression
|
112
|
+
"""
|
113
|
+
|
114
|
+
# Regular expression to match floats written with underscores instead of dots
|
115
|
+
float_with_underscore_pattern = r"""
|
116
|
+
(?<!\w) # Negative lookbehind to ensure not part of a word
|
117
|
+
-? # Optional negative sign
|
118
|
+
\d+ # One or more digits (before underscore)
|
119
|
+
_ # The underscore acting as decimal separator
|
120
|
+
\d+ # One or more digits (after underscore)
|
121
|
+
([eE][-+]?\d+)? # Optional exponent part for scientific notation
|
122
|
+
(?!\w) # Negative lookahead to ensure not part of a word
|
123
|
+
"""
|
124
|
+
|
125
|
+
# Function to replace the underscore with a dot
|
126
|
+
def underscore_to_dot(match: re.Match) -> Any:
|
127
|
+
return match.group(0).replace("_", ".")
|
128
|
+
|
129
|
+
# Compile the regular expression
|
130
|
+
pattern = re.compile(float_with_underscore_pattern, re.VERBOSE)
|
131
|
+
|
132
|
+
return pattern.sub(underscore_to_dot, s)
|
133
|
+
|
134
|
+
|
135
|
+
def sympy_to_pyq(expr: sympy.Expr) -> ConcretizedCallable | Tensor:
|
136
|
+
"""Convert sympy expression to pyqtorch ConcretizedCallable object.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
expr (sympy.Expr): sympy expression
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
ConcretizedCallable: expression encoded as ConcretizedCallable
|
143
|
+
"""
|
144
|
+
|
145
|
+
# base case - independent argument
|
146
|
+
if len(expr.args) == 0:
|
147
|
+
try:
|
148
|
+
res = torch.as_tensor(float(expr))
|
149
|
+
except Exception as e:
|
150
|
+
res = str(expr)
|
151
|
+
|
152
|
+
if "/" in res: # Found a rational
|
153
|
+
res = torch.as_tensor(float(sympy.Rational(res).evalf()))
|
154
|
+
return res
|
155
|
+
|
156
|
+
# Recursively iterate through current function arguments
|
157
|
+
all_results = []
|
158
|
+
for arg in expr.args:
|
159
|
+
res = sympy_to_pyq(arg)
|
160
|
+
all_results.append(res)
|
161
|
+
|
162
|
+
# deal with multi-argument (>2) sympy functions: converting to nested
|
163
|
+
# ConcretizedCallable objects
|
164
|
+
if len(all_results) > 2:
|
165
|
+
|
166
|
+
def fn(x: str | ConcretizedCallable, y: str | ConcretizedCallable) -> Callable:
|
167
|
+
return partial(ConcretizedCallable, call_name=SYMPY_TO_PYQ_MAPPING[expr.func])( # type: ignore [no-any-return]
|
168
|
+
abstract_args=[x, y]
|
169
|
+
)
|
170
|
+
|
171
|
+
concretized_callable = reduce(fn, all_results)
|
172
|
+
else:
|
173
|
+
concretized_callable = ConcretizedCallable(SYMPY_TO_PYQ_MAPPING[expr.func], all_results)
|
174
|
+
return concretized_callable
|
175
|
+
|
176
|
+
|
101
177
|
def convert_block(
|
102
|
-
block: AbstractBlock,
|
178
|
+
block: AbstractBlock,
|
179
|
+
n_qubits: int = None,
|
180
|
+
config: Configuration = None,
|
103
181
|
) -> Sequence[Module | Tensor | str | sympy.Expr]:
|
182
|
+
"""Convert block to native Pyqtorch representation.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
block (AbstractBlock): Block to convert.
|
186
|
+
n_qubits (int, optional): Number of qubits. Defaults to None.
|
187
|
+
config (Configuration, optional): Backend configuration instance. Defaults to None.
|
188
|
+
|
189
|
+
Raises:
|
190
|
+
NotImplementedError: For non supported blocks.
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
Sequence[Module | Tensor | str | sympy.Expr]: List of native operations.
|
194
|
+
"""
|
104
195
|
if isinstance(block, (Tensor, str, sympy.Expr)): # case for hamevo generators
|
105
196
|
if isinstance(block, Tensor):
|
106
197
|
block = block.permute(1, 2, 0) # put batch size in the back
|
@@ -112,41 +203,73 @@ def convert_block(
|
|
112
203
|
if config is None:
|
113
204
|
config = Configuration()
|
114
205
|
|
206
|
+
noise: NoiseHandler | None = None
|
207
|
+
if hasattr(block, "noise") and block.noise:
|
208
|
+
noise = convert_digital_noise(block.noise)
|
209
|
+
|
115
210
|
if isinstance(block, ScaleBlock):
|
116
211
|
scaled_ops = convert_block(block.block, n_qubits, config)
|
117
|
-
scale = extract_parameter(block, config)
|
118
|
-
|
212
|
+
scale = extract_parameter(block, config=config)
|
213
|
+
|
214
|
+
# replace underscore by dot when underscore is between two numbers in string
|
215
|
+
if isinstance(scale, str):
|
216
|
+
scale = replace_underscore_floats(scale)
|
217
|
+
|
218
|
+
if isinstance(scale, str) and not config._use_gate_params:
|
219
|
+
param = sympy_to_pyq(sympy.parse_expr(scale))
|
220
|
+
else:
|
221
|
+
param = scale
|
222
|
+
|
223
|
+
return [pyq.Scale(pyq.Sequence(scaled_ops), param)]
|
119
224
|
|
120
225
|
elif isinstance(block, TimeEvolutionBlock):
|
226
|
+
duration = block.duration # type: ignore [attr-defined]
|
121
227
|
if getattr(block.generator, "is_time_dependent", False):
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
check_hermitian=True,
|
134
|
-
)
|
135
|
-
)[0]
|
136
|
-
else:
|
137
|
-
generator = convert_block(block.generator, n_qubits, config)[0] # type: ignore[arg-type]
|
138
|
-
time_param = config.get_param_name(block)[0]
|
139
|
-
return [
|
140
|
-
pyq.HamiltonianEvolution(
|
228
|
+
config._use_gate_params = False
|
229
|
+
duration = config.get_param_name(block)[1]
|
230
|
+
generator = convert_block(block.generator, config=config)[0] # type: ignore [arg-type]
|
231
|
+
elif isinstance(block.generator, sympy.Basic):
|
232
|
+
generator = config.get_param_name(block)[1]
|
233
|
+
|
234
|
+
elif isinstance(block.generator, Tensor):
|
235
|
+
m = block.generator.to(dtype=cdouble)
|
236
|
+
generator = convert_block(
|
237
|
+
MatrixBlock(
|
238
|
+
m,
|
141
239
|
qubit_support=qubit_support,
|
142
|
-
|
143
|
-
|
144
|
-
cache_length=0,
|
240
|
+
check_unitary=False,
|
241
|
+
check_hermitian=True,
|
145
242
|
)
|
243
|
+
)[0]
|
244
|
+
else:
|
245
|
+
generator = convert_block(block.generator, n_qubits, config)[0] # type: ignore[arg-type]
|
246
|
+
time_param = config.get_param_name(block)[0]
|
247
|
+
|
248
|
+
# convert noise operators here
|
249
|
+
noise_operators: list = [
|
250
|
+
convert_block(noise_block, config=config)[0] for noise_block in block.noise_operators
|
251
|
+
]
|
252
|
+
if len(noise_operators) > 0:
|
253
|
+
# squeeze batch size for noise operators
|
254
|
+
noise_operators = [
|
255
|
+
pyq_op.tensor(full_support=qubit_support).squeeze(-1) for pyq_op in noise_operators
|
146
256
|
]
|
147
257
|
|
258
|
+
return [
|
259
|
+
pyq.HamiltonianEvolution(
|
260
|
+
qubit_support=qubit_support,
|
261
|
+
generator=generator,
|
262
|
+
time=time_param,
|
263
|
+
cache_length=0,
|
264
|
+
duration=duration,
|
265
|
+
solver=config.ode_solver,
|
266
|
+
steps=config.n_steps_hevo,
|
267
|
+
noise_operators=noise_operators,
|
268
|
+
)
|
269
|
+
]
|
270
|
+
|
148
271
|
elif isinstance(block, MatrixBlock):
|
149
|
-
return [pyq.primitives.Primitive(block.matrix, block.qubit_support)]
|
272
|
+
return [pyq.primitives.Primitive(block.matrix, block.qubit_support, noise=noise)]
|
150
273
|
elif isinstance(block, CompositeBlock):
|
151
274
|
ops = list(flatten(*(convert_block(b, n_qubits, config) for b in block.blocks)))
|
152
275
|
if isinstance(block, AddBlock):
|
@@ -159,38 +282,66 @@ def convert_block(
|
|
159
282
|
if isinstance(block, ProjectorBlock):
|
160
283
|
projector = getattr(pyq, block.name)
|
161
284
|
if block.name == OpName.N:
|
162
|
-
return [projector(target=qubit_support)]
|
285
|
+
return [projector(target=qubit_support, noise=noise)]
|
163
286
|
else:
|
164
|
-
return [
|
287
|
+
return [
|
288
|
+
projector(
|
289
|
+
qubit_support=qubit_support,
|
290
|
+
ket=block.ket,
|
291
|
+
bra=block.bra,
|
292
|
+
noise=noise,
|
293
|
+
)
|
294
|
+
]
|
165
295
|
else:
|
166
296
|
return [getattr(pyq, block.name)(qubit_support[0])]
|
167
297
|
elif isinstance(block, tuple(single_qubit_gateset)):
|
168
298
|
pyq_cls = getattr(pyq, block.name)
|
169
299
|
if isinstance(block, ParametricBlock):
|
170
300
|
if isinstance(block, U):
|
171
|
-
op = pyq_cls(
|
301
|
+
op = pyq_cls(
|
302
|
+
qubit_support[0],
|
303
|
+
*config.get_param_name(block),
|
304
|
+
noise=noise,
|
305
|
+
)
|
172
306
|
else:
|
173
|
-
|
307
|
+
param = extract_parameter(block, config)
|
308
|
+
op = pyq_cls(qubit_support[0], param, noise=noise)
|
174
309
|
else:
|
175
|
-
op = pyq_cls(qubit_support[0])
|
310
|
+
op = pyq_cls(qubit_support[0], noise=noise) # type: ignore [attr-defined]
|
176
311
|
return [op]
|
177
312
|
elif isinstance(block, tuple(two_qubit_gateset)):
|
178
313
|
pyq_cls = getattr(pyq, block.name)
|
179
314
|
if isinstance(block, ParametricBlock):
|
180
|
-
op = pyq_cls(
|
315
|
+
op = pyq_cls(
|
316
|
+
qubit_support[0],
|
317
|
+
qubit_support[1],
|
318
|
+
extract_parameter(block, config),
|
319
|
+
noise=noise,
|
320
|
+
)
|
181
321
|
else:
|
182
|
-
op = pyq_cls(
|
322
|
+
op = pyq_cls(
|
323
|
+
qubit_support[0], qubit_support[1], noise=noise # type: ignore [attr-defined]
|
324
|
+
)
|
183
325
|
return [op]
|
184
326
|
elif isinstance(block, tuple(three_qubit_gateset) + tuple(multi_qubit_gateset)):
|
185
327
|
block_name = block.name[1:] if block.name.startswith("M") else block.name
|
186
328
|
pyq_cls = getattr(pyq, block_name)
|
187
329
|
if isinstance(block, ParametricBlock):
|
188
|
-
op = pyq_cls(
|
330
|
+
op = pyq_cls(
|
331
|
+
qubit_support[:-1],
|
332
|
+
qubit_support[-1],
|
333
|
+
extract_parameter(block, config),
|
334
|
+
noise=noise,
|
335
|
+
)
|
189
336
|
else:
|
190
337
|
if "CSWAP" in block_name:
|
191
|
-
op = pyq_cls(
|
338
|
+
op = pyq_cls(
|
339
|
+
qubit_support[:-2], qubit_support[-2:], noise=noise # type: ignore [attr-defined]
|
340
|
+
)
|
192
341
|
else:
|
193
|
-
op = pyq_cls(
|
342
|
+
op = pyq_cls(
|
343
|
+
qubit_support[:-1], qubit_support[-1], noise=noise # type: ignore [attr-defined]
|
344
|
+
)
|
194
345
|
return [op]
|
195
346
|
else:
|
196
347
|
raise NotImplementedError(
|
@@ -200,182 +351,43 @@ def convert_block(
|
|
200
351
|
)
|
201
352
|
|
202
353
|
|
203
|
-
|
204
|
-
|
205
|
-
self,
|
206
|
-
qubit_support: Tuple[int, ...],
|
207
|
-
n_qubits: int,
|
208
|
-
block: TimeEvolutionBlock,
|
209
|
-
config: Configuration,
|
210
|
-
):
|
211
|
-
super().__init__()
|
212
|
-
self.qubit_support = qubit_support
|
213
|
-
self.n_qubits = n_qubits
|
214
|
-
self.param_names = config.get_param_name(block)
|
215
|
-
self.block = block
|
216
|
-
self.hmat: Tensor
|
217
|
-
self.config = config
|
218
|
-
|
219
|
-
def _hamiltonian(self: PyQTimeDependentEvolution, values: dict[str, Tensor]) -> Tensor:
|
220
|
-
hmat = _block_to_tensor_embedded(
|
221
|
-
block.generator, # type: ignore[arg-type]
|
222
|
-
values=values,
|
223
|
-
qubit_support=self.qubit_support,
|
224
|
-
use_full_support=False,
|
225
|
-
device=self.device,
|
226
|
-
)
|
227
|
-
return hmat.permute(1, 2, 0)
|
228
|
-
|
229
|
-
self._hamiltonian = _hamiltonian
|
230
|
-
|
231
|
-
self._time_evolution = lambda values: values[self.param_names[0]]
|
232
|
-
self._device: torch_device = (
|
233
|
-
self.hmat.device if hasattr(self, "hmat") else torch_device("cpu")
|
234
|
-
)
|
235
|
-
self._dtype: torch_dtype = self.hmat.dtype if hasattr(self, "hmat") else cdouble
|
354
|
+
def convert_digital_noise(noise: NoiseHandler) -> pyq.noise.NoiseProtocol | None:
|
355
|
+
"""Convert the digital noise into pyqtorch NoiseProtocol.
|
236
356
|
|
237
|
-
|
238
|
-
|
239
|
-
diag_check = tensor(
|
240
|
-
[is_diag(hamiltonian[..., i]) for i in range(hamiltonian.size()[2])], device=self.device
|
241
|
-
)
|
242
|
-
|
243
|
-
def _evolve_diag_operator(hamiltonian: Tensor, time_evolution: Tensor) -> Tensor:
|
244
|
-
evol_operator = diagonal(hamiltonian) * (-1j * time_evolution).view((-1, 1))
|
245
|
-
evol_operator = diag_embed(exp(evol_operator))
|
246
|
-
return transpose(evol_operator, 0, -1)
|
247
|
-
|
248
|
-
def _evolve_matrixexp_operator(hamiltonian: Tensor, time_evolution: Tensor) -> Tensor:
|
249
|
-
evol_operator = transpose(hamiltonian, 0, -1) * (-1j * time_evolution).view((-1, 1, 1))
|
250
|
-
evol_operator = linalg.matrix_exp(evol_operator)
|
251
|
-
return transpose(evol_operator, 0, -1)
|
252
|
-
|
253
|
-
evolve_operator = (
|
254
|
-
_evolve_diag_operator if bool(prod(diag_check)) else _evolve_matrixexp_operator
|
255
|
-
)
|
256
|
-
return evolve_operator(hamiltonian, time_evolution)
|
257
|
-
|
258
|
-
def unitary(self, values: dict[str, Tensor]) -> Tensor:
|
259
|
-
"""The evolved operator given current parameter values for generator and time evolution."""
|
260
|
-
return self._unitary(self._hamiltonian(self, values), self._time_evolution(values))
|
261
|
-
|
262
|
-
def jacobian_time(self, values: dict[str, Tensor]) -> Tensor:
|
263
|
-
"""Approximate jacobian of the evolved operator with respect to time evolution."""
|
264
|
-
return finitediff(
|
265
|
-
lambda t: self._unitary(time_evolution=t, hamiltonian=self._hamiltonian(self, values)),
|
266
|
-
values[self.param_names[0]].reshape(-1, 1),
|
267
|
-
(0,),
|
268
|
-
)
|
269
|
-
|
270
|
-
def jacobian_generator(self, values: dict[str, Tensor]) -> Tensor:
|
271
|
-
"""Approximate jacobian of the evolved operator with respect to generator parameter(s)."""
|
272
|
-
if len(self.param_names) > 2:
|
273
|
-
raise NotImplementedError(
|
274
|
-
"jacobian_generator does not support generators\
|
275
|
-
with more than 1 parameter."
|
276
|
-
)
|
357
|
+
Args:
|
358
|
+
noise (NoiseHandler): Noise to convert.
|
277
359
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
)
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
lambda v: self._unitary(
|
292
|
-
time_evolution=self._time_evolution(values), hamiltonian=_generator(v)
|
293
|
-
),
|
294
|
-
values[self.param_names[1]].reshape(-1, 1),
|
295
|
-
(0,),
|
296
|
-
)
|
360
|
+
Returns:
|
361
|
+
pyq.noise.NoiseProtocol | None: Pyqtorch native noise protocol
|
362
|
+
if there are any digital noise protocols.
|
363
|
+
"""
|
364
|
+
digital_part = noise.filter(NoiseProtocol.DIGITAL)
|
365
|
+
if digital_part is None:
|
366
|
+
return None
|
367
|
+
return pyq.noise.NoiseProtocol(
|
368
|
+
[
|
369
|
+
pyq.noise.NoiseProtocol(proto, option.get("error_probability"))
|
370
|
+
for proto, option in zip(digital_part.protocol, digital_part.options)
|
371
|
+
]
|
372
|
+
)
|
297
373
|
|
298
|
-
def dagger(self, values: dict[str, Tensor]) -> Tensor:
|
299
|
-
"""Dagger of the evolved operator given the current parameter values."""
|
300
|
-
return _dagger(self.unitary(values))
|
301
|
-
|
302
|
-
def _get_time_parameter(self) -> str:
|
303
|
-
# get unique time parameters
|
304
|
-
unique_time_params = set()
|
305
|
-
for p in parameters(self.block.generator): # type: ignore [arg-type]
|
306
|
-
if getattr(p, "is_time", False):
|
307
|
-
unique_time_params.add(str(p))
|
308
|
-
|
309
|
-
if len(unique_time_params) > 1:
|
310
|
-
raise Exception("Only a single time parameter is supported.")
|
311
|
-
|
312
|
-
return unique_time_params.pop()
|
313
|
-
|
314
|
-
def forward(
|
315
|
-
self,
|
316
|
-
state: Tensor,
|
317
|
-
values: dict[str, Tensor] | ParameterDict = dict(),
|
318
|
-
embedding: Embedding | None = None,
|
319
|
-
) -> Tensor:
|
320
|
-
def Ht(t: Tensor | float) -> Tensor:
|
321
|
-
# values dict has to change with new value of t
|
322
|
-
# initial value of a feature parameter inside generator block
|
323
|
-
# has to be inferred here
|
324
|
-
new_vals = dict()
|
325
|
-
for str_expr, val in values.items():
|
326
|
-
expr = sympy.sympify(str_expr)
|
327
|
-
t_symb = sympy.Symbol(self._get_time_parameter())
|
328
|
-
free_symbols = expr.free_symbols
|
329
|
-
if t_symb in free_symbols:
|
330
|
-
# create substitution list for time and feature params
|
331
|
-
subs_list = [(t_symb, t)]
|
332
|
-
|
333
|
-
if len(free_symbols) > 1:
|
334
|
-
# get feature param symbols
|
335
|
-
feat_symbols = free_symbols.difference(set([t_symb]))
|
336
|
-
|
337
|
-
# get feature param values
|
338
|
-
feat_vals = values["orig_param_values"]
|
339
|
-
|
340
|
-
# update substitution list with feature param values
|
341
|
-
for fs in feat_symbols:
|
342
|
-
subs_list.append((fs, feat_vals[str(fs)]))
|
343
|
-
|
344
|
-
# evaluate expression with new time param value
|
345
|
-
new_vals[str_expr] = torch.tensor(float(expr.subs(subs_list)))
|
346
|
-
else:
|
347
|
-
# expression doesn't contain time parameter - copy it as is
|
348
|
-
new_vals[str_expr] = val
|
349
|
-
|
350
|
-
# get matrix form of generator
|
351
|
-
hmat = _block_to_tensor_embedded(
|
352
|
-
self.block.generator, # type: ignore[arg-type]
|
353
|
-
values=new_vals,
|
354
|
-
qubit_support=self.qubit_support,
|
355
|
-
use_full_support=False,
|
356
|
-
device=self.device,
|
357
|
-
).squeeze(0)
|
358
|
-
|
359
|
-
return hmat
|
360
|
-
|
361
|
-
tsave = torch.linspace(0, self.block.duration, self.config.n_steps_hevo) # type: ignore [attr-defined]
|
362
|
-
result = pyqify(
|
363
|
-
sesolve(Ht, unpyqify(state).T[:, 0:1], tsave, self.config.ode_solver).states[-1].T
|
364
|
-
)
|
365
374
|
|
366
|
-
|
375
|
+
def convert_readout_noise(n_qubits: int, noise: NoiseHandler) -> pyq.noise.ReadoutNoise | None:
|
376
|
+
"""Convert the readout noise into pyqtorch ReadoutNoise.
|
367
377
|
|
368
|
-
|
369
|
-
|
370
|
-
|
378
|
+
Args:
|
379
|
+
n_qubits (int): Number of qubits
|
380
|
+
noise (NoiseHandler): Noise to convert.
|
371
381
|
|
372
|
-
|
373
|
-
|
374
|
-
|
382
|
+
Returns:
|
383
|
+
pyq.noise.ReadoutNoise | None: Pyqtorch native ReadoutNoise instance
|
384
|
+
if readout is is noise.
|
385
|
+
"""
|
386
|
+
readout_part = noise.filter(NoiseProtocol.READOUT)
|
387
|
+
if readout_part is None:
|
388
|
+
return None
|
375
389
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
self._dtype = self.hmat.dtype
|
381
|
-
return self
|
390
|
+
if readout_part.protocol[0] == NoiseProtocol.READOUT.INDEPENDENT:
|
391
|
+
return pyq.noise.ReadoutNoise(n_qubits, **readout_part.options[0])
|
392
|
+
else:
|
393
|
+
return pyq.noise.CorrelatedReadoutNoise(**readout_part.options[0])
|
qadence/backends/utils.py
CHANGED
@@ -10,6 +10,7 @@ import torch
|
|
10
10
|
from numpy.typing import ArrayLike
|
11
11
|
from pyqtorch.apply import apply_operator
|
12
12
|
from pyqtorch.primitives import Parametric as PyQParametric
|
13
|
+
from pyqtorch.utils import DensityMatrix
|
13
14
|
from torch import (
|
14
15
|
Tensor,
|
15
16
|
cat,
|
@@ -121,7 +122,14 @@ def pyqify(state: Tensor, n_qubits: int = None) -> ArrayLike:
|
|
121
122
|
|
122
123
|
|
123
124
|
def unpyqify(state: Tensor) -> Tensor:
|
124
|
-
"""
|
125
|
+
"""
|
126
|
+
Convert a state of shape [2] * n_qubits + [batch_size] to (batch_size, 2**n_qubits).
|
127
|
+
|
128
|
+
Convert a density matrix of shape (2**n_qubits, 2**n_qubits, batch_size)
|
129
|
+
to (batch_size, 2**n_qubits, 2**n_qubits)
|
130
|
+
"""
|
131
|
+
if isinstance(state, DensityMatrix):
|
132
|
+
return torch.einsum("ijk->kij", state)
|
125
133
|
return torch.flatten(state, start_dim=0, end_dim=-2).t()
|
126
134
|
|
127
135
|
|
qadence/blocks/abstract.py
CHANGED
qadence/blocks/embedding.py
CHANGED
@@ -5,7 +5,7 @@ from typing import Callable, Iterable, List
|
|
5
5
|
import sympy
|
6
6
|
from numpy import array as nparray
|
7
7
|
from numpy import cdouble as npcdouble
|
8
|
-
from torch import tensor
|
8
|
+
from torch import as_tensor, tensor
|
9
9
|
|
10
10
|
from qadence.blocks import (
|
11
11
|
AbstractBlock,
|
@@ -111,11 +111,13 @@ def embedding(
|
|
111
111
|
angle: ArrayLike
|
112
112
|
values = {}
|
113
113
|
for symbol in expr.free_symbols:
|
114
|
-
if
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
114
|
+
if symbol.name in inputs:
|
115
|
+
value = inputs[symbol.name]
|
116
|
+
elif symbol.name in params:
|
117
|
+
value = params[symbol.name]
|
118
|
+
else:
|
119
|
+
if symbol.is_time:
|
120
|
+
value = tensor(1.0)
|
119
121
|
else:
|
120
122
|
msg_trainable = "Trainable" if symbol.trainable else "Non-trainable"
|
121
123
|
raise KeyError(
|
@@ -123,9 +125,7 @@ def embedding(
|
|
123
125
|
f"inputs list: {list(inputs.keys())} nor the "
|
124
126
|
f"params list: {list(params.keys())}."
|
125
127
|
)
|
126
|
-
|
127
|
-
else:
|
128
|
-
values[symbol.name] = tensor(1.0)
|
128
|
+
values[symbol.name] = value
|
129
129
|
angle = fn(**values)
|
130
130
|
# do not reshape parameters which are multi-dimensional
|
131
131
|
# tensors, such as for example generator matrices
|
@@ -142,8 +142,18 @@ def embedding(
|
|
142
142
|
gate_lvl_params[uuid] = embedded_params[e]
|
143
143
|
return gate_lvl_params
|
144
144
|
else:
|
145
|
-
|
146
|
-
|
145
|
+
embedded_params.update(inputs)
|
146
|
+
for k, v in params.items():
|
147
|
+
if k not in embedded_params:
|
148
|
+
embedded_params[k] = v
|
149
|
+
out = {
|
150
|
+
stringify(k)
|
151
|
+
if not isinstance(k, str)
|
152
|
+
else k: as_tensor(v)[None]
|
153
|
+
if as_tensor(v).ndim == 0
|
154
|
+
else v
|
155
|
+
for k, v in embedded_params.items()
|
156
|
+
}
|
147
157
|
return out
|
148
158
|
|
149
159
|
params: ParamDictType
|