qadence 1.7.8__py3-none-any.whl → 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qadence/analog/device.py +1 -1
- qadence/backend.py +3 -3
- qadence/backends/horqrux/backend.py +3 -3
- qadence/backends/pulser/backend.py +16 -17
- qadence/backends/pulser/convert_ops.py +2 -2
- qadence/backends/pyqtorch/backend.py +7 -7
- qadence/backends/pyqtorch/convert_ops.py +191 -240
- 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 +36 -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 -14
- qadence/extensions.py +1 -1
- qadence/measurements/shadow.py +4 -5
- qadence/measurements/tomography.py +2 -2
- qadence/measurements/utils.py +2 -2
- qadence/mitigations/analog_zne.py +8 -7
- qadence/mitigations/protocols.py +2 -2
- qadence/mitigations/readout.py +8 -5
- qadence/ml_tools/constructors.py +2 -2
- qadence/ml_tools/models.py +7 -7
- qadence/ml_tools/printing.py +2 -1
- qadence/model.py +5 -5
- qadence/noise/__init__.py +2 -2
- qadence/noise/protocols.py +216 -29
- qadence/operations/control_ops.py +37 -22
- qadence/operations/ham_evo.py +1 -0
- 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 +46 -0
- qadence/types.py +26 -2
- {qadence-1.7.8.dist-info → qadence-1.8.0.dist-info}/METADATA +5 -8
- {qadence-1.7.8.dist-info → qadence-1.8.0.dist-info}/RECORD +47 -50
- 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-1.7.8.dist-info → qadence-1.8.0.dist-info}/WHEEL +0 -0
- {qadence-1.7.8.dist-info → qadence-1.8.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,6 +99,81 @@ 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
178
|
block: AbstractBlock, n_qubits: int = None, config: Configuration = None
|
103
179
|
) -> Sequence[Module | Tensor | str | sympy.Expr]:
|
@@ -112,41 +188,57 @@ def convert_block(
|
|
112
188
|
if config is None:
|
113
189
|
config = Configuration()
|
114
190
|
|
191
|
+
noise: NoiseHandler | None = None
|
192
|
+
if hasattr(block, "noise") and block.noise:
|
193
|
+
noise = convert_digital_noise(block.noise)
|
194
|
+
|
115
195
|
if isinstance(block, ScaleBlock):
|
116
196
|
scaled_ops = convert_block(block.block, n_qubits, config)
|
117
|
-
scale = extract_parameter(block, config)
|
118
|
-
|
197
|
+
scale = extract_parameter(block, config=config)
|
198
|
+
|
199
|
+
# replace underscore by dot when underscore is between two numbers in string
|
200
|
+
if isinstance(scale, str):
|
201
|
+
scale = replace_underscore_floats(scale)
|
202
|
+
|
203
|
+
if isinstance(scale, str) and not config._use_gate_params:
|
204
|
+
param = sympy_to_pyq(sympy.parse_expr(scale))
|
205
|
+
else:
|
206
|
+
param = scale
|
207
|
+
|
208
|
+
return [pyq.Scale(pyq.Sequence(scaled_ops), param)]
|
119
209
|
|
120
210
|
elif isinstance(block, TimeEvolutionBlock):
|
121
211
|
if getattr(block.generator, "is_time_dependent", False):
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
check_unitary=False,
|
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(
|
212
|
+
config._use_gate_params = False
|
213
|
+
generator = convert_block(block.generator, config=config)[0] # type: ignore [arg-type]
|
214
|
+
elif isinstance(block.generator, sympy.Basic):
|
215
|
+
generator = config.get_param_name(block)[1]
|
216
|
+
|
217
|
+
elif isinstance(block.generator, Tensor):
|
218
|
+
m = block.generator.to(dtype=cdouble)
|
219
|
+
generator = convert_block(
|
220
|
+
MatrixBlock(
|
221
|
+
m,
|
141
222
|
qubit_support=qubit_support,
|
142
|
-
|
143
|
-
|
144
|
-
cache_length=0,
|
223
|
+
check_unitary=False,
|
224
|
+
check_hermitian=True,
|
145
225
|
)
|
146
|
-
]
|
226
|
+
)[0]
|
227
|
+
else:
|
228
|
+
generator = convert_block(block.generator, n_qubits, config)[0] # type: ignore[arg-type]
|
229
|
+
time_param = config.get_param_name(block)[0]
|
230
|
+
|
231
|
+
return [
|
232
|
+
pyq.HamiltonianEvolution(
|
233
|
+
qubit_support=qubit_support,
|
234
|
+
generator=generator,
|
235
|
+
time=time_param,
|
236
|
+
cache_length=0,
|
237
|
+
)
|
238
|
+
]
|
147
239
|
|
148
240
|
elif isinstance(block, MatrixBlock):
|
149
|
-
return [pyq.primitives.Primitive(block.matrix, block.qubit_support)]
|
241
|
+
return [pyq.primitives.Primitive(block.matrix, block.qubit_support, noise=noise)]
|
150
242
|
elif isinstance(block, CompositeBlock):
|
151
243
|
ops = list(flatten(*(convert_block(b, n_qubits, config) for b in block.blocks)))
|
152
244
|
if isinstance(block, AddBlock):
|
@@ -159,38 +251,66 @@ def convert_block(
|
|
159
251
|
if isinstance(block, ProjectorBlock):
|
160
252
|
projector = getattr(pyq, block.name)
|
161
253
|
if block.name == OpName.N:
|
162
|
-
return [projector(target=qubit_support)]
|
254
|
+
return [projector(target=qubit_support, noise=noise)]
|
163
255
|
else:
|
164
|
-
return [
|
256
|
+
return [
|
257
|
+
projector(
|
258
|
+
qubit_support=qubit_support,
|
259
|
+
ket=block.ket,
|
260
|
+
bra=block.bra,
|
261
|
+
noise=noise,
|
262
|
+
)
|
263
|
+
]
|
165
264
|
else:
|
166
265
|
return [getattr(pyq, block.name)(qubit_support[0])]
|
167
266
|
elif isinstance(block, tuple(single_qubit_gateset)):
|
168
267
|
pyq_cls = getattr(pyq, block.name)
|
169
268
|
if isinstance(block, ParametricBlock):
|
170
269
|
if isinstance(block, U):
|
171
|
-
op = pyq_cls(
|
270
|
+
op = pyq_cls(
|
271
|
+
qubit_support[0],
|
272
|
+
*config.get_param_name(block),
|
273
|
+
noise=noise,
|
274
|
+
)
|
172
275
|
else:
|
173
|
-
|
276
|
+
param = extract_parameter(block, config)
|
277
|
+
op = pyq_cls(qubit_support[0], param, noise=noise)
|
174
278
|
else:
|
175
|
-
op = pyq_cls(qubit_support[0])
|
279
|
+
op = pyq_cls(qubit_support[0], noise=noise) # type: ignore [attr-defined]
|
176
280
|
return [op]
|
177
281
|
elif isinstance(block, tuple(two_qubit_gateset)):
|
178
282
|
pyq_cls = getattr(pyq, block.name)
|
179
283
|
if isinstance(block, ParametricBlock):
|
180
|
-
op = pyq_cls(
|
284
|
+
op = pyq_cls(
|
285
|
+
qubit_support[0],
|
286
|
+
qubit_support[1],
|
287
|
+
extract_parameter(block, config),
|
288
|
+
noise=noise,
|
289
|
+
)
|
181
290
|
else:
|
182
|
-
op = pyq_cls(
|
291
|
+
op = pyq_cls(
|
292
|
+
qubit_support[0], qubit_support[1], noise=noise # type: ignore [attr-defined]
|
293
|
+
)
|
183
294
|
return [op]
|
184
295
|
elif isinstance(block, tuple(three_qubit_gateset) + tuple(multi_qubit_gateset)):
|
185
296
|
block_name = block.name[1:] if block.name.startswith("M") else block.name
|
186
297
|
pyq_cls = getattr(pyq, block_name)
|
187
298
|
if isinstance(block, ParametricBlock):
|
188
|
-
op = pyq_cls(
|
299
|
+
op = pyq_cls(
|
300
|
+
qubit_support[:-1],
|
301
|
+
qubit_support[-1],
|
302
|
+
extract_parameter(block, config),
|
303
|
+
noise=noise,
|
304
|
+
)
|
189
305
|
else:
|
190
306
|
if "CSWAP" in block_name:
|
191
|
-
op = pyq_cls(
|
307
|
+
op = pyq_cls(
|
308
|
+
qubit_support[:-2], qubit_support[-2:], noise=noise # type: ignore [attr-defined]
|
309
|
+
)
|
192
310
|
else:
|
193
|
-
op = pyq_cls(
|
311
|
+
op = pyq_cls(
|
312
|
+
qubit_support[:-1], qubit_support[-1], noise=noise # type: ignore [attr-defined]
|
313
|
+
)
|
194
314
|
return [op]
|
195
315
|
else:
|
196
316
|
raise NotImplementedError(
|
@@ -200,182 +320,13 @@ def convert_block(
|
|
200
320
|
)
|
201
321
|
|
202
322
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
236
|
-
|
237
|
-
def _unitary(self, hamiltonian: Tensor, time_evolution: Tensor) -> Tensor:
|
238
|
-
self.batch_size = max(hamiltonian.size()[2], len(time_evolution))
|
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
|
-
)
|
277
|
-
|
278
|
-
def _generator(val: Tensor) -> Tensor:
|
279
|
-
val_copy = values.copy()
|
280
|
-
val_copy[self.param_names[1]] = val
|
281
|
-
hmat = _block_to_tensor_embedded(
|
282
|
-
self.block.generator, # type: ignore[arg-type]
|
283
|
-
values=val_copy,
|
284
|
-
qubit_support=self.qubit_support,
|
285
|
-
use_full_support=False,
|
286
|
-
device=self.device,
|
287
|
-
)
|
288
|
-
return hmat.permute(1, 2, 0)
|
289
|
-
|
290
|
-
return finitediff(
|
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
|
-
)
|
297
|
-
|
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
|
-
|
366
|
-
return result
|
367
|
-
|
368
|
-
@property
|
369
|
-
def device(self) -> torch_device:
|
370
|
-
return self._device
|
371
|
-
|
372
|
-
@property
|
373
|
-
def dtype(self) -> torch_dtype:
|
374
|
-
return self._dtype
|
375
|
-
|
376
|
-
def to(self, *args: Any, **kwargs: Any) -> PyQTimeDependentEvolution:
|
377
|
-
if hasattr(self, "hmat"):
|
378
|
-
self.hmat = self.hmat.to(*args, **kwargs)
|
379
|
-
self._device = self.hmat.device
|
380
|
-
self._dtype = self.hmat.dtype
|
381
|
-
return self
|
323
|
+
def convert_digital_noise(noise: NoiseHandler) -> pyq.noise.NoiseProtocol:
|
324
|
+
digital_part = noise.filter(NoiseProtocol.DIGITAL)
|
325
|
+
if digital_part is None:
|
326
|
+
return None
|
327
|
+
return pyq.noise.NoiseProtocol(
|
328
|
+
[
|
329
|
+
pyq.noise.NoiseProtocol(proto, option.get("error_probability"))
|
330
|
+
for proto, option in zip(digital_part.protocol, digital_part.options)
|
331
|
+
]
|
332
|
+
)
|
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
|
qadence/blocks/matrix.py
CHANGED
@@ -8,6 +8,7 @@ import torch
|
|
8
8
|
from torch.linalg import eigvals
|
9
9
|
|
10
10
|
from qadence.blocks import PrimitiveBlock
|
11
|
+
from qadence.noise import NoiseHandler
|
11
12
|
|
12
13
|
logger = getLogger(__name__)
|
13
14
|
|
@@ -64,6 +65,7 @@ class MatrixBlock(PrimitiveBlock):
|
|
64
65
|
self,
|
65
66
|
matrix: torch.Tensor | np.ndarray,
|
66
67
|
qubit_support: tuple[int, ...],
|
68
|
+
noise: NoiseHandler | None = None,
|
67
69
|
check_unitary: bool = True,
|
68
70
|
check_hermitian: bool = False,
|
69
71
|
) -> None:
|
@@ -82,7 +84,7 @@ class MatrixBlock(PrimitiveBlock):
|
|
82
84
|
if not self.is_unitary(matrix):
|
83
85
|
logger.warning("Provided matrix is not unitary.")
|
84
86
|
self.matrix = matrix.clone()
|
85
|
-
super().__init__(qubit_support)
|
87
|
+
super().__init__(qubit_support, noise)
|
86
88
|
|
87
89
|
@cached_property
|
88
90
|
def eigenvalues_generator(self) -> torch.Tensor:
|