qadence 1.1.1__py3-none-any.whl → 1.2.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/__init__.py +4 -2
- qadence/analog/addressing.py +167 -0
- qadence/analog/constants.py +59 -0
- qadence/analog/device.py +82 -0
- qadence/analog/hamiltonian_terms.py +101 -0
- qadence/analog/parse_analog.py +120 -0
- qadence/backend.py +27 -1
- qadence/backends/braket/backend.py +1 -1
- qadence/backends/pulser/__init__.py +0 -1
- qadence/backends/pulser/backend.py +30 -15
- qadence/backends/pulser/config.py +19 -10
- qadence/backends/pulser/devices.py +57 -63
- qadence/backends/pulser/pulses.py +70 -12
- qadence/backends/pyqtorch/backend.py +2 -3
- qadence/backends/pyqtorch/config.py +18 -12
- qadence/backends/pyqtorch/convert_ops.py +12 -4
- qadence/backends/pytorch_wrapper.py +2 -1
- qadence/backends/utils.py +1 -10
- qadence/blocks/abstract.py +5 -1
- qadence/blocks/analog.py +18 -9
- qadence/blocks/block_to_tensor.py +11 -0
- qadence/blocks/primitive.py +81 -9
- qadence/constructors/__init__.py +4 -0
- qadence/constructors/feature_maps.py +84 -60
- qadence/constructors/hamiltonians.py +27 -98
- qadence/constructors/rydberg_feature_maps.py +113 -0
- qadence/divergences.py +12 -0
- qadence/extensions.py +1 -6
- qadence/finitediff.py +47 -0
- qadence/mitigations/readout.py +92 -25
- qadence/models/qnn.py +88 -23
- qadence/operations.py +55 -70
- qadence/parameters.py +10 -2
- qadence/register.py +91 -43
- qadence/transpile/__init__.py +1 -0
- qadence/transpile/apply_fn.py +40 -0
- qadence/types.py +19 -1
- qadence/utils.py +35 -0
- {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/METADATA +2 -2
- {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/RECORD +42 -36
- {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/WHEEL +1 -1
- qadence/analog/interaction.py +0 -198
- qadence/analog/utils.py +0 -132
- {qadence-1.1.1.dist-info → qadence-1.2.0.dist-info}/licenses/LICENSE +0 -0
qadence/blocks/analog.py
CHANGED
@@ -59,8 +59,8 @@ class AnalogBlock(AbstractBlock):
|
|
59
59
|
@property
|
60
60
|
def eigenvalues_generator(self) -> torch.Tensor:
|
61
61
|
msg = (
|
62
|
-
"Eigenvalues of analog blocks can be computed via "
|
63
|
-
"`
|
62
|
+
"Eigenvalues of for generator of analog blocks can be computed via "
|
63
|
+
"`add_background_hamiltonian(block, register).eigenvalues_generator`. "
|
64
64
|
)
|
65
65
|
raise NotImplementedError(msg)
|
66
66
|
|
@@ -68,7 +68,7 @@ class AnalogBlock(AbstractBlock):
|
|
68
68
|
def eigenvalues(self) -> torch.Tensor:
|
69
69
|
msg = (
|
70
70
|
"Eigenvalues of analog blocks can be computed via "
|
71
|
-
"`
|
71
|
+
"`add_background_hamiltonian(block, register).eigenvalues`. "
|
72
72
|
)
|
73
73
|
raise NotImplementedError(msg)
|
74
74
|
|
@@ -83,11 +83,19 @@ class AnalogBlock(AbstractBlock):
|
|
83
83
|
return s
|
84
84
|
|
85
85
|
def compute_eigenvalues_generator(
|
86
|
-
self,
|
86
|
+
self,
|
87
|
+
block: AbstractBlock,
|
88
|
+
register: Register,
|
87
89
|
) -> torch.Tensor:
|
88
|
-
|
90
|
+
# FIXME: Revisit analog blocks eigenvalues
|
91
|
+
from qadence.analog import add_background_hamiltonian
|
92
|
+
|
93
|
+
return add_background_hamiltonian(block, register).eigenvalues_generator # type: ignore [union-attr]
|
89
94
|
|
90
|
-
|
95
|
+
def dagger(self) -> AbstractBlock:
|
96
|
+
raise NotImplementedError(
|
97
|
+
f"Hermitian adjoint of block type {type(self)} is not implemented yet."
|
98
|
+
)
|
91
99
|
|
92
100
|
|
93
101
|
@dataclass(eq=False, repr=False)
|
@@ -108,8 +116,6 @@ class WaitBlock(AnalogBlock):
|
|
108
116
|
with `nᵢ = (1-Zᵢ)/2`.
|
109
117
|
|
110
118
|
To construct this block, use the [`wait`][qadence.operations.wait] function.
|
111
|
-
|
112
|
-
Can be used with `add_interaction`.
|
113
119
|
"""
|
114
120
|
|
115
121
|
_eigenvalues_generator: torch.Tensor | None = None
|
@@ -117,6 +123,8 @@ class WaitBlock(AnalogBlock):
|
|
117
123
|
parameters: ParamMap = ParamMap(duration=1000.0) # ns
|
118
124
|
qubit_support: QubitSupport = QubitSupport("global")
|
119
125
|
|
126
|
+
add_pattern: bool = True
|
127
|
+
|
120
128
|
@property
|
121
129
|
def eigenvalues_generator(self) -> torch.Tensor | None:
|
122
130
|
return self._eigenvalues_generator
|
@@ -145,7 +153,6 @@ class ConstantAnalogRotation(AnalogBlock):
|
|
145
153
|
[`AnalogRY`][qadence.operations.AnalogRY],
|
146
154
|
[`AnalogRZ`][qadence.operations.AnalogRZ]
|
147
155
|
|
148
|
-
Can be used with `add_interaction`.
|
149
156
|
WARNING: do not use `ConstantAnalogRotation` with `alpha` as differentiable parameter - use
|
150
157
|
the convenience wrappers mentioned above.
|
151
158
|
"""
|
@@ -161,6 +168,8 @@ class ConstantAnalogRotation(AnalogBlock):
|
|
161
168
|
)
|
162
169
|
qubit_support: QubitSupport = QubitSupport("global")
|
163
170
|
|
171
|
+
add_pattern: bool = True
|
172
|
+
|
164
173
|
@property
|
165
174
|
def _block_title(self) -> str:
|
166
175
|
a = self.parameters.alpha
|
@@ -15,8 +15,11 @@ from qadence.blocks import (
|
|
15
15
|
PrimitiveBlock,
|
16
16
|
ScaleBlock,
|
17
17
|
)
|
18
|
+
from qadence.blocks.primitive import ProjectorBlock
|
18
19
|
from qadence.blocks.utils import chain, kron, uuid_to_expression
|
19
20
|
from qadence.parameters import evaluate, stringify
|
21
|
+
|
22
|
+
# from qadence.states import product_state
|
20
23
|
from qadence.types import Endianness, TensorType, TNumber
|
21
24
|
|
22
25
|
J = torch.tensor(1j)
|
@@ -463,6 +466,14 @@ def _block_to_tensor_embedded(
|
|
463
466
|
# add missing identities on unused qubits
|
464
467
|
mat = _fill_identities(block_mat, block.qubit_support, qubit_support, endianness=endianness)
|
465
468
|
|
469
|
+
elif isinstance(block, ProjectorBlock):
|
470
|
+
from qadence.states import product_state
|
471
|
+
|
472
|
+
bra = product_state(block.bra)
|
473
|
+
ket = product_state(block.ket)
|
474
|
+
|
475
|
+
mat = torch.kron(ket, bra.T)
|
476
|
+
|
466
477
|
else:
|
467
478
|
raise TypeError(f"Conversion for block type {type(block)} not supported.")
|
468
479
|
|
qadence/blocks/primitive.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from abc import abstractmethod
|
4
|
+
from copy import deepcopy
|
4
5
|
from typing import Any, Iterable, Tuple
|
5
6
|
|
6
7
|
import sympy
|
@@ -13,6 +14,7 @@ from qadence.blocks.abstract import AbstractBlock
|
|
13
14
|
from qadence.parameters import (
|
14
15
|
Parameter,
|
15
16
|
ParamMap,
|
17
|
+
dagger_expression,
|
16
18
|
evaluate,
|
17
19
|
extract_original_param_entry,
|
18
20
|
stringify,
|
@@ -101,6 +103,9 @@ class PrimitiveBlock(AbstractBlock):
|
|
101
103
|
def n_supports(self) -> int:
|
102
104
|
return len(self.qubit_support)
|
103
105
|
|
106
|
+
def dagger(self) -> PrimitiveBlock:
|
107
|
+
return self
|
108
|
+
|
104
109
|
|
105
110
|
class ParametricBlock(PrimitiveBlock):
|
106
111
|
"""Parameterized primitive blocks."""
|
@@ -200,11 +205,10 @@ class ParametricBlock(PrimitiveBlock):
|
|
200
205
|
target = d["qubit_support"][0]
|
201
206
|
return cls(target, params) # type: ignore[call-arg]
|
202
207
|
|
203
|
-
def dagger(self) -> ParametricBlock:
|
208
|
+
def dagger(self) -> ParametricBlock:
|
204
209
|
exprs = self.parameters.expressions()
|
205
|
-
|
206
|
-
|
207
|
-
return self.__class__(*args) # type: ignore[arg-type]
|
210
|
+
params = tuple(-extract_original_param_entry(param) for param in exprs)
|
211
|
+
return type(self)(*self.qubit_support, *params) # type: ignore[arg-type]
|
208
212
|
|
209
213
|
|
210
214
|
class ScaleBlock(ParametricBlock):
|
@@ -304,9 +308,8 @@ class ScaleBlock(ParametricBlock):
|
|
304
308
|
)
|
305
309
|
|
306
310
|
def dagger(self) -> ScaleBlock:
|
307
|
-
|
308
|
-
|
309
|
-
)
|
311
|
+
p = list(self.parameters.expressions())[0]
|
312
|
+
return self.__class__(self.block.dagger(), dagger_expression(p))
|
310
313
|
|
311
314
|
def _to_dict(self) -> dict:
|
312
315
|
return {
|
@@ -350,13 +353,25 @@ class ControlBlock(PrimitiveBlock):
|
|
350
353
|
"""The abstract ControlBlock."""
|
351
354
|
|
352
355
|
name = "Control"
|
356
|
+
control: tuple[int, ...]
|
357
|
+
target: tuple[int, ...]
|
353
358
|
|
354
359
|
def __init__(self, control: tuple[int, ...], target_block: PrimitiveBlock) -> None:
|
360
|
+
self.control = control
|
355
361
|
self.blocks = (target_block,)
|
362
|
+
self.target = target_block.qubit_support
|
356
363
|
|
357
364
|
# using tuple expansion because some control operations could
|
358
365
|
# have multiple targets, e.g. CSWAP
|
359
|
-
super().__init__((*control, *
|
366
|
+
super().__init__((*control, *self.target)) # target_block.qubit_support[0]))
|
367
|
+
|
368
|
+
@property
|
369
|
+
def n_controls(self) -> int:
|
370
|
+
return len(self.control)
|
371
|
+
|
372
|
+
@property
|
373
|
+
def n_targets(self) -> int:
|
374
|
+
return len(self.target)
|
360
375
|
|
361
376
|
@property
|
362
377
|
def _block_title(self) -> str:
|
@@ -391,16 +406,28 @@ class ControlBlock(PrimitiveBlock):
|
|
391
406
|
target = d["qubit_support"][1]
|
392
407
|
return cls(control, target)
|
393
408
|
|
409
|
+
def dagger(self) -> ControlBlock:
|
410
|
+
blk = deepcopy(self)
|
411
|
+
blk.blocks = (self.blocks[0].dagger(),)
|
412
|
+
return blk
|
413
|
+
|
394
414
|
|
395
415
|
class ParametricControlBlock(ParametricBlock):
|
396
416
|
"""The abstract parametrized ControlBlock."""
|
397
417
|
|
398
418
|
name = "ParameterizedControl"
|
419
|
+
control: tuple[int, ...] = ()
|
420
|
+
blocks: tuple[ParametricBlock, ...]
|
399
421
|
|
400
422
|
def __init__(self, control: tuple[int, ...], target_block: ParametricBlock) -> None:
|
401
423
|
self.blocks = (target_block,)
|
424
|
+
self.control = control
|
402
425
|
self.parameters = target_block.parameters
|
403
|
-
super().__init__((*control, target_block.qubit_support
|
426
|
+
super().__init__((*control, *target_block.qubit_support))
|
427
|
+
|
428
|
+
@property
|
429
|
+
def n_controls(self) -> int:
|
430
|
+
return len(self.control)
|
404
431
|
|
405
432
|
@property
|
406
433
|
def eigenvalues_generator(self) -> torch.Tensor:
|
@@ -454,3 +481,48 @@ class ParametricControlBlock(ParametricBlock):
|
|
454
481
|
|
455
482
|
s += rf" \[params: {params_str}]"
|
456
483
|
return s if self.tag is None else (s + rf" \[tag: {self.tag}]")
|
484
|
+
|
485
|
+
def dagger(self) -> ParametricControlBlock:
|
486
|
+
blk = deepcopy(self)
|
487
|
+
blocks = tuple(b.dagger() for b in blk.blocks)
|
488
|
+
blk.blocks = blocks
|
489
|
+
blk.parameters = blocks[0].parameters
|
490
|
+
return blk
|
491
|
+
|
492
|
+
|
493
|
+
class ProjectorBlock(PrimitiveBlock):
|
494
|
+
"""The abstract ProjectorBlock."""
|
495
|
+
|
496
|
+
name = "ProjectorBlock"
|
497
|
+
|
498
|
+
def __init__(
|
499
|
+
self,
|
500
|
+
ket: str,
|
501
|
+
bra: str,
|
502
|
+
qubit_support: int | tuple[int, ...],
|
503
|
+
) -> None:
|
504
|
+
"""
|
505
|
+
Arguments:
|
506
|
+
|
507
|
+
ket (str): The ket given as a bitstring.
|
508
|
+
bra (str): The bra given as a bitstring.
|
509
|
+
qubit_support (int | tuple[int]): The qubit_support of the block.
|
510
|
+
"""
|
511
|
+
if isinstance(qubit_support, int):
|
512
|
+
qubit_support = (qubit_support,)
|
513
|
+
if len(bra) != len(ket):
|
514
|
+
raise ValueError(
|
515
|
+
"Bra and ket must be bitstrings of same length in the 'Projector' definition."
|
516
|
+
)
|
517
|
+
elif len(bra) != len(qubit_support):
|
518
|
+
raise ValueError("Bra or ket must be of same length as the 'qubit_support'")
|
519
|
+
for wf in [bra, ket]:
|
520
|
+
if not all(int(item) == 0 or int(item) == 1 for item in wf):
|
521
|
+
raise ValueError(
|
522
|
+
"All qubits must be either in the '0' or '1' state"
|
523
|
+
" in the 'ProjectorBlock' definition."
|
524
|
+
)
|
525
|
+
|
526
|
+
self.ket = ket
|
527
|
+
self.bra = bra
|
528
|
+
super().__init__(qubit_support)
|
qadence/constructors/__init__.py
CHANGED
@@ -23,6 +23,7 @@ from .hamiltonians import (
|
|
23
23
|
)
|
24
24
|
|
25
25
|
from .rydberg_hea import rydberg_hea, rydberg_hea_layer
|
26
|
+
from .rydberg_feature_maps import rydberg_feature_map, analog_feature_map, rydberg_tower_feature_map
|
26
27
|
|
27
28
|
from .qft import qft
|
28
29
|
|
@@ -45,4 +46,7 @@ __all__ = [
|
|
45
46
|
"daqc_transform",
|
46
47
|
"rydberg_hea",
|
47
48
|
"rydberg_hea_layer",
|
49
|
+
"rydberg_feature_map",
|
50
|
+
"analog_feature_map",
|
51
|
+
"rydberg_tower_feature_map",
|
48
52
|
]
|
@@ -6,7 +6,7 @@ from collections.abc import Callable
|
|
6
6
|
from math import isclose, pi
|
7
7
|
from typing import Union
|
8
8
|
|
9
|
-
from sympy import Function, acos
|
9
|
+
from sympy import Basic, Function, acos
|
10
10
|
|
11
11
|
from qadence.blocks import AbstractBlock, KronBlock, chain, kron, tag
|
12
12
|
from qadence.logger import get_logger
|
@@ -36,65 +36,12 @@ RS_FUNC_DICT = {
|
|
36
36
|
}
|
37
37
|
|
38
38
|
|
39
|
-
def
|
40
|
-
|
41
|
-
support: tuple[int, ...] | None = None,
|
39
|
+
def fm_parameter(
|
40
|
+
fm_type: BasisSet | type[Function] | str,
|
42
41
|
param: Parameter | str = "phi",
|
43
|
-
op: RotationTypes = RX,
|
44
|
-
fm_type: BasisSet | type[Function] | str = BasisSet.FOURIER,
|
45
|
-
reupload_scaling: ReuploadScaling | Callable | str = ReuploadScaling.CONSTANT,
|
46
42
|
feature_range: tuple[float, float] | None = None,
|
47
43
|
target_range: tuple[float, float] | None = None,
|
48
|
-
|
49
|
-
) -> KronBlock:
|
50
|
-
"""Construct a feature map of a given type.
|
51
|
-
|
52
|
-
Arguments:
|
53
|
-
n_qubits: Number of qubits the feature map covers. Results in `support=range(n_qubits)`.
|
54
|
-
support: Puts one feature-encoding rotation gate on every qubit in `support`. n_qubits in
|
55
|
-
this case specifies the total overall qubits of the circuit, which may be wider than the
|
56
|
-
support itself, but not narrower.
|
57
|
-
param: Parameter of the feature map; you can pass a string or Parameter;
|
58
|
-
it will be set as non-trainable (FeatureParameter) regardless.
|
59
|
-
op: Rotation operation of the feature map; choose from RX, RY, RZ or PHASE.
|
60
|
-
fm_type: Basis set for data encoding; choose from `BasisSet.FOURIER` for Fourier
|
61
|
-
encoding, or `BasisSet.CHEBYSHEV` for Chebyshev polynomials of the first kind.
|
62
|
-
reupload_scaling: how the feature map scales the data that is re-uploaded for each qubit.
|
63
|
-
choose from `ReuploadScaling` enumeration or provide your own function with a single
|
64
|
-
int as input and int or float as output.
|
65
|
-
feature_range: range of data that the input data is assumed to come from.
|
66
|
-
target_range: range of data the data encoder assumes as the natural range. For example,
|
67
|
-
in Chebyshev polynomials it is (-1, 1), while for Fourier it may be chosen as (0, 2*pi).
|
68
|
-
multiplier: overall multiplier; this is useful for reuploading the feature map serially with
|
69
|
-
different scalings; can be a number or parameter/expression.
|
70
|
-
|
71
|
-
Example:
|
72
|
-
```python exec="on" source="material-block" result="json"
|
73
|
-
from qadence import feature_map, BasisSet, ReuploadScaling
|
74
|
-
|
75
|
-
fm = feature_map(3, fm_type=BasisSet.FOURIER)
|
76
|
-
print(f"{fm = }")
|
77
|
-
|
78
|
-
fm = feature_map(3, fm_type=BasisSet.CHEBYSHEV)
|
79
|
-
print(f"{fm = }")
|
80
|
-
|
81
|
-
fm = feature_map(3, fm_type=BasisSet.FOURIER, reupload_scaling = ReuploadScaling.TOWER)
|
82
|
-
print(f"{fm = }")
|
83
|
-
```
|
84
|
-
"""
|
85
|
-
|
86
|
-
# Process input
|
87
|
-
if support is None:
|
88
|
-
support = tuple(range(n_qubits))
|
89
|
-
elif len(support) != n_qubits:
|
90
|
-
raise ValueError("Wrong qubit support supplied")
|
91
|
-
|
92
|
-
if op not in ROTATIONS:
|
93
|
-
raise ValueError(
|
94
|
-
f"Operation {op} not supported. "
|
95
|
-
f"Please provide one from {[rot.__name__ for rot in ROTATIONS]}."
|
96
|
-
)
|
97
|
-
|
44
|
+
) -> Parameter | Basic:
|
98
45
|
# Backwards compatibility
|
99
46
|
if fm_type in ("fourier", "chebyshev", "tower"):
|
100
47
|
logger.warning(
|
@@ -108,7 +55,6 @@ def feature_map(
|
|
108
55
|
fm_type = BasisSet.CHEBYSHEV
|
109
56
|
elif fm_type == "tower":
|
110
57
|
fm_type = BasisSet.CHEBYSHEV
|
111
|
-
reupload_scaling = ReuploadScaling.TOWER
|
112
58
|
|
113
59
|
if isinstance(param, Parameter):
|
114
60
|
fparam = param
|
@@ -144,8 +90,12 @@ def feature_map(
|
|
144
90
|
"the given feature parameter with."
|
145
91
|
)
|
146
92
|
|
147
|
-
|
93
|
+
return transformed_feature
|
94
|
+
|
148
95
|
|
96
|
+
def fm_reupload_scaling_fn(
|
97
|
+
reupload_scaling: ReuploadScaling | Callable | str = ReuploadScaling.CONSTANT,
|
98
|
+
) -> tuple[Callable, str]:
|
149
99
|
# Set reupload scaling function
|
150
100
|
if callable(reupload_scaling):
|
151
101
|
rs_func = reupload_scaling
|
@@ -163,8 +113,82 @@ def feature_map(
|
|
163
113
|
else:
|
164
114
|
rs_tag = reupload_scaling
|
165
115
|
|
116
|
+
return rs_func, rs_tag
|
117
|
+
|
118
|
+
|
119
|
+
def feature_map(
|
120
|
+
n_qubits: int,
|
121
|
+
support: tuple[int, ...] | None = None,
|
122
|
+
param: Parameter | str = "phi",
|
123
|
+
op: RotationTypes = RX,
|
124
|
+
fm_type: BasisSet | type[Function] | str = BasisSet.FOURIER,
|
125
|
+
reupload_scaling: ReuploadScaling | Callable | str = ReuploadScaling.CONSTANT,
|
126
|
+
feature_range: tuple[float, float] | None = None,
|
127
|
+
target_range: tuple[float, float] | None = None,
|
128
|
+
multiplier: Parameter | TParameter | None = None,
|
129
|
+
) -> KronBlock:
|
130
|
+
"""Construct a feature map of a given type.
|
131
|
+
|
132
|
+
Arguments:
|
133
|
+
n_qubits: Number of qubits the feature map covers. Results in `support=range(n_qubits)`.
|
134
|
+
support: Puts one feature-encoding rotation gate on every qubit in `support`. n_qubits in
|
135
|
+
this case specifies the total overall qubits of the circuit, which may be wider than the
|
136
|
+
support itself, but not narrower.
|
137
|
+
param: Parameter of the feature map; you can pass a string or Parameter;
|
138
|
+
it will be set as non-trainable (FeatureParameter) regardless.
|
139
|
+
op: Rotation operation of the feature map; choose from RX, RY, RZ or PHASE.
|
140
|
+
fm_type: Basis set for data encoding; choose from `BasisSet.FOURIER` for Fourier
|
141
|
+
encoding, or `BasisSet.CHEBYSHEV` for Chebyshev polynomials of the first kind.
|
142
|
+
reupload_scaling: how the feature map scales the data that is re-uploaded for each qubit.
|
143
|
+
choose from `ReuploadScaling` enumeration or provide your own function with a single
|
144
|
+
int as input and int or float as output.
|
145
|
+
feature_range: range of data that the input data is assumed to come from.
|
146
|
+
target_range: range of data the data encoder assumes as the natural range. For example,
|
147
|
+
in Chebyshev polynomials it is (-1, 1), while for Fourier it may be chosen as (0, 2*pi).
|
148
|
+
multiplier: overall multiplier; this is useful for reuploading the feature map serially with
|
149
|
+
different scalings; can be a number or parameter/expression.
|
150
|
+
|
151
|
+
Example:
|
152
|
+
```python exec="on" source="material-block" result="json"
|
153
|
+
from qadence import feature_map, BasisSet, ReuploadScaling
|
154
|
+
|
155
|
+
fm = feature_map(3, fm_type=BasisSet.FOURIER)
|
156
|
+
print(f"{fm = }")
|
157
|
+
|
158
|
+
fm = feature_map(3, fm_type=BasisSet.CHEBYSHEV)
|
159
|
+
print(f"{fm = }")
|
160
|
+
|
161
|
+
fm = feature_map(3, fm_type=BasisSet.FOURIER, reupload_scaling = ReuploadScaling.TOWER)
|
162
|
+
print(f"{fm = }")
|
163
|
+
```
|
164
|
+
"""
|
165
|
+
|
166
|
+
# Process input
|
167
|
+
if support is None:
|
168
|
+
support = tuple(range(n_qubits))
|
169
|
+
elif len(support) != n_qubits:
|
170
|
+
raise ValueError("Wrong qubit support supplied")
|
171
|
+
|
172
|
+
if op not in ROTATIONS:
|
173
|
+
raise ValueError(
|
174
|
+
f"Operation {op} not supported. "
|
175
|
+
f"Please provide one from {[rot.__name__ for rot in ROTATIONS]}."
|
176
|
+
)
|
177
|
+
|
178
|
+
transformed_feature = fm_parameter(
|
179
|
+
fm_type, param, feature_range=feature_range, target_range=target_range
|
180
|
+
)
|
181
|
+
|
182
|
+
# Backwards compatibility
|
183
|
+
if fm_type == "tower":
|
184
|
+
logger.warning("Forcing reupload scaling strategy to TOWER")
|
185
|
+
reupload_scaling = ReuploadScaling.TOWER
|
186
|
+
|
187
|
+
basis_tag = fm_type.value if isinstance(fm_type, BasisSet) else str(fm_type)
|
188
|
+
rs_func, rs_tag = fm_reupload_scaling_fn(reupload_scaling)
|
189
|
+
|
166
190
|
# Set overall multiplier
|
167
|
-
multiplier = 1 if multiplier is None else multiplier
|
191
|
+
multiplier = 1 if multiplier is None else Parameter(multiplier)
|
168
192
|
|
169
193
|
# Build feature map
|
170
194
|
op_list = []
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import
|
4
|
-
from typing import List, Tuple, Type, Union
|
3
|
+
from typing import List, Type, Union
|
5
4
|
|
6
5
|
import numpy as np
|
7
6
|
from torch import Tensor, double, ones, rand
|
@@ -57,8 +56,7 @@ def hamiltonian_factory(
|
|
57
56
|
interaction_strength: TArray | str | None = None,
|
58
57
|
detuning_strength: TArray | str | None = None,
|
59
58
|
random_strength: bool = False,
|
60
|
-
|
61
|
-
use_complete_graph: bool = False,
|
59
|
+
use_all_node_pairs: bool = False,
|
62
60
|
) -> AbstractBlock:
|
63
61
|
"""
|
64
62
|
General Hamiltonian creation function.
|
@@ -80,9 +78,8 @@ def hamiltonian_factory(
|
|
80
78
|
Alternatively, some string "x" can be passed, which will create a parameterized
|
81
79
|
detuning for each qubit, each labelled as `"x_i"`.
|
82
80
|
random_strength: set random interaction and detuning strengths between -1 and 1.
|
83
|
-
|
84
|
-
|
85
|
-
independent of the edges in the register. Useful for defining Hamiltonians
|
81
|
+
use_all_node_pairs: computes an interaction term for every pair of nodes in the graph,
|
82
|
+
independent of the edge topology in the register. Useful for defining Hamiltonians
|
86
83
|
where the interaction strength decays with the distance.
|
87
84
|
|
88
85
|
Examples:
|
@@ -121,12 +118,9 @@ def hamiltonian_factory(
|
|
121
118
|
register = Register(register) if isinstance(register, int) else register
|
122
119
|
|
123
120
|
# Get interaction function
|
124
|
-
|
125
|
-
int_fn = INTERACTION_DICT
|
126
|
-
|
127
|
-
if interaction is None:
|
128
|
-
pass
|
129
|
-
else:
|
121
|
+
if interaction is not None:
|
122
|
+
int_fn = INTERACTION_DICT.get(interaction, None)
|
123
|
+
if int_fn is None:
|
130
124
|
raise KeyError(f"Interaction {interaction} not supported.")
|
131
125
|
|
132
126
|
# Check single-qubit detuning
|
@@ -134,37 +128,27 @@ def hamiltonian_factory(
|
|
134
128
|
raise TypeError(f"Detuning of type {type(detuning)} not supported.")
|
135
129
|
|
136
130
|
# Pre-process detuning and interaction strengths and update register
|
137
|
-
|
138
|
-
register, detuning_strength, "nodes",
|
131
|
+
detuning_strength_array = _preprocess_strengths(
|
132
|
+
register, detuning_strength, "nodes", random_strength
|
139
133
|
)
|
140
134
|
|
141
|
-
edge_str = "
|
142
|
-
|
143
|
-
register, interaction_strength, edge_str,
|
135
|
+
edge_str = "all_node_pairs" if use_all_node_pairs else "edges"
|
136
|
+
interaction_strength_array = _preprocess_strengths(
|
137
|
+
register, interaction_strength, edge_str, random_strength
|
144
138
|
)
|
145
139
|
|
146
|
-
if (not has_detuning_strength) or force_update:
|
147
|
-
register = _update_detuning_strength(register, detuning_strength)
|
148
|
-
|
149
|
-
if (not has_interaction_strength) or force_update:
|
150
|
-
register = _update_interaction_strength(register, interaction_strength, use_complete_graph)
|
151
|
-
|
152
140
|
# Create single-qubit detunings:
|
153
141
|
single_qubit_terms: List[AbstractBlock] = []
|
154
142
|
if detuning is not None:
|
155
|
-
for node in register.nodes:
|
156
|
-
|
157
|
-
strength_sq = register.nodes[node]["strength"]
|
158
|
-
single_qubit_terms.append(strength_sq * block_sq)
|
143
|
+
for strength, node in zip(detuning_strength_array, register.nodes):
|
144
|
+
single_qubit_terms.append(strength * detuning(node))
|
159
145
|
|
160
146
|
# Create two-qubit interactions:
|
161
147
|
two_qubit_terms: List[AbstractBlock] = []
|
162
|
-
edge_data = register.
|
163
|
-
if interaction is not None:
|
164
|
-
for edge in edge_data:
|
165
|
-
|
166
|
-
strength_tq = edge_data[edge]["strength"]
|
167
|
-
two_qubit_terms.append(strength_tq * block_tq)
|
148
|
+
edge_data = register.all_node_pairs if use_all_node_pairs else register.edges
|
149
|
+
if interaction is not None and int_fn is not None:
|
150
|
+
for strength, edge in zip(interaction_strength_array, edge_data):
|
151
|
+
two_qubit_terms.append(strength * int_fn(*edge))
|
168
152
|
|
169
153
|
return add(*single_qubit_terms, *two_qubit_terms)
|
170
154
|
|
@@ -173,22 +157,13 @@ def _preprocess_strengths(
|
|
173
157
|
register: Register,
|
174
158
|
strength: TArray | str | None,
|
175
159
|
nodes_or_edges: str,
|
176
|
-
force_update: bool,
|
177
160
|
random_strength: bool,
|
178
|
-
) ->
|
161
|
+
) -> Tensor | list:
|
179
162
|
data = getattr(register, nodes_or_edges)
|
180
163
|
|
181
164
|
# Useful for error messages:
|
182
165
|
strength_target = "detuning" if nodes_or_edges == "nodes" else "interaction"
|
183
166
|
|
184
|
-
# First we check if strength values already exist in the register
|
185
|
-
has_strength = any(["strength" in data[i] for i in data])
|
186
|
-
if has_strength and not force_update:
|
187
|
-
if strength is not None:
|
188
|
-
logger.warning(
|
189
|
-
"Register already includes " + strength_target + " strengths. "
|
190
|
-
"Skipping update. Use `force_update = True` to override them."
|
191
|
-
)
|
192
167
|
# Next we process the strength given in the input arguments
|
193
168
|
if strength is None:
|
194
169
|
if random_strength:
|
@@ -202,8 +177,11 @@ def _preprocess_strengths(
|
|
202
177
|
message = "Array of " + strength_target + " strengths has incorrect size."
|
203
178
|
raise ValueError(message)
|
204
179
|
elif isinstance(strength, str):
|
205
|
-
|
206
|
-
|
180
|
+
prefix = strength
|
181
|
+
if nodes_or_edges == "nodes":
|
182
|
+
strength = [prefix + f"_{node}" for node in data]
|
183
|
+
if nodes_or_edges in ["edges", "all_node_pairs"]:
|
184
|
+
strength = [prefix + f"_{edge[0]}{edge[1]}" for edge in data]
|
207
185
|
else:
|
208
186
|
# If not of the accepted types ARRAYS or str, we error out
|
209
187
|
raise TypeError(
|
@@ -212,69 +190,27 @@ def _preprocess_strengths(
|
|
212
190
|
"parameterized " + strength_target + "s."
|
213
191
|
)
|
214
192
|
|
215
|
-
return
|
216
|
-
|
217
|
-
|
218
|
-
def _update_detuning_strength(register: Register, detuning_strength: TArray | str) -> Register:
|
219
|
-
for node in register.nodes:
|
220
|
-
if isinstance(detuning_strength, str):
|
221
|
-
register.nodes[node]["strength"] = detuning_strength + f"_{node}"
|
222
|
-
elif isinstance(detuning_strength, ARRAYS):
|
223
|
-
register.nodes[node]["strength"] = detuning_strength[node]
|
224
|
-
return register
|
225
|
-
|
226
|
-
|
227
|
-
def _update_interaction_strength(
|
228
|
-
register: Register, interaction_strength: TArray | str, use_complete_graph: bool
|
229
|
-
) -> Register:
|
230
|
-
edge_data = register.all_edges if use_complete_graph else register.edges
|
231
|
-
for idx, edge in enumerate(edge_data):
|
232
|
-
if isinstance(interaction_strength, str):
|
233
|
-
edge_data[edge]["strength"] = interaction_strength + f"_{edge[0]}{edge[1]}"
|
234
|
-
elif isinstance(interaction_strength, ARRAYS):
|
235
|
-
edge_data[edge]["strength"] = interaction_strength[idx]
|
236
|
-
return register
|
237
|
-
|
193
|
+
return strength
|
238
194
|
|
239
|
-
# FIXME: Previous hamiltonian / observable functions, now refactored, to be deprecated:
|
240
195
|
|
241
|
-
|
196
|
+
def total_magnetization(n_qubits: int, z_terms: np.ndarray | list | None = None) -> AbstractBlock:
|
197
|
+
return hamiltonian_factory(n_qubits, detuning=Z, detuning_strength=z_terms)
|
242
198
|
|
243
199
|
|
244
200
|
def single_z(qubit: int = 0, z_coefficient: float = 1.0) -> AbstractBlock:
|
245
|
-
message = DEPRECATION_MESSAGE + "Please use `z_coefficient * Z(qubit)` directly."
|
246
|
-
warnings.warn(message, FutureWarning)
|
247
201
|
return Z(qubit) * z_coefficient
|
248
202
|
|
249
203
|
|
250
|
-
def total_magnetization(n_qubits: int, z_terms: np.ndarray | list | None = None) -> AbstractBlock:
|
251
|
-
message = (
|
252
|
-
DEPRECATION_MESSAGE
|
253
|
-
+ "Please use `hamiltonian_factory(n_qubits, detuning=Z, node_coeff=z_terms)`."
|
254
|
-
)
|
255
|
-
warnings.warn(message, FutureWarning)
|
256
|
-
return hamiltonian_factory(n_qubits, detuning=Z, detuning_strength=z_terms)
|
257
|
-
|
258
|
-
|
259
204
|
def zz_hamiltonian(
|
260
205
|
n_qubits: int,
|
261
206
|
z_terms: np.ndarray | None = None,
|
262
207
|
zz_terms: np.ndarray | None = None,
|
263
208
|
) -> AbstractBlock:
|
264
|
-
message = (
|
265
|
-
DEPRECATION_MESSAGE
|
266
|
-
+ """
|
267
|
-
Please use `hamiltonian_factory(n_qubits, Interaction.ZZ, Z, interaction_strength, z_terms)`. \
|
268
|
-
Note that the argument `zz_terms` in this function is a 2D array of size `(n_qubits, n_qubits)`, \
|
269
|
-
while `interaction_strength` is expected as a 1D array of size `0.5 * n_qubits * (n_qubits - 1)`."""
|
270
|
-
)
|
271
|
-
warnings.warn(message, FutureWarning)
|
272
209
|
if zz_terms is not None:
|
273
210
|
register = Register(n_qubits)
|
274
211
|
interaction_strength = [zz_terms[edge[0], edge[1]] for edge in register.edges]
|
275
212
|
else:
|
276
213
|
interaction_strength = None
|
277
|
-
|
278
214
|
return hamiltonian_factory(n_qubits, Interaction.ZZ, Z, interaction_strength, z_terms)
|
279
215
|
|
280
216
|
|
@@ -284,13 +220,6 @@ def ising_hamiltonian(
|
|
284
220
|
z_terms: np.ndarray | None = None,
|
285
221
|
zz_terms: np.ndarray | None = None,
|
286
222
|
) -> AbstractBlock:
|
287
|
-
message = (
|
288
|
-
DEPRECATION_MESSAGE
|
289
|
-
+ """
|
290
|
-
You can build a general transverse field ising model with the `hamiltonian_factory` function. \
|
291
|
-
Check the hamiltonian construction tutorial in the documentation for more information."""
|
292
|
-
)
|
293
|
-
warnings.warn(message, FutureWarning)
|
294
223
|
zz_ham = zz_hamiltonian(n_qubits, z_terms=z_terms, zz_terms=zz_terms)
|
295
224
|
x_ham = hamiltonian_factory(n_qubits, detuning=X, detuning_strength=x_terms)
|
296
225
|
return zz_ham + x_ham
|